def create_stack_domain_user(self, username, project_id, password=None): """ Create a user defined as part of a stack, either via template or created internally by a resource. This user will be added to the heat_stack_user_role as defined in the config, and created in the specified project (which is expected to be in the stack_domain. Returns the keystone ID of the resulting user """ # We add the new user to a special keystone role # This role is designed to allow easier differentiation of the # heat-generated "stack users" which will generally have credentials # deployed on an instance (hence are implicitly untrusted) roles_list = self.domain_admin_client.roles.list() role_id = self._get_stack_user_role(roles_list) if role_id: # Create user user = self.domain_admin_client.users.create( name=self._get_username(username), password=password, default_project=project_id, domain=self.stack_domain_id) # Add to stack user role logger.debug(_("Adding user %(user)s to role %(role)s") % { 'user': user.id, 'role': role_id}) self.domain_admin_client.roles.grant(role=role_id, user=user.id, project=project_id) else: logger.error(_("Failed to add user %(user)s to role %(role)s, " "check role exists!") % {'user': username, 'role': cfg.CONF.heat_stack_user_role}) raise exception.Error(_("Can't find role %s") % cfg.CONF.heat_stack_user_role) return user.id
def validate(self): ''' Validate any of the provided params ''' res = super(Instance, self).validate() if res: return res # check validity of security groups vs. network interfaces security_groups = self._get_security_groups() if security_groups and self.properties.get(self.NETWORK_INTERFACES): raise exception.ResourcePropertyConflict( '/'.join([self.SECURITY_GROUPS, self.SECURITY_GROUP_IDS]), self.NETWORK_INTERFACES) # check bdm property # now we don't support without snapshot_id in bdm bdm = self.properties.get(self.BLOCK_DEVICE_MAPPINGS) if bdm: for mapping in bdm: ebs = mapping.get(self.EBS) if ebs: snapshot_id = ebs.get(self.SNAPSHOT_ID) if not snapshot_id: msg = _("SnapshotId is missing, this is required " "when specifying BlockDeviceMappings.") raise exception.StackValidationFailed(message=msg) else: msg = _("Ebs is missing, this is required " "when specifying BlockDeviceMappings.") raise exception.StackValidationFailed(message=msg)
def _check_active(self, cookie): server, volume_attach = cookie if not volume_attach.started(): if server.status != 'ACTIVE': server.get() # Some clouds append extra (STATUS) strings to the status short_server_status = server.status.split('(')[0] if short_server_status in nova_utils.deferred_server_statuses: return False elif server.status == 'ACTIVE': self._set_ipaddress(server.networks) volume_attach.start() return volume_attach.done() elif server.status == 'ERROR': fault = getattr(server, 'fault', {}) message = fault.get('message', 'Unknown') code = fault.get('code', 500) exc = exception.Error(_("Creation of server %(server)s " "failed: %(message)s (%(code)s)") % dict(server=server.name, message=message, code=code)) raise exc else: exc = exception.Error(_("Creation of server %(server)s failed " "with unknown status: %(status)s") % dict(server=server.name, status=server.status)) raise exc else: return volume_attach.step()
def _check_stack_domain_user(self, user_id, project_id, action): # Sanity check that domain/project is correct user = self.domain_admin_client.users.get(user_id) if user.domain_id != self.stack_domain_id: raise ValueError(_('User %s in invalid domain') % action) if user.default_project_id != project_id: raise ValueError(_('User %s in invalid project') % action)
def handle_signal(self, details=None): if self.action in (self.SUSPEND, self.DELETE): msg = _('Cannot signal resource during %s') % self.action raise Exception(msg) if details is None: alarm_state = 'alarm' else: alarm_state = details.get('state', 'alarm').lower() LOG.info(_('%(name)s Alarm, new state %(state)s') % {'name': self.name, 'state': alarm_state}) if alarm_state != 'alarm': return victim = self._find_resource(self.properties[self.INSTANCE_ID]) if victim is None: LOG.info(_('%(name)s Alarm, can not find instance %(instance)s') % {'name': self.name, 'instance': self.properties[self.INSTANCE_ID]}) return LOG.info(_('%(name)s Alarm, restarting resource: %(victim)s') % {'name': self.name, 'victim': victim.name}) self.stack.restart_resource(victim.name)
def create_consumer(self, topic, proxy, fanout=False): # Register with matchmaker. _get_matchmaker().register(topic, CONF.rpc_zmq_host) # Subscription scenarios if fanout: sock_type = zmq.SUB subscribe = ('', fanout)[type(fanout) == str] topic = 'fanout~' + topic.split('.', 1)[0] else: sock_type = zmq.PULL subscribe = None topic = '.'.join((topic.split('.', 1)[0], CONF.rpc_zmq_host)) if topic in self.topics: LOG.info(_("Skipping topic registration. Already registered.")) return # Receive messages from (local) proxy inaddr = "ipc://%s/zmq_topic_%s" % \ (CONF.rpc_zmq_ipc_dir, topic) LOG.debug(_("Consumer is a zmq.%s"), ['PULL', 'SUB'][sock_type == zmq.SUB]) self.reactor.register(proxy, inaddr, sock_type, subscribe=subscribe, in_bind=False) self.topics.append(topic)
def _multi_send(method, context, topic, msg, timeout=None, envelope=False, _msg_id=None): """Wraps the sending of messages. Dispatches to the matchmaker and sends message to all relevant hosts. """ conf = CONF LOG.debug(_("%(msg)s") % {'msg': ' '.join(map(pformat, (topic, msg)))}) queues = _get_matchmaker().queues(topic) LOG.debug(_("Sending message(s) to: %s"), queues) # Don't stack if we have no matchmaker results if not queues: LOG.warn(_("No matchmaker results. Not casting.")) # While not strictly a timeout, callers know how to handle # this exception and a timeout isn't too big a lie. raise rpc_common.Timeout(_("No match from matchmaker.")) # This supports brokerless fanout (addresses > 1) for queue in queues: (_topic, ip_addr) = queue _addr = "tcp://%s:%s" % (ip_addr, conf.rpc_zmq_port) if method.__name__ == '_cast': eventlet.spawn_n(method, _addr, context, _topic, msg, timeout, envelope, _msg_id) return return method(_addr, context, _topic, msg, timeout, envelope)
def consume_in_thread(self): """Runs the ZmqProxy service.""" ipc_dir = CONF.rpc_zmq_ipc_dir consume_in = "tcp://%s:%s" % \ (CONF.rpc_zmq_bind_address, CONF.rpc_zmq_port) consumption_proxy = InternalContext(None) try: os.makedirs(ipc_dir) except os.error: if not os.path.isdir(ipc_dir): with excutils.save_and_reraise_exception(): LOG.error(_("Required IPC directory does not exist at" " %s") % (ipc_dir, )) try: self.register(consumption_proxy, consume_in, zmq.PULL) except zmq.ZMQError: if os.access(ipc_dir, os.X_OK): with excutils.save_and_reraise_exception(): LOG.error(_("Permission denied to IPC directory at" " %s") % (ipc_dir, )) with excutils.save_and_reraise_exception(): LOG.error(_("Could not create ZeroMQ receiver daemon. " "Socket may already be in use.")) super(ZmqProxy, self).consume_in_thread()
def consume(self, sock): #TODO(ewindisch): use zero-copy (i.e. references, not copying) data = sock.recv() LOG.debug(_("CONSUMER RECEIVED DATA: %s"), data) proxy = self.proxies[sock] if data[2] == 'cast': # Legacy protocol packenv = data[3] ctx, msg = _deserialize(packenv) request = rpc_common.deserialize_msg(msg) ctx = RpcContext.unmarshal(ctx) elif data[2] == 'impl_zmq_v2': packenv = data[4:] msg = unflatten_envelope(packenv) request = rpc_common.deserialize_msg(msg) # Unmarshal only after verifying the message. ctx = RpcContext.unmarshal(data[3]) else: LOG.error(_("ZMQ Envelope version unsupported or unknown.")) return self.pool.spawn_n(self.process, proxy, ctx, request)
def __init__(self, addr, zmq_type, bind=True, subscribe=None): self.sock = _get_ctxt().socket(zmq_type) self.addr = addr self.type = zmq_type self.subscriptions = [] # Support failures on sending/receiving on wrong socket type. self.can_recv = zmq_type in (zmq.PULL, zmq.SUB) self.can_send = zmq_type in (zmq.PUSH, zmq.PUB) self.can_sub = zmq_type in (zmq.SUB, ) # Support list, str, & None for subscribe arg (cast to list) do_sub = { list: subscribe, str: [subscribe], type(None): [] }[type(subscribe)] for f in do_sub: self.subscribe(f) str_data = {'addr': addr, 'type': self.socket_s(), 'subscribe': subscribe, 'bind': bind} LOG.debug(_("Connecting to %(addr)s with %(type)s"), str_data) LOG.debug(_("-> Subscribed to %(subscribe)s"), str_data) LOG.debug(_("-> bind: %(bind)s"), str_data) try: if bind: self.sock.bind(addr) else: self.sock.connect(addr) except Exception: raise RPCException(_("Could not open socket."))
def publisher(waiter): LOG.info(_("Creating proxy for topic: %s"), topic) try: # The topic is received over the network, # don't trust this input. if self.badchars.search(topic) is not None: emsg = _("Topic contained dangerous characters.") LOG.warn(emsg) raise RPCException(emsg) out_sock = ZmqSocket("ipc://%s/zmq_topic_%s" % (ipc_dir, topic), sock_type, bind=True) except RPCException: waiter.send_exception(*sys.exc_info()) return self.topic_proxy[topic] = eventlet.queue.LightQueue( CONF.rpc_zmq_topic_backlog) self.sockets.append(out_sock) # It takes some time for a pub socket to open, # before we can have any faith in doing a send() to it. if sock_type == zmq.PUB: eventlet.sleep(.5) waiter.send(True) while(True): data = self.topic_proxy[topic].get() out_sock.send(data, copy=False)
def handle_str_replace(args): if not (isinstance(args, dict) or isinstance(args, list)): raise TypeError(_('Arguments to "str_replace" must be a' 'dictionary or a list')) try: if isinstance(args, dict): text = args.get('template') params = args.get('params', {}) elif isinstance(args, list): params, text = args if text is None: raise KeyError() except KeyError: example = ('''str_replace: template: This is var1 template var2 params: var1: a var2: string''') raise KeyError(_('"str_replace" syntax should be %s') % example) if not hasattr(text, 'replace'): raise TypeError(_('"template" parameter must be a string')) if not isinstance(params, dict): raise TypeError( _('"params" parameter must be a dictionary')) for key in params.iterkeys(): value = params.get(key, '') or "" text = text.replace(key, str(value)) return text
def handle_create(self): """Allocate a floating IP for the current tenant.""" ips = None if self.properties[self.DOMAIN] and clients.neutronclient: from heat.engine.resources.internet_gateway import InternetGateway ext_net = InternetGateway.get_external_network_id(self.neutron()) props = {"floating_network_id": ext_net} ips = self.neutron().create_floatingip({"floatingip": props})["floatingip"] self.ipaddress = ips["floating_ip_address"] self.resource_id_set(ips["id"]) LOG.info(_("ElasticIp create %s") % str(ips)) else: if self.properties[self.DOMAIN]: raise exception.Error( _("Domain property can not be set on " "resource %s without Neutron available") % self.name ) try: ips = self.nova().floating_ips.create() except clients.novaclient.exceptions.NotFound: with excutils.save_and_reraise_exception(): msg = _("No default floating IP pool configured. " "Set 'default_floating_pool' in nova.conf.") LOG.error(msg) if ips: self.ipaddress = ips.ip self.resource_id_set(ips.id) LOG.info(_("ElasticIp create %s") % str(ips)) instance_id = self.properties[self.INSTANCE_ID] if instance_id: server = self.nova().servers.get(instance_id) server.add_floating_ip(self._ipaddress())
def _create_resource(self, new_res): res_name = new_res.name # Clean up previous resource if res_name in self.previous_stack: prev_res = self.previous_stack[res_name] if prev_res.state not in ((prev_res.INIT, prev_res.COMPLETE), (prev_res.DELETE, prev_res.COMPLETE)): # Swap in the backup resource if it is in a valid state, # instead of creating a new resource if prev_res.status == prev_res.COMPLETE: logger.debug(_("Swapping in backup Resource %s") % res_name) self._exchange_stacks(self.existing_stack[res_name], prev_res) return logger.debug(_("Deleting backup Resource %s") % res_name) yield prev_res.destroy() # Back up existing resource if res_name in self.existing_stack: logger.debug(_("Backing up existing Resource %s") % res_name) existing_res = self.existing_stack[res_name] self.previous_stack[res_name] = existing_res existing_res.state_set(existing_res.UPDATE, existing_res.COMPLETE) self.existing_stack[res_name] = new_res yield new_res.create()
def validate(self): ''' Validate any of the provided params ''' res = super(OSDBInstance, self).validate() if res: return res # check validity of user and databases users = self.properties.get(self.USERS) if not users: return databases = self.properties.get(self.DATABASES) if not databases: msg = _('Databases property is required if users property' ' is provided') raise exception.StackValidationFailed(message=msg) db_names = set([db[self.DATABASE_NAME] for db in databases]) for user in users: if not user.get(self.USER_DATABASES, []): msg = _('Must provide access to at least one database for ' 'user %s') % user[self.USER_NAME] raise exception.StackValidationFailed(message=msg) missing_db = [db_name for db_name in user[self.USER_DATABASES] if db_name not in db_names] if missing_db: msg = _('Database %s specified for user does not exist in ' 'databases.') % missing_db raise exception.StackValidationFailed(message=msg)
def run(self): prefix = self.arguments and self.arguments.pop() or None content = [] for resource_type, resource_class in _all_resources(prefix): self.resource_type = resource_type self.resource_class = resource_class section = self._section(content, resource_type, '%s') self.props_schemata = properties.schemata( self.resource_class.properties_schema) self.attrs_schemata = attributes.schemata( self.resource_class.attributes_schema) if resource_class.support_status.status == support.DEPRECATED: sstatus = resource_class.support_status.to_dict() msg = _('%(status)s') if sstatus['message'] is not None: msg = _('%(status)s - %(message)s') para = nodes.inline('', msg % sstatus) warning = nodes.note('', para) section.append(warning) cls_doc = pydoc.getdoc(resource_class) if cls_doc: para = nodes.paragraph('', cls_doc) section.append(para) self.contribute_properties(section) self.contribute_attributes(section) self.contribute_hot_syntax(section) self.contribute_yaml_syntax(section) self.contribute_json_syntax(section) return content
def validate(self): ''' Validate any of the provided params ''' super(Server, self).validate() # check validity of key key_name = self.properties.get('key_name', None) if key_name: nova_utils.get_keypair(self.nova(), key_name) # either volume_id or snapshot_id needs to be specified, but not both # for block device mapping. bdm = self.properties.get('block_device_mapping') or [] bootable_vol = False for mapping in bdm: if mapping['device_name'] == 'vda': bootable_vol = True if mapping.get('volume_id') and mapping.get('snapshot_id'): raise exception.ResourcePropertyConflict('volume_id', 'snapshot_id') if not mapping.get('volume_id') and not mapping.get('snapshot_id'): msg = _('Either volume_id or snapshot_id must be specified for' ' device mapping %s') % mapping['device_name'] raise exception.StackValidationFailed(message=msg) # make sure the image exists if specified. image = self.properties.get('image', None) if image: nova_utils.get_image_id(self.nova(), image) elif not image and not bootable_vol: msg = _('Neither image nor bootable volume is specified for' ' instance %s') % self.name raise exception.StackValidationFailed(message=msg)
def _inner(): if initial_delay: greenthread.sleep(initial_delay) try: while self._running: start = timeutils.utcnow() self.f(*self.args, **self.kw) end = timeutils.utcnow() if not self._running: break delay = interval - timeutils.delta_seconds(start, end) if delay <= 0: LOG.warn(_('task run outlasted interval by %s sec') % -delay) greenthread.sleep(delay if delay > 0 else 0) except LoopingCallDone as e: self.stop() done.send(e.retvalue) except Exception: LOG.exception(_('in fixed duration looping call')) done.send_exception(*sys.exc_info()) return else: done.send(True)
def contribute_attributes(self, parent): if not self.attrs_schemata: return section = self._section(parent, _('Attributes'), '%s-attrs') prop_list = nodes.definition_list() section.append(prop_list) for prop_key, prop in sorted(self.attrs_schemata.items()): description = prop.description prop_item = nodes.definition_list_item( '', nodes.term('', prop_key)) prop_list.append(prop_item) definition = nodes.definition() prop_item.append(definition) if prop.support_status.status != support.SUPPORTED: sstatus = prop.support_status.to_dict() msg = _('%(status)s') if sstatus['message'] is not None: msg = _('%(status)s - %(message)s') para = nodes.inline('', msg % sstatus) warning = nodes.note('', para) definition.append(warning) if description: def_para = nodes.paragraph('', description) definition.append(def_para)
def state_set(self, action, status, reason): '''Update the stack state in the database.''' if action not in self.ACTIONS: raise ValueError(_("Invalid action %s") % action) if status not in self.STATUSES: raise ValueError(_("Invalid status %s") % status) self.action = action self.status = status self.status_reason = reason if self.id is None: return stack = db_api.stack_get(self.context, self.id) if stack is not None: stack.update_and_save({'action': action, 'status': status, 'status_reason': reason}) msg = _('Stack %(action)s %(status)s (%(name)s): %(reason)s') LOG.info(msg % {'action': action, 'status': status, 'name': self.name, 'reason': reason}) notification.send(self)
def restart_resource(self, resource_name): ''' stop resource_name and all that depend on it start resource_name and all that depend on it ''' deps = self.dependencies[self[resource_name]] failed = False for res in reversed(deps): try: scheduler.TaskRunner(res.destroy)() except exception.ResourceFailure as ex: failed = True LOG.error(_('delete: %s') % ex) for res in deps: if not failed: try: res.state_reset() scheduler.TaskRunner(res.create)() except exception.ResourceFailure as ex: LOG.exception(_('create')) failed = True else: res.state_set(res.CREATE, res.FAILED, 'Resource restart aborted')
def wait(self): """Loop waiting on children to die and respawning as necessary.""" LOG.debug(_('Full set of CONF:')) CONF.log_opt_values(LOG, std_logging.DEBUG) while self.running: wrap = self._wait_child() if not wrap: # Yield to other threads if no children have exited # Sleep for a short time to avoid excessive CPU usage # (see bug #1095346) eventlet.greenthread.sleep(.01) continue while self.running and len(wrap.children) < wrap.workers: self._start_child(wrap) if self.sigcaught: signame = {signal.SIGTERM: 'SIGTERM', signal.SIGINT: 'SIGINT'}[self.sigcaught] LOG.info(_('Caught %s, stopping children'), signame) for pid in self.children: try: os.kill(pid, signal.SIGTERM) except OSError as exc: if exc.errno != errno.ESRCH: raise # Wait for children to die if self.children: LOG.info(_('Waiting on %d children to exit'), len(self.children)) while self.children: self._wait_child()
def _connect(self, params): """Connect to rabbit. Re-establish any queues that may have been declared before if we are reconnecting. Exceptions should be handled by the caller. """ if self.connection: LOG.info(_("Reconnecting to AMQP server on " "%(hostname)s:%(port)d") % params) try: self.connection.release() except self.connection_errors: pass # Setting this in case the next statement fails, though # it shouldn't be doing any network operations, yet. self.connection = None self.connection = kombu.connection.BrokerConnection(**params) self.connection_errors = self.connection.connection_errors if self.memory_transport: # Kludge to speed up tests. self.connection.transport.polling_interval = 0.0 self.consumer_num = itertools.count(1) self.connection.connect() self.channel = self.connection.channel() # work around 'memory' transport bug in 1.1.3 if self.memory_transport: self.channel._new_queue('ae.undeliver') for consumer in self.consumers: consumer.reconnect(self.channel) LOG.info(_('Connected to AMQP server on %(hostname)s:%(port)d') % params)
def _process_data(self, ctxt, version, method, args): """Process a message in a new thread. If the proxy object we have has a dispatch method (see rpc.dispatcher.RpcDispatcher), pass it the version, method, and args and let it dispatch as appropriate. If not, use the old behavior of magically calling the specified method on the proxy we have here. """ ctxt.update_store() try: rval = self.proxy.dispatch(ctxt, version, method, **args) # Check if the result was a generator if inspect.isgenerator(rval): for x in rval: ctxt.reply(x, None, connection_pool=self.connection_pool) else: ctxt.reply(rval, None, connection_pool=self.connection_pool) # This final None tells multicall that it is done. ctxt.reply(ending=True, connection_pool=self.connection_pool) except rpc_common.ClientException as e: LOG.debug(_("Expected exception during message handling (%s)") % e._exc_info[1]) ctxt.reply(None, e._exc_info, connection_pool=self.connection_pool, log_failure=False) except Exception: LOG.exception(_("Exception during message handling")) ctxt.reply(None, sys.exc_info(), connection_pool=self.connection_pool)
def _register_class(resource_type, resource_class): logger.info(_('Registering resource type %s') % resource_type) if resource_type in _resource_classes: logger.warning(_('Replacing existing resource type %s') % resource_type) _resource_classes[resource_type] = resource_class
def create_stack_user(self, username, password=''): """Create a user defined as part of a stack. The user is defined either via template or created internally by a resource. This user will be added to the heat_stack_user_role as defined in the config. Returns the keystone ID of the resulting user. """ # FIXME(shardy): There's duplicated logic between here and # create_stack_domain user, but this function is expected to # be removed after the transition of all resources to domain # users has been completed stack_user_role = self.client.roles.list( name=cfg.CONF.heat_stack_user_role) if len(stack_user_role) == 1: role_id = stack_user_role[0].id # Create the user user = self.client.users.create( name=self._get_username(username), password=password, default_project=self.context.tenant_id) # Add user to heat_stack_user_role LOG.debug("Adding user %(user)s to role %(role)s" % { 'user': user.id, 'role': role_id}) self.client.roles.grant(role=role_id, user=user.id, project=self.context.tenant_id) else: LOG.error(_("Failed to add user %(user)s to role %(role)s, " "check role exists!") % { 'user': username, 'role': cfg.CONF.heat_stack_user_role}) raise exception.Error(_("Can't find role %s") % cfg.CONF.heat_stack_user_role) return user.id
def __call__(self, message_data): """Consumer callback to call a method on a proxy object. Parses the message for validity and fires off a thread to call the proxy object method. Message data should be a dictionary with two keys: method: string representing the method to call args: dictionary of arg: value Example: {'method': 'echo', 'args': {'value': 42}} """ # It is important to clear the context here, because at this point # the previous context is stored in local.store.context if hasattr(local.store, "context"): del local.store.context rpc_common._safe_log(LOG.debug, _("received %s"), message_data) ctxt = unpack_context(self.conf, message_data) method = message_data.get("method") args = message_data.get("args", {}) version = message_data.get("version", None) if not method: LOG.warn(_("no method for message: %s") % message_data) ctxt.reply(_("No method for message: %s") % message_data, connection_pool=self.connection_pool) return self.pool.spawn_n(self._process_data, ctxt, version, method, args)
def _wait_child(self): try: # Don't block if no child processes have exited pid, status = os.waitpid(0, os.WNOHANG) if not pid: return None except OSError as exc: if exc.errno not in (errno.EINTR, errno.ECHILD): raise return None if os.WIFSIGNALED(status): sig = os.WTERMSIG(status) LOG.info(_('Child %(pid)d killed by signal %(sig)d'), dict(pid=pid, sig=sig)) else: code = os.WEXITSTATUS(status) LOG.info(_('Child %(pid)s exited with status %(code)d'), dict(pid=pid, code=code)) if pid not in self.children: LOG.warning(_('pid %d not in child list'), pid) return None wrap = self.children.pop(pid) wrap.children.remove(pid) return wrap
def reconnect(self): """Handles reconnecting and re-establishing sessions and queues""" if self.connection.opened(): try: self.connection.close() except qpid_exceptions.ConnectionError: pass attempt = 0 delay = 1 while True: broker = self.brokers[attempt % len(self.brokers)] attempt += 1 try: self.connection_create(broker) self.connection.open() except qpid_exceptions.ConnectionError, e: msg_dict = dict(e=e, delay=delay) msg = _("Unable to connect to AMQP server: %(e)s. " "Sleeping %(delay)s seconds") % msg_dict LOG.error(msg) time.sleep(delay) delay = min(2 * delay, 60) else: LOG.info(_('Connected to AMQP server on %s'), broker) break
def set_alarm_state(self, req): """ Implements SetAlarmState API action """ self._enforce(req, "SetAlarmState") # Map from AWS state names to those used in the engine state_map = { "OK": engine_api.WATCH_STATE_OK, "ALARM": engine_api.WATCH_STATE_ALARM, "INSUFFICIENT_DATA": engine_api.WATCH_STATE_NODATA, } con = req.context parms = dict(req.params) # Get mandatory parameters name = api_utils.get_param_value(parms, "AlarmName") state = api_utils.get_param_value(parms, "StateValue") if state not in state_map: msg = _("Invalid state %(state)s, " "expecting one of %(expect)s") % { "state": state, "expect": state_map.keys(), } logger.error(msg) return exception.HeatInvalidParameterValueError(msg) logger.debug(_("setting %(name)s to %(state)s") % {"name": name, "state": state_map[state]}) try: self.rpc_client.set_watch_state(con, watch_name=name, state=state_map[state]) except rpc_common.RemoteError as ex: return exception.map_remote_error(ex) return api_utils.format_response("SetAlarmState", "")
class RequestUriTooLong(HeatException): msg_fmt = _("The URI was too long.")
class InvalidTemplateVersion(HeatException): msg_fmt = _("The template version is invalid: %(explanation)s")
class UnknownUserParameter(HeatException): msg_fmt = _("The Parameter (%(key)s) was not defined in template.")
class UserParameterMissing(HeatException): msg_fmt = _("The Parameter (%(key)s) was not provided.")
class RegionAmbiguity(HeatException): msg_fmt = _("Multiple 'image' service matches for region %(region)s. This " "generally means that a region is required and you have not " "supplied one.")
class InvalidRedirect(HeatException): msg_fmt = _("Received invalid HTTP redirect.")
class MaxRedirectsExceeded(HeatException): msg_fmt = _("Maximum redirects (%(redirects)s) was exceeded.")
class Invalid(HeatException): msg_fmt = _("Data supplied was not valid: %(reason)s")
class Forbidden(HeatException): msg_fmt = _("You are not authorized to complete this action.")
class AuthorizationRedirect(HeatException): msg_fmt = _("Redirecting to %(uri)s for authorization.")
class AuthorizationFailure(HeatException): msg_fmt = _("Authorization failed.")
class NotAuthorized(Forbidden): msg_fmt = _("You are not authorized to complete this action.")
class AuthBadRequest(HeatException): msg_fmt = _("Connect error/bad request to Auth service at URL %(url)s.")
class NotAuthenticated(HeatException): msg_fmt = _("You are not authenticated.")
class MissingCredentialError(HeatException): msg_fmt = _("Missing required credential: %(required)s")
class AuthUrlNotFound(HeatException): msg_fmt = _("Auth service at URL %(url)s not found.")
class MatchMakerException(Exception): """Signified a match could not be found.""" message = _("Match not found by MatchMaker.")
class BadAuthStrategy(HeatException): msg_fmt = _("Incorrect auth strategy, expected \"%(expected)s\" but " "received \"%(received)s\"")
class KeyPair(resource.Resource): """ A resource for creating Nova key pairs. **Note** that if a new key is generated setting `save_private_key` to `True` results in the system saving the private key which can then be retrieved via the `private_key` attribute of this resource. Setting the `public_key` property means that the `private_key` attribute of this resource will always return an empty string regardless of the `save_private_key` setting since there will be no private key data to save. """ PROPERTIES = ( NAME, SAVE_PRIVATE_KEY, PUBLIC_KEY, ) = ( 'name', 'save_private_key', 'public_key', ) properties_schema = { NAME: properties.Schema(properties.Schema.STRING, _('The name of the key pair.'), required=True), SAVE_PRIVATE_KEY: properties.Schema( properties.Schema.BOOLEAN, _('True if the system should remember a generated private key; ' 'False otherwise.'), default=False), PUBLIC_KEY: properties.Schema( properties.Schema.STRING, _('The optional public key. This allows users to supply the ' 'public key from a pre-existing key pair. If not supplied, a ' 'new key pair will be generated.')), } attributes_schema = { 'public_key': _('The public key.'), 'private_key': _('The private key if it has been saved.') } def __init__(self, name, json_snippet, stack): super(KeyPair, self).__init__(name, json_snippet, stack) self._private_key = None self._public_key = None @property def private_key(self): """Return the private SSH key for the resource.""" if (self._private_key is None and self.id and self.properties[self.SAVE_PRIVATE_KEY]): try: self._private_key = db_api.resource_data_get( self, 'private_key') except exception.NotFound: pass return self._private_key or "" @property def public_key(self): """Return the public SSH key for the resource.""" if not self._public_key: if self.properties[self.PUBLIC_KEY]: self._public_key = self.properties[self.PUBLIC_KEY] elif self.resource_id: nova_key = nova_utils.get_keypair(self.nova(), self.resource_id) self._public_key = nova_key.public_key return self._public_key def handle_create(self): pub_key = self.properties[self.PUBLIC_KEY] or None new_keypair = self.nova().keypairs.create(self.properties[self.NAME], public_key=pub_key) if (self.properties[self.SAVE_PRIVATE_KEY] and hasattr(new_keypair, 'private_key')): db_api.resource_data_set(self, 'private_key', new_keypair.private_key, True) self.resource_id_set(new_keypair.id) def handle_delete(self): if self.resource_id: try: self.nova().keypairs.delete(self.resource_id) except nova_exceptions.NotFound: pass def _resolve_attribute(self, key): attr_fn = { 'private_key': self.private_key, 'public_key': self.public_key } return unicode(attr_fn[key]) def FnGetRefId(self): return self.resource_id
def __init__(self): # Array of tuples. Index [2] toggles negation, [3] is last-if-true self.bindings = [] self.no_heartbeat_msg = _('Matchmaker does not implement ' 'registration or heartbeat.')
def _stack_suspend(stack): logger.debug(_("suspending stack %s") % stack.name) stack.suspend()
def list_metrics(self, req): """ Implements ListMetrics API action Lists metric datapoints associated with a particular alarm, or all alarms if none specified """ self._enforce(req, 'ListMetrics') def format_metric_data(d, fil={}): """ Reformat engine output into the AWS "Metric" format Takes an optional filter dict, which is traversed so a metric dict is only returned if all keys match the filter dict """ dimensions = [{ 'AlarmName': d[engine_api.WATCH_DATA_ALARM] }, { 'Timestamp': d[engine_api.WATCH_DATA_TIME] }] for key in d[engine_api.WATCH_DATA]: dimensions.append({key: d[engine_api.WATCH_DATA][key]}) newdims = self._reformat_dimensions(dimensions) result = { 'MetricName': d[engine_api.WATCH_DATA_METRIC], 'Dimensions': newdims, 'Namespace': d[engine_api.WATCH_DATA_NAMESPACE], } for f in fil: try: value = result[f] if value != fil[f]: # Filter criteria not met, return None return except KeyError: logger.warning(_("Invalid filter key %s, ignoring") % f) return result con = req.context parms = dict(req.params) # FIXME : Don't yet handle filtering by Dimensions filter_result = dict((k, v) for (k, v) in parms.iteritems() if k in ("MetricName", "Namespace")) logger.debug(_("filter parameters : %s") % filter_result) try: # Engine does not currently support query by namespace/metric # so we pass None/None and do any filtering locally null_kwargs = {'metric_namespace': None, 'metric_name': None} watch_data = self.rpc_client.show_watch_metric(con, **null_kwargs) except rpc_common.RemoteError as ex: return exception.map_remote_error(ex) res = {'Metrics': []} for d in watch_data: metric = format_metric_data(d, filter_result) if metric: res['Metrics'].append(metric) result = api_utils.format_response("ListMetrics", res) return result
class DBInvalidUnicodeParameter(Exception): message = _("Invalid Parameter: " "Unicode is not supported by the current database.")
def _stack_resume(stack): logger.debug(_("resuming stack %s") % stack.name) stack.resume()
def contribute_property(self, prop_list, prop_key, prop): prop_item = nodes.definition_list_item('', nodes.term('', prop_key)) prop_list.append(prop_item) prop_item.append(nodes.classifier('', prop.type)) definition = nodes.definition() prop_item.append(definition) if prop.support_status.status != support.SUPPORTED: para = nodes.inline( '', _('%(status)s - %(message)s') % prop.support_status.to_dict()) warning = nodes.note('', para) definition.append(warning) if not prop.implemented: para = nodes.inline('', _('Not implemented.')) warning = nodes.note('', para) definition.append(warning) return if prop.description: para = nodes.paragraph('', prop.description) definition.append(para) if prop.update_allowed: para = nodes.paragraph('', _('Can be updated without replacement.')) definition.append(para) else: para = nodes.paragraph('', _('Updates cause replacement.')) definition.append(para) if prop.required: para = nodes.paragraph('', _('Required property.')) elif prop.default is not None: para = nodes.paragraph( '', _('Optional property, defaults to "%s".') % prop.default) else: para = nodes.paragraph('', _('Optional property.')) definition.append(para) for constraint in prop.constraints: para = nodes.paragraph('', str(constraint)) definition.append(para) sub_schema = None if prop.schema and prop.type == properties.Schema.MAP: para = nodes.emphasis('', _('Map properties:')) definition.append(para) sub_schema = prop.schema elif prop.schema and prop.type == properties.Schema.LIST: para = nodes.emphasis('', _('List contents:')) definition.append(para) sub_schema = prop.schema if sub_schema: sub_prop_list = nodes.definition_list() definition.append(sub_prop_list) for sub_prop_key, sub_prop in sorted(sub_schema.items(), self.cmp_prop): self.contribute_property(sub_prop_list, sub_prop_key, sub_prop)
def delete_stack(self, cnxt, stack_identity): """ The delete_stack method deletes a given stack. :param cnxt: RPC context. :param stack_identity: Name of the stack you want to delete. """ def remote_stop(lock_engine_id): rpc = proxy.RpcProxy(lock_engine_id, "1.0") msg = rpc.make_msg("stop_stack", stack_identity=stack_identity) timeout = cfg.CONF.engine_life_check_timeout try: rpc.call(cnxt, msg, topic=lock_engine_id, timeout=timeout) except rpc_common.Timeout: return False st = self._get_stack(cnxt, stack_identity) logger.info(_('Deleting stack %s') % st.name) stack = parser.Stack.load(cnxt, stack=st) lock = stack_lock.StackLock(cnxt, stack, self.engine_id) acquire_result = lock.try_acquire() # Successfully acquired lock if acquire_result is None: self.thread_group_mgr.start_with_acquired_lock( stack, lock, stack.delete) return # Current engine has the lock elif acquire_result == self.engine_id: self.thread_group_mgr.stop(stack.id) # Another active engine has the lock elif stack_lock.StackLock.engine_alive(cnxt, acquire_result): stop_result = remote_stop(acquire_result) if stop_result is None: logger.debug( _("Successfully stopped remote task on engine %s") % acquire_result) else: raise exception.StopActionFailed(stack_name=stack.name, engine_id=acquire_result) # If the lock isn't released here, then the call to # start_with_lock below will raise an ActionInProgress # exception. Ideally, we wouldn't be calling another # release() here, since it should be called as soon as the # ThreadGroup is stopped. But apparently there's a race # between release() the next call to lock.acquire(). db_api.stack_lock_release(stack.id, acquire_result) # There may be additional resources that we don't know about # if an update was in-progress when the stack was stopped, so # reload the stack from the database. st = self._get_stack(cnxt, stack_identity) stack = parser.Stack.load(cnxt, stack=st) self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id, stack.delete) return None
def handle_create(self): # create the resource managers _drive_manager = self._get_drive_manager() logger.debug(_("list drives %s") % _drive_manager.list()) _compute_manager = self._get_compute_manager() logger.debug(_("list servers %s") % _compute_manager.list()) # handle meta _meta = json.loads(self.properties.get(self.META)) if self.properties.get(self.DESCRIPTION): _meta['description'] = self.properties.get(self.DESCRIPTION) # FIXME it overwrites the field that may be in meta if self.properties.get(self.SSH_PUBLIC_KEY): _meta['ssh_public_key'] = self.properties.get(self.SSH_PUBLIC_KEY) # FIXME base64 encode - overwrites base64_fields, cloudinit_user_data if self.properties.get(self.CLOUDINIT_USER_DATA): _meta[ 'base64_fields'] = 'cloudinit-user-data' #XXX hyphens not underscores!!! _meta['cloudinit-user-data'] = base64.b64encode( self.properties.get(self.CLOUDINIT_USER_DATA)) # create the server description _compute_description = { 'name': self.properties.get(self.INSTANCE_NAME), 'cpu': self.properties.get(self.CPU_MHZ), 'mem': self.properties.get(self.MEM_SIZE) * 1024**2, 'vnc_password': self.properties.get(self.VNC_PASSWORD), 'drives': [], 'nics': [], # we need to parse the JSON format supplied as a parameter to add it as an object - not as a string 'meta': _meta } #--------------------------HANDLE DRIVES -------------------------------------- # FIXME drives can be left behind after an unsuccessful create # decide what to do with the drives if self.properties.get(self.DRIVE_UUID): # so we have a drive # check the drive with GET _drive = _drive_manager.get(self.properties.get(self.DRIVE_UUID)) # add the drive to the server description # TODO do append instead of setting the drives list - this requires computing the dev_channel attachment point _compute_description['drives'].append({ 'boot_order': 1, 'dev_channel': "0:0", 'device': "virtio", 'drive': { 'uuid': _drive['uuid'] } }) elif self.properties.get(self.DRIVE_CLONE_UUID): # we need to clone a drive - its an asynchronous operation # check the drive - will raise an exception if uuid is wrong # cloudsigma.errors.ClientError: (404, u'[{"error_point": null, # "error_type": "notexist", "error_message": "Object with uuid b49dc74a-f7a5-42f5-9842-290e7475d67a does not exist"}]') _drive = _drive_manager.get( self.properties.get(self.DRIVE_CLONE_UUID)) # clone the drive # will raise exception if is mounted, etc. _clone = _drive_manager.clone(_drive['uuid']) # check the clone while True: # wait loop for the clone creation to finish if _drive_manager.get( _clone['uuid'] )['status'] == 'unmounted': # FIXME is the test right break else: time.sleep(5) # FIXME do we need this, do we need a hard timeout if self.properties.get(self.DRIVE_CLONE_RESIZE): # we need to resize the drive _clone['size'] = self.properties.get( self.DRIVE_CLONE_RESIZE ) # TODO sanity check the clone new size _drive_manager.resize(_clone['uuid'], _clone) # check the clone while True: # wait loop for the clone creation to finish if _drive_manager.get( _clone['uuid'] )['status'] == 'unmounted': # FIXME is the test right break else: time.sleep(5) # FIXME do we need this, do we need a hard timeout # attach the drive by changing the configuration # TODO do append instead of setting the drives list - this requires computing the dev_channel _compute_description['drives'].append({ 'boot_order': 1, 'dev_channel': "0:0", 'device': "virtio", 'drive': { 'uuid': _clone['uuid'] } }) #--------------------------HANDLE DRIVES -------------------------------------- #--------------------------HANDLE NETWORK -------------------------------------- for _ip in self.properties.get(self.NET_IP_UUIDS): # handle the public IPs _ip_manager = self._get_ip_manager() logger.debug(_("list IPs %s") % _ip_manager.list()) # handle the different types of configuration if _ip == 'dhcp': _ip_attachment = {'ip_v4_conf': {'conf': 'dhcp'}} elif _ip == 'manual': _ip_attachment = {'ip_v4_conf': {'conf': 'manual'}} else: # check the uuid with GET _ip_manager.get(_ip) _ip_attachment = {'ip_v4_conf': {'conf': 'static', 'ip': _ip}} # attach the ip _compute_description['nics'].append(_ip_attachment) for _vlan in self.properties.get(self.NET_VLAN_UUIDS): # handle the private networks _vlan_manager = self._get_vlan_manager() # check VLAN uuid with GET _vlan_manager.get(_vlan) _vlan_attachment = {'vlan': _vlan} _compute_description['nics'].append(_vlan_attachment) #--------------------------HANDLE NETWORK -------------------------------------- # create the server with the attached drives and nics logger.debug( _("Trying to create a VM with this description %s") % _compute_description) _compute = _compute_manager.create(_compute_description) logger.debug(_("VM Created %s") % _compute) # save the uuid for future operations self.resource_id_set(_compute['uuid']) # start the sever _compute_manager.start(_compute['uuid']) # use this object to check for node creation completion return _compute['uuid']
class LoadBalancer(stack_resource.StackResource): PROPERTIES = ( AVAILABILITY_ZONES, HEALTH_CHECK, INSTANCES, LISTENERS, APP_COOKIE_STICKINESS_POLICY, LBCOOKIE_STICKINESS_POLICY, SECURITY_GROUPS, SUBNETS, ) = ( 'AvailabilityZones', 'HealthCheck', 'Instances', 'Listeners', 'AppCookieStickinessPolicy', 'LBCookieStickinessPolicy', 'SecurityGroups', 'Subnets', ) _HEALTH_CHECK_KEYS = ( HEALTH_CHECK_HEALTHY_THRESHOLD, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_TARGET, HEALTH_CHECK_TIMEOUT, HEALTH_CHECK_UNHEALTHY_THRESHOLD, ) = ( 'HealthyThreshold', 'Interval', 'Target', 'Timeout', 'UnhealthyThreshold', ) _LISTENER_KEYS = ( LISTENER_INSTANCE_PORT, LISTENER_LOAD_BALANCER_PORT, LISTENER_PROTOCOL, LISTENER_SSLCERTIFICATE_ID, LISTENER_POLICY_NAMES, ) = ( 'InstancePort', 'LoadBalancerPort', 'Protocol', 'SSLCertificateId', 'PolicyNames', ) properties_schema = { AVAILABILITY_ZONES: properties.Schema( properties.Schema.LIST, _('The Availability Zones in which to create the load balancer.'), required=True), HEALTH_CHECK: properties.Schema( properties.Schema.MAP, _('An application health check for the instances.'), schema={ HEALTH_CHECK_HEALTHY_THRESHOLD: properties.Schema( properties.Schema.NUMBER, _('The number of consecutive health probe successes ' 'required before moving the instance to the ' 'healthy state.'), required=True), HEALTH_CHECK_INTERVAL: properties.Schema( properties.Schema.NUMBER, _('The approximate interval, in seconds, between ' 'health checks of an individual instance.'), required=True), HEALTH_CHECK_TARGET: properties.Schema(properties.Schema.STRING, _('The port being checked.'), required=True), HEALTH_CHECK_TIMEOUT: properties.Schema(properties.Schema.NUMBER, _('Health probe timeout, in seconds.'), required=True), HEALTH_CHECK_UNHEALTHY_THRESHOLD: properties.Schema( properties.Schema.NUMBER, _('The number of consecutive health probe failures ' 'required before moving the instance to the ' 'unhealthy state'), required=True), }), INSTANCES: properties.Schema(properties.Schema.LIST, _('The list of instance IDs load balanced.'), update_allowed=True), LISTENERS: properties.Schema( properties.Schema.LIST, _('One or more listeners for this load balancer.'), schema=properties.Schema( properties.Schema.MAP, schema={ LISTENER_INSTANCE_PORT: properties.Schema( properties.Schema.NUMBER, _('TCP port on which the instance server is ' 'listening.'), required=True), LISTENER_LOAD_BALANCER_PORT: properties.Schema( properties.Schema.NUMBER, _('The external load balancer port number.'), required=True), LISTENER_PROTOCOL: properties.Schema( properties.Schema.STRING, _('The load balancer transport protocol to use.'), required=True, constraints=[ constraints.AllowedValues(['TCP', 'HTTP']), ]), LISTENER_SSLCERTIFICATE_ID: properties.Schema(properties.Schema.STRING, _('Not Implemented.'), implemented=False), LISTENER_POLICY_NAMES: properties.Schema(properties.Schema.LIST, _('Not Implemented.'), implemented=False), }, ), required=True), APP_COOKIE_STICKINESS_POLICY: properties.Schema(properties.Schema.STRING, _('Not Implemented.'), implemented=False), LBCOOKIE_STICKINESS_POLICY: properties.Schema(properties.Schema.STRING, _('Not Implemented.'), implemented=False), SECURITY_GROUPS: properties.Schema(properties.Schema.STRING, _('Not Implemented.'), implemented=False), SUBNETS: properties.Schema(properties.Schema.LIST, _('Not Implemented.'), implemented=False), } attributes_schema = { "CanonicalHostedZoneName": _("The name of the hosted zone that is " "associated with the LoadBalancer."), "CanonicalHostedZoneNameID": _("The ID of the hosted zone name " "that is associated with the " "LoadBalancer."), "DNSName": _("The DNS name for the LoadBalancer."), "SourceSecurityGroup.GroupName": _("The security group that you can " "use as part of your inbound " "rules for your LoadBalancer's " "back-end instances."), "SourceSecurityGroup.OwnerAlias": _("Owner of the source " "security group.") } def _haproxy_config(self, templ, instances): # initial simplifications: # - only one Listener # - only http (no tcp or ssl) # # option httpchk HEAD /check.txt HTTP/1.0 gl = ''' global daemon maxconn 256 stats socket /tmp/.haproxy-stats defaults mode http timeout connect 5000ms timeout client 50000ms timeout server 50000ms ''' listener = self.properties[self.LISTENERS][0] lb_port = listener[self.LISTENER_LOAD_BALANCER_PORT] inst_port = listener[self.LISTENER_INSTANCE_PORT] spaces = ' ' frontend = ''' frontend http bind *:%s ''' % (lb_port) health_chk = self.properties[self.HEALTH_CHECK] if health_chk: check = 'check inter %ss fall %s rise %s' % ( health_chk[self.HEALTH_CHECK_INTERVAL], health_chk[self.HEALTH_CHECK_UNHEALTHY_THRESHOLD], health_chk[self.HEALTH_CHECK_HEALTHY_THRESHOLD]) timeout = int(health_chk[self.HEALTH_CHECK_TIMEOUT]) timeout_check = 'timeout check %ds' % timeout else: check = '' timeout_check = '' backend = ''' default_backend servers backend servers balance roundrobin option http-server-close option forwardfor option httpchk %s ''' % timeout_check servers = [] n = 1 client = self.nova() for i in instances: ip = nova_utils.server_to_ipaddress(client, i) or '0.0.0.0' logger.debug('haproxy server:%s' % ip) servers.append('%sserver server%d %s:%s %s' % (spaces, n, ip, inst_port, check)) n = n + 1 return '%s%s%s%s\n' % (gl, frontend, backend, '\n'.join(servers)) def get_parsed_template(self): if cfg.CONF.loadbalancer_template: with open(cfg.CONF.loadbalancer_template) as templ_fd: logger.info( _('Using custom loadbalancer template %s') % cfg.CONF.loadbalancer_template) contents = templ_fd.read() else: contents = lb_template_default return template_format.parse(contents) def child_params(self): params = {} # If the owning stack defines KeyName, we use that key for the nested # template, otherwise use no key if 'KeyName' in self.stack.parameters: params['KeyName'] = self.stack.parameters['KeyName'] return params def child_template(self): templ = self.get_parsed_template() # If the owning stack defines KeyName, we use that key for the nested # template, otherwise use no key if 'KeyName' not in self.stack.parameters: del templ['Resources']['LB_instance']['Properties']['KeyName'] del templ['Parameters']['KeyName'] return templ def handle_create(self): templ = self.child_template() params = self.child_params() if self.properties[self.INSTANCES]: md = templ['Resources']['LB_instance']['Metadata'] files = md['AWS::CloudFormation::Init']['config']['files'] cfg = self._haproxy_config(templ, self.properties[self.INSTANCES]) files['/etc/haproxy/haproxy.cfg']['content'] = cfg return self.create_with_template(templ, params) def handle_update(self, json_snippet, tmpl_diff, prop_diff): ''' re-generate the Metadata save it to the db. rely on the cfn-hup to reconfigure HAProxy ''' if self.INSTANCES in prop_diff: templ = self.get_parsed_template() cfg = self._haproxy_config(templ, prop_diff[self.INSTANCES]) md = self.nested()['LB_instance'].metadata_get() files = md['AWS::CloudFormation::Init']['config']['files'] files['/etc/haproxy/haproxy.cfg']['content'] = cfg self.nested()['LB_instance'].metadata_set(md) def handle_delete(self): return self.delete_nested() def validate(self): ''' Validate any of the provided params ''' res = super(LoadBalancer, self).validate() if res: return res if cfg.CONF.loadbalancer_template and \ not os.access(cfg.CONF.loadbalancer_template, os.R_OK): msg = _('Custom LoadBalancer template can not be found') raise exception.StackValidationFailed(message=msg) health_chk = self.properties[self.HEALTH_CHECK] if health_chk: interval = float(health_chk[self.HEALTH_CHECK_INTERVAL]) timeout = float(health_chk[self.HEALTH_CHECK_TIMEOUT]) if interval < timeout: return {'Error': 'Interval must be larger than Timeout'} def FnGetRefId(self): return unicode(self.name) def _resolve_attribute(self, name): ''' We don't really support any of these yet. ''' if name == 'DNSName': return self.get_output('PublicIp') elif name in self.attributes_schema: # Not sure if we should return anything for the other attribs # since they aren't really supported in any meaningful way return ''
class CloudSigmaCompute(resource.Resource): PROPERTIES = (API_ENDPOINT, USERNAME, PASSWORD, INSTANCE_NAME, MEM_SIZE, CPU_MHZ, VNC_PASSWORD, META, DESCRIPTION, CLOUDINIT_USER_DATA, SSH_PUBLIC_KEY, DRIVE_CLONE_UUID, DRIVE_CLONE_RESIZE, DRIVE_UUID, NET_IP_UUIDS, NET_VLAN_UUIDS) = ('api_endpoint', 'username', 'password', 'instance_name', 'mem_size', 'cpu_mhz', 'vnc_password', 'meta', 'description', 'cloudinit_user_data', 'ssh_public_key', 'drive_clone_uuid', 'drive_clone_resize', 'drive_uuid', 'net_ip_uuids', 'net_vlan_uuids') properties_schema = { #------------------API-------------------------------- API_ENDPOINT: properties.Schema( properties.Schema.STRING, _('The URL for the RESTful API. Defaults to https://zrh.cloudsigma.com/api/2.0/' ), required=True, default='https://zrh.cloudsigma.com/api/2.0/'), USERNAME: properties.Schema(properties.Schema.STRING, _('The username in the CloudSigma Cloud.'), required=True), PASSWORD: properties.Schema(properties.Schema.STRING, _('The password in the CloudSigma Cloud.'), required=True), #------------------API-------------------------------- INSTANCE_NAME: properties.Schema( properties.Schema.STRING, _('The instance name. The default is "Server <random uuid>".'), required=False, default=''), MEM_SIZE: properties.Schema(properties.Schema.INTEGER, _('Memory size in MB. The default is 256MB'), required=False, default=256), CPU_MHZ: properties.Schema(properties.Schema.INTEGER, _('CPU speed in MHz. The default is 250MHz'), required=False, default=250), VNC_PASSWORD: properties.Schema( properties.Schema.STRING, _('The VNC password for remote screen. The default is Cl0ud_Sigma' ), required=True, default='Cl0ud_Sigma'), #------------------DRIVES-------------------------------- # FIXME works with a single drive for the minimal implementation DRIVE_CLONE_UUID: properties.Schema(properties.Schema.STRING, _('Drive UUID to clone and attach'), required=False), DRIVE_CLONE_RESIZE: properties.Schema( properties.Schema.INTEGER, _('Resize the cloned drive. Size in bytes. The default 4GB'), required=False), DRIVE_UUID: properties.Schema(properties.Schema.STRING, _('Drive UUID to attach'), required=False), #------------------DRIVES-------------------------------- #------------------METADATA-------------------------------- META: properties.Schema( properties.Schema.STRING, _('The metadata to pass to the server. It needs to be a proper JSON' ), required=False, default='{}'), DESCRIPTION: properties.Schema(properties.Schema.STRING, _('The instance\'s description'), required=False), CLOUDINIT_USER_DATA: properties.Schema( properties.Schema.STRING, _('Cloudinit user data. Requires cloudinit > 0.7.5.'), required=False), SSH_PUBLIC_KEY: properties.Schema( properties.Schema.STRING, _('SSH public key for the default user. Requires cloudinit > 0.7.5.' ), required=False), #------------------METADATA-------------------------------- #------------------NETWORKING-------------------------------- # FIXME for a minimal implementation - accept here also dhcp and manual NET_IP_UUIDS: properties.Schema( properties.Schema.LIST, _('The subscribed IP UUID. Can be also "dhcp" or "manual"'), required=False, # XXX http://docs.openstack.org/developer/heat/pluginguide.html says that # Based on the property type, properties without a set value will return the default 'empty' value for LIST is [] # but we get 'NoneType' object is not iterable default=[]), NET_VLAN_UUIDS: properties.Schema( properties.Schema.LIST, _('The subscribed VLAN UUIDs.'), required=False, # XXX http://docs.openstack.org/developer/heat/pluginguide.html says that # Based on the property type, properties without a set value will return the default 'empty' value for LIST is [] # but we get 'NoneType' object is not iterable default=[]) #------------------NETWORKING-------------------------------- } attributes_schema = {'network_ip': _('Container ip address')} def __int__(self, name, json_snippet, stack): super(CloudSigmaCompute, self).__init__(name, json_snippet, stack) # -------------------------------- RESOURCE MANAGERS ----------------------------------------------------- # TODO in a more detailed implementation, create a different resource for each of these managers def _get_compute_manager(self): endpoint = self.properties.get(self.API_ENDPOINT) username = self.properties.get(self.USERNAME) password = self.properties.get(self.PASSWORD) logger.debug( _("_get_compute_manager api_endpoint=%s, username=%s") % (endpoint, username)) return cloudsigma.resource.Server(api_endpoint=endpoint, username=username, password=password) def _get_drive_manager(self): endpoint = self.properties.get(self.API_ENDPOINT) username = self.properties.get(self.USERNAME) password = self.properties.get(self.PASSWORD) logger.debug( _("_get_drive_manager api_endpoint=%s, username=%s") % (endpoint, username)) return cloudsigma.resource.Drive(api_endpoint=endpoint, username=username, password=password) def _get_ip_manager(self): endpoint = self.properties.get(self.API_ENDPOINT) username = self.properties.get(self.USERNAME) password = self.properties.get(self.PASSWORD) logger.debug( _("_get_ip_manager api_endpoint=%s, username=%s") % (endpoint, username)) return cloudsigma.resource.IP(api_endpoint=endpoint, username=username, password=password) def _get_vlan_manager(self): endpoint = self.properties.get(self.API_ENDPOINT) username = self.properties.get(self.USERNAME) password = self.properties.get(self.PASSWORD) logger.debug( _("_get_ip_manager api_endpoint=%s, username=%s") % (endpoint, username)) return cloudsigma.resource.VLAN(api_endpoint=endpoint, username=username, password=password) def _get_compute_data(self, compute_id): return self._get_compute_manager().get(compute_id) # -------------------------------- RESOURCE MANAGERS ----------------------------------------------------- def _resolve_attribute(self, name): if not self.resource_id: return if name == 'network_ip': _instance_data = self._get_compute_data(self.resource_id) res = [] for nic in _instance_data['nics']: if nic['runtime']: try: res.append(nic['runtime']['ip_v4']['uuid']) except TypeError: pass return res def handle_create(self): # create the resource managers _drive_manager = self._get_drive_manager() logger.debug(_("list drives %s") % _drive_manager.list()) _compute_manager = self._get_compute_manager() logger.debug(_("list servers %s") % _compute_manager.list()) # handle meta _meta = json.loads(self.properties.get(self.META)) if self.properties.get(self.DESCRIPTION): _meta['description'] = self.properties.get(self.DESCRIPTION) # FIXME it overwrites the field that may be in meta if self.properties.get(self.SSH_PUBLIC_KEY): _meta['ssh_public_key'] = self.properties.get(self.SSH_PUBLIC_KEY) # FIXME base64 encode - overwrites base64_fields, cloudinit_user_data if self.properties.get(self.CLOUDINIT_USER_DATA): _meta[ 'base64_fields'] = 'cloudinit-user-data' #XXX hyphens not underscores!!! _meta['cloudinit-user-data'] = base64.b64encode( self.properties.get(self.CLOUDINIT_USER_DATA)) # create the server description _compute_description = { 'name': self.properties.get(self.INSTANCE_NAME), 'cpu': self.properties.get(self.CPU_MHZ), 'mem': self.properties.get(self.MEM_SIZE) * 1024**2, 'vnc_password': self.properties.get(self.VNC_PASSWORD), 'drives': [], 'nics': [], # we need to parse the JSON format supplied as a parameter to add it as an object - not as a string 'meta': _meta } #--------------------------HANDLE DRIVES -------------------------------------- # FIXME drives can be left behind after an unsuccessful create # decide what to do with the drives if self.properties.get(self.DRIVE_UUID): # so we have a drive # check the drive with GET _drive = _drive_manager.get(self.properties.get(self.DRIVE_UUID)) # add the drive to the server description # TODO do append instead of setting the drives list - this requires computing the dev_channel attachment point _compute_description['drives'].append({ 'boot_order': 1, 'dev_channel': "0:0", 'device': "virtio", 'drive': { 'uuid': _drive['uuid'] } }) elif self.properties.get(self.DRIVE_CLONE_UUID): # we need to clone a drive - its an asynchronous operation # check the drive - will raise an exception if uuid is wrong # cloudsigma.errors.ClientError: (404, u'[{"error_point": null, # "error_type": "notexist", "error_message": "Object with uuid b49dc74a-f7a5-42f5-9842-290e7475d67a does not exist"}]') _drive = _drive_manager.get( self.properties.get(self.DRIVE_CLONE_UUID)) # clone the drive # will raise exception if is mounted, etc. _clone = _drive_manager.clone(_drive['uuid']) # check the clone while True: # wait loop for the clone creation to finish if _drive_manager.get( _clone['uuid'] )['status'] == 'unmounted': # FIXME is the test right break else: time.sleep(5) # FIXME do we need this, do we need a hard timeout if self.properties.get(self.DRIVE_CLONE_RESIZE): # we need to resize the drive _clone['size'] = self.properties.get( self.DRIVE_CLONE_RESIZE ) # TODO sanity check the clone new size _drive_manager.resize(_clone['uuid'], _clone) # check the clone while True: # wait loop for the clone creation to finish if _drive_manager.get( _clone['uuid'] )['status'] == 'unmounted': # FIXME is the test right break else: time.sleep(5) # FIXME do we need this, do we need a hard timeout # attach the drive by changing the configuration # TODO do append instead of setting the drives list - this requires computing the dev_channel _compute_description['drives'].append({ 'boot_order': 1, 'dev_channel': "0:0", 'device': "virtio", 'drive': { 'uuid': _clone['uuid'] } }) #--------------------------HANDLE DRIVES -------------------------------------- #--------------------------HANDLE NETWORK -------------------------------------- for _ip in self.properties.get(self.NET_IP_UUIDS): # handle the public IPs _ip_manager = self._get_ip_manager() logger.debug(_("list IPs %s") % _ip_manager.list()) # handle the different types of configuration if _ip == 'dhcp': _ip_attachment = {'ip_v4_conf': {'conf': 'dhcp'}} elif _ip == 'manual': _ip_attachment = {'ip_v4_conf': {'conf': 'manual'}} else: # check the uuid with GET _ip_manager.get(_ip) _ip_attachment = {'ip_v4_conf': {'conf': 'static', 'ip': _ip}} # attach the ip _compute_description['nics'].append(_ip_attachment) for _vlan in self.properties.get(self.NET_VLAN_UUIDS): # handle the private networks _vlan_manager = self._get_vlan_manager() # check VLAN uuid with GET _vlan_manager.get(_vlan) _vlan_attachment = {'vlan': _vlan} _compute_description['nics'].append(_vlan_attachment) #--------------------------HANDLE NETWORK -------------------------------------- # create the server with the attached drives and nics logger.debug( _("Trying to create a VM with this description %s") % _compute_description) _compute = _compute_manager.create(_compute_description) logger.debug(_("VM Created %s") % _compute) # save the uuid for future operations self.resource_id_set(_compute['uuid']) # start the sever _compute_manager.start(_compute['uuid']) # use this object to check for node creation completion return _compute['uuid'] # TODO migrate the drive creation here def check_create_complete(self, _compute_id): logger.debug(_("Check create server %s") % self.resource_id) _instance_data = self._get_compute_data(_compute_id) return _instance_data['status'] == 'running' def handle_suspend(self): if not self.resource_id: return self._get_compute_manager().stop(self.resource_id) return self.resource_id def check_suspend_complete(self, _compute_id): _instance_data = self._get_compute_data(_compute_id) return _instance_data['status'] == 'stopped' def handle_resume(self): if not self.resource_id: return self._get_compute_manager().start(self.resource_id) return self.resource_id def check_resume_complete(self, _compute_id): _instance_data = self._get_compute_data(_compute_id) return _instance_data['status'] == 'running' def handle_delete(self): logger.debug(_("Delete server %s") % self.resource_id) # enables to delete a stack if it was not created successfully, e.a. no resource_id if self.resource_id is None: logger.debug( _("Delete: resource_id is empty - nothing to do, exitting.")) return _compute_manager = self._get_compute_manager() # try to get the server description try: _instance_data = self._get_compute_data(self.resource_id) except cloudsigma.errors.ClientError: # throws an 404 nonexistent - nothing to delete return if _instance_data['status'] == 'running': logger.debug( _("Delete server %s; stopping first ...") % self.resource_id) _compute_manager.stop(self.resource_id) time.sleep(5) # XXX wait for the status to update while self._get_compute_data( self.resource_id)['status'] == 'stopping': # XXX May happen that the status is still = 'running' # XXX introduce a hard timeout logger.debug( _("Delete server %s; waiting to be stopped; sleep 5 secs") % self.resource_id) time.sleep(5) # FIXME wait a little so not to flood the server _compute_manager.delete_with_disks(self.resource_id)
def check_create_complete(self, _compute_id): logger.debug(_("Check create server %s") % self.resource_id) _instance_data = self._get_compute_data(_compute_id) return _instance_data['status'] == 'running'