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
Example #2
0
    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)
Example #3
0
    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)
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
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)
Example #8
0
    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()
Example #9
0
    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)
Example #10
0
    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."))
Example #11
0
            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)
Example #12
0
File: hot.py Project: ntt-sic/heat
        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
Example #13
0
    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())
Example #14
0
    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()
Example #15
0
    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)
Example #16
0
    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
Example #17
0
    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)
Example #18
0
        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)
Example #19
0
    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)
Example #20
0
    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)
Example #21
0
    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')
Example #22
0
    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()
Example #23
0
 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)
Example #24
0
    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)
Example #25
0
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
Example #26
0
    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
Example #27
0
    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)
Example #28
0
    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
Example #29
0
    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
Example #30
0
    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", "")
Example #31
0
class RequestUriTooLong(HeatException):
    msg_fmt = _("The URI was too long.")
Example #32
0
class InvalidTemplateVersion(HeatException):
    msg_fmt = _("The template version is invalid: %(explanation)s")
Example #33
0
class UnknownUserParameter(HeatException):
    msg_fmt = _("The Parameter (%(key)s) was not defined in template.")
Example #34
0
class UserParameterMissing(HeatException):
    msg_fmt = _("The Parameter (%(key)s) was not provided.")
Example #35
0
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.")
Example #36
0
class InvalidRedirect(HeatException):
    msg_fmt = _("Received invalid HTTP redirect.")
Example #37
0
class MaxRedirectsExceeded(HeatException):
    msg_fmt = _("Maximum redirects (%(redirects)s) was exceeded.")
Example #38
0
class Invalid(HeatException):
    msg_fmt = _("Data supplied was not valid: %(reason)s")
Example #39
0
class Forbidden(HeatException):
    msg_fmt = _("You are not authorized to complete this action.")
Example #40
0
class AuthorizationRedirect(HeatException):
    msg_fmt = _("Redirecting to %(uri)s for authorization.")
Example #41
0
class AuthorizationFailure(HeatException):
    msg_fmt = _("Authorization failed.")
Example #42
0
class NotAuthorized(Forbidden):
    msg_fmt = _("You are not authorized to complete this action.")
Example #43
0
class AuthBadRequest(HeatException):
    msg_fmt = _("Connect error/bad request to Auth service at URL %(url)s.")
Example #44
0
class NotAuthenticated(HeatException):
    msg_fmt = _("You are not authenticated.")
Example #45
0
class MissingCredentialError(HeatException):
    msg_fmt = _("Missing required credential: %(required)s")
Example #46
0
class AuthUrlNotFound(HeatException):
    msg_fmt = _("Auth service at URL %(url)s not found.")
Example #47
0
class MatchMakerException(Exception):
    """Signified a match could not be found."""
    message = _("Match not found by MatchMaker.")
Example #48
0
class BadAuthStrategy(HeatException):
    msg_fmt = _("Incorrect auth strategy, expected \"%(expected)s\" but "
                "received \"%(received)s\"")
Example #49
0
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
Example #50
0
    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.')
Example #51
0
 def _stack_suspend(stack):
     logger.debug(_("suspending stack %s") % stack.name)
     stack.suspend()
Example #52
0
    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
Example #53
0
class DBInvalidUnicodeParameter(Exception):
    message = _("Invalid Parameter: "
                "Unicode is not supported by the current database.")
Example #54
0
 def _stack_resume(stack):
     logger.debug(_("resuming stack %s") % stack.name)
     stack.resume()
Example #55
0
    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)
Example #56
0
    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']
Example #58
0
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'