示例#1
0
class NoneResource(resource.Resource):
    """Enables easily disabling certain resources via the resource_registry.

    It does nothing, but can effectively stub out any other resource because it
    will accept any properties and return any attribute (as None). Note this
    resource always does nothing on update (e.g it is not replaced even if a
    change to the stubbed resource properties would cause replacement).
    """

    support_status = support.SupportStatus(version='5.0.0')
    properties_schema = {}
    attributes_schema = {}

    def _needs_update(self, after, before, after_props, before_props,
                      prev_resource, check_init_complete=True):
        return False

    def reparse(self, translate=True, client_resolve=True):
        self.properties = properties.Properties(schema={}, data={})
        if translate:
            self.translate_properties(self.properties, client_resolve)

    def handle_create(self):
        self.resource_id_set(six.text_type(uuid.uuid4()))

    def validate(self):
        pass

    def get_attribute(self, key, *path):
        return None
示例#2
0
class SoftwareDeployments(SoftwareDeploymentGroup):

    deprecation_msg = _('Use of this resource is discouraged. Please use '
                        'OS::Heat::SoftwareDeploymentGroup instead.')
    support_status = support.SupportStatus(status=support.DEPRECATED,
                                           message=deprecation_msg,
                                           version='2014.2')
class StructuredConfig(sc.SoftwareConfig):
    """A resource which has same logic with OS::Heat::SoftwareConfig.

    This resource is like OS::Heat::SoftwareConfig except that the config
    property is represented by a Map rather than a String.

    This is useful for configuration tools which use YAML or JSON as their
    configuration syntax. The resulting configuration is transferred,
    stored and returned by the software_configs API as parsed JSON.
    """

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (GROUP, CONFIG, OPTIONS, INPUTS,
                  OUTPUTS) = (sc.SoftwareConfig.GROUP,
                              sc.SoftwareConfig.CONFIG,
                              sc.SoftwareConfig.OPTIONS,
                              sc.SoftwareConfig.INPUTS,
                              sc.SoftwareConfig.OUTPUTS)

    properties_schema = {
        GROUP:
        sc.SoftwareConfig.properties_schema[GROUP],
        OPTIONS:
        sc.SoftwareConfig.properties_schema[OPTIONS],
        INPUTS:
        sc.SoftwareConfig.properties_schema[INPUTS],
        OUTPUTS:
        sc.SoftwareConfig.properties_schema[OUTPUTS],
        CONFIG:
        properties.Schema(
            properties.Schema.MAP,
            _('Map representing the configuration data structure which will '
              'be serialized to JSON format.'))
    }
示例#4
0
 def __init__(self,
              data_type,
              description=None,
              default=None,
              schema=None,
              required=False,
              constraints=None,
              implemented=True,
              update_allowed=False,
              immutable=False,
              support_status=support.SupportStatus(),
              allow_conversion=False):
     super(Schema, self).__init__(data_type,
                                  description,
                                  default,
                                  schema,
                                  required,
                                  constraints,
                                  immutable=immutable)
     self.implemented = implemented
     self.update_allowed = update_allowed
     self.support_status = support_status
     self.allow_conversion = allow_conversion
     # validate structural correctness of schema itself
     self.validate()
示例#5
0
class ElbBaseResource(resource.Resource):
    support_status = support.SupportStatus(version='2014.2')
    default_client_name = 'elb'

    def _prepare_properties(self, props):
        return dict((k, v) for k, v in props.items() if v is not None)

    def _set_job(self, data):
        self.data_set('job', jsonutils.dumps(data))

    def _get_job(self):
        data = self.data().get('job')
        return jsonutils.loads(data) if data else {}

    def _get_job_info(self, job_id):
        job = self.client('job').job.get(job_id)
        status = job.get('status')
        entities = job.get('entities')
        error_code = job.get('error_code')
        LOG.info(
            _('Job %(job)s details: status: %(status)s,  '
              'error_code: %(e_code)s, '
              'entities: %(entities)s') % dict(job=job_id,
                                               status=status,
                                               e_code=error_code,
                                               entities=entities))
        return status, entities, error_code

    def _check_job_success(self, job_id, ignore_not_found=False):
        job_status, entities, error_code = self._get_job_info(job_id)
        if job_status == utils.FAIL:
            # for the user case of delete
            if ignore_not_found:
                # the user case of delete loadbalancer or member if they
                # do not exist
                if error_code == 'NOT_FOUND_ERROR':
                    return True
                # the user case of remove members when listener has been
                # deleted, elb will raise ELB.2050: member listener not
                # belong to any loadbalancer
                error_msg = json.dumps(entities)
                if 'ELB.2050' in error_msg:
                    return True
            raise exception.ResourceUnknownStatus(
                result=(_('Job %(job)s failed: %(error_code)s, '
                          '%(entities)s') % {
                              'job': job_id,
                              'error_code': error_code,
                              'entities': entities
                          }),
                resource_status='unknown')

        return job_status == utils.SUCCESS

    def _check_active(self, elb_status):
        if elb_status == utils.ACTIVE:
            return True
        if elb_status == utils.ERROR:
            raise exception.ResourceInError(resource_status=elb_status)
示例#6
0
 def __init__(self, description=None,
              support_status=support.SupportStatus(),
              cache_mode=CACHE_LOCAL,
              type=None):
     self.description = description
     self.support_status = support_status
     self.cache_mode = cache_mode
     self.type = type
class UpdateWaitConditionHandle(aws_wch.WaitConditionHandle):
    """WaitConditionHandle that clears signals and changes handle on update.

    This works identically to a regular WaitConditionHandle, except that
    on update it clears all signals received and changes the handle. Using
    this handle means that you must setup the signal senders to send their
    signals again any time the update handle changes. This allows us to roll
    out new configurations and be confident that they are rolled out once
    UPDATE COMPLETE is reached.
    """

    support_status = support.SupportStatus(version='2014.1')

    def update(self, after, before=None, prev_resource=None):
        raise exception.UpdateReplace(self.name)
示例#8
0
class KeystoneGroupRoleAssignment(resource.Resource,
                                  KeystoneRoleAssignmentMixin):
    """Resource for granting roles to a group.

    Resource for specifying groups and their's roles.
    """

    support_status = support.SupportStatus(
        version='5.0.0', message=_('Supported versions: keystone v3'))

    default_client_name = 'keystone'

    PROPERTIES = (GROUP, ) = ('group', )

    properties_schema = {
        GROUP:
        properties.Schema(
            properties.Schema.STRING,
            _('Name or id of keystone group.'),
            required=True,
            update_allowed=True,
            constraints=[constraints.CustomConstraint('keystone.group')])
    }

    properties_schema.update(
        KeystoneRoleAssignmentMixin.mixin_properties_schema)

    def __init__(self, *args, **kwargs):
        super(KeystoneGroupRoleAssignment, self).__init__(*args, **kwargs)

    @property
    def group_id(self):
        return (self.client_plugin().get_group_id(
            self.properties.get(self.GROUP)))

    def handle_create(self):
        self.create_assignment(group_id=self.group_id)

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        self.update_assignment(group_id=self.group_id, prop_diff=prop_diff)

    def handle_delete(self):
        self.delete_assignment(group_id=self.group_id)

    def validate(self):
        super(KeystoneGroupRoleAssignment, self).validate()
        self.validate_assignment_properties()
示例#9
0
class HISImage(resource.Resource):
    """A resource managing hyper images in HIS.

    A resource provides managing images that are meant to be used with other
    services.
    """

    support_status = support.SupportStatus(version='2014.2')

    PROPERTIES = (NAME, ORIGINAL_IMAGE_ID) = ('name', 'original_image_id')

    properties_schema = {
        NAME:
        properties.Schema(properties.Schema.STRING,
                          _('Name for the hyper container image.'),
                          required=True),
        ORIGINAL_IMAGE_ID:
        properties.Schema(properties.Schema.STRING,
                          _('The image ID.'),
                          required=True),
    }

    # default_client_name = 'his'

    entity = 'images'

    def handle_create(self):
        args = dict(
            (k, v) for k, v in self.properties.items() if v is not None)
        hyper_image_id = \
            self.his().images.convert(**args)['hyper_image_id']
        self.resource_id_set(hyper_image_id)
        return hyper_image_id

    def check_create_complete(self, image_id):
        image = self.glance().images.get(image_id)
        if image is not None:
            return image.status == 'active'
        return False

    def _show_resource(self):
        image = self.glance().images.get(self.resource_id)
        return dict(image)

    def validate(self):
        super(HISImage, self).validate()
示例#10
0
class KeystoneRole(resource.Resource):
    """Heat Template Resource for Keystone Role.

    Roles dictate the level of authorization the end user can obtain. Roles can
    be granted at either the domain or project level. Role can be assigned to
    the individual user or at the group level. Role names are globally unique.
    """

    support_status = support.SupportStatus(
        version='2015.1', message=_('Supported versions: keystone v3'))

    default_client_name = 'keystone'

    entity = 'roles'

    PROPERTIES = (NAME) = ('name')

    properties_schema = {
        NAME:
        properties.Schema(properties.Schema.STRING,
                          _('Name of keystone role.'),
                          update_allowed=True)
    }

    def client(self):
        return super(KeystoneRole, self).client().client

    def handle_create(self):
        role_name = (self.properties[self.NAME]
                     or self.physical_resource_name())

        role = self.client().roles.create(name=role_name)

        self.resource_id_set(role.id)

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            # Don't update the name if no change
            if self.NAME in prop_diff:
                name = prop_diff[self.NAME] or self.physical_resource_name()
                self.client().roles.update(role=self.resource_id, name=name)
class WaitConditionHandle(wc_base.BaseWaitConditionHandle):
    """AWS WaitConditionHandle resource.

    the main point of this class is to :
    have no dependencies (so the instance can reference it)
    generate a unique url (to be returned in the reference)
    then the cfn-signal will use this url to post to and
    WaitCondition will poll it to see if has been written to.
    """

    support_status = support.SupportStatus(version='2014.1')

    METADATA_KEYS = (DATA, REASON, STATUS, UNIQUE_ID) = ('Data', 'Reason',
                                                         'Status', 'UniqueId')

    def get_reference_id(self):
        if self.resource_id:
            wc = signal_responder.WAITCONDITION
            return six.text_type(self._get_ec2_signed_url(signal_type=wc))
        else:
            return six.text_type(self.name)

    def metadata_update(self, new_metadata=None):
        """DEPRECATED. Should use handle_signal instead."""
        self.handle_signal(details=new_metadata)

    def handle_signal(self, details=None):
        """Validate and update the resource metadata.

        metadata must use the following format:
        {
            "Status" : "Status (must be SUCCESS or FAILURE)",
            "UniqueId" : "Some ID, should be unique for Count>1",
            "Data" : "Arbitrary Data",
            "Reason" : "Reason String"
        }
        """
        if details is None:
            return
        return super(WaitConditionHandle, self).handle_signal(details)
示例#12
0
class QoSRule(neutron.NeutronResource):
    """A resource for Neutron QoS base rule."""

    required_service_extension = 'qos'

    support_status = support.SupportStatus(version='6.0.0')

    PROPERTIES = (
        POLICY, TENANT_ID,
    ) = (
        'policy', 'tenant_id',
    )

    properties_schema = {
        POLICY: properties.Schema(
            properties.Schema.STRING,
            _('ID or name of the QoS policy.'),
            required=True,
            constraints=[constraints.CustomConstraint('neutron.qos_policy')]
        ),
        TENANT_ID: properties.Schema(
            properties.Schema.STRING,
            _('The owner tenant ID of this rule.')
        ),
    }

    def __init__(self, name, json_snippet, stack):
        super(QoSRule, self).__init__(name, json_snippet, stack)
        self._policy_id = None

    @property
    def policy_id(self):
        if not self._policy_id:
            self._policy_id = self.client_plugin().get_qos_policy_id(
                self.properties[self.POLICY])

        return self._policy_id
示例#13
0
class CloudConfig(software_config.SoftwareConfig):
    """A configuration resource for representing cloud-init cloud-config.

    This resource allows cloud-config YAML to be defined and stored by the
    config API. Any intrinsic functions called in the config will be resolved
    before storing the result.

    This resource will generally be referenced by OS::Nova::Server user_data,
    or OS::Heat::MultipartMime parts config. Since cloud-config is boot-only
    configuration, any changes to the definition will result in the
    replacement of all servers which reference it.
    """

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (CLOUD_CONFIG) = ('cloud_config')

    properties_schema = {
        CLOUD_CONFIG:
        properties.Schema(
            properties.Schema.MAP,
            _('Map representing the cloud-config data structure which will '
              'be formatted as YAML.'))
    }

    def handle_create(self):
        cloud_config = template_format.yaml.dump(
            self.properties[self.CLOUD_CONFIG],
            Dumper=template_format.yaml_dumper)
        props = {
            self.NAME: self.physical_resource_name(),
            self.CONFIG: '#cloud-config\n%s' % cloud_config,
            self.GROUP: 'Heat::Ungrouped'
        }
        sc = self.rpc_client().create_software_config(self.context, **props)
        self.resource_id_set(sc[rpc_api.SOFTWARE_CONFIG_ID])
示例#14
0
class Pool(neutron.NeutronResource):
    """A resource for managing load balancer pools in Neutron.

    A load balancing pool is a logical set of devices, such as web servers,
    that you group together to receive and process traffic. The loadbalancing
    function chooses a member of the pool according to the configured load
    balancing method to handle the new requests or connections received on the
    VIP address. There is only one pool for a VIP.
    """

    required_service_extension = 'lbaas'

    PROPERTIES = (
        PROTOCOL, SUBNET_ID, SUBNET, LB_METHOD, NAME, DESCRIPTION,
        ADMIN_STATE_UP, VIP, MONITORS, PROVIDER,
    ) = (
        'protocol', 'subnet_id', 'subnet', 'lb_method', 'name', 'description',
        'admin_state_up', 'vip', 'monitors', 'provider',
    )

    _VIP_KEYS = (
        VIP_NAME, VIP_DESCRIPTION, VIP_SUBNET, VIP_ADDRESS,
        VIP_CONNECTION_LIMIT, VIP_PROTOCOL_PORT,
        VIP_SESSION_PERSISTENCE, VIP_ADMIN_STATE_UP,
    ) = (
        'name', 'description', 'subnet', 'address',
        'connection_limit', 'protocol_port',
        'session_persistence', 'admin_state_up',
    )

    _VIP_SESSION_PERSISTENCE_KEYS = (
        VIP_SESSION_PERSISTENCE_TYPE, VIP_SESSION_PERSISTENCE_COOKIE_NAME,
    ) = (
        'type', 'cookie_name',
    )

    ATTRIBUTES = (
        ADMIN_STATE_UP_ATTR, NAME_ATTR, PROTOCOL_ATTR, SUBNET_ID_ATTR,
        LB_METHOD_ATTR, DESCRIPTION_ATTR, TENANT_ID, VIP_ATTR, PROVIDER_ATTR,
    ) = (
        'admin_state_up', 'name', 'protocol', 'subnet_id',
        'lb_method', 'description', 'tenant_id', 'vip', 'provider',
    )

    properties_schema = {
        PROTOCOL: properties.Schema(
            properties.Schema.STRING,
            _('Protocol for balancing.'),
            required=True,
            constraints=[
                constraints.AllowedValues(['TCP', 'HTTP', 'HTTPS']),
            ]
        ),
        SUBNET_ID: properties.Schema(
            properties.Schema.STRING,
            support_status=support.SupportStatus(
                status=support.HIDDEN,
                version='5.0.0',
                message=_('Use property %s.') % SUBNET,
                previous_status=support.SupportStatus(
                    status=support.DEPRECATED,
                    version='2014.2'
                )
            ),
            constraints=[
                constraints.CustomConstraint('neutron.subnet')
            ]
        ),
        SUBNET: properties.Schema(
            properties.Schema.STRING,
            _('The subnet for the port on which the members '
              'of the pool will be connected.'),
            support_status=support.SupportStatus(version='2014.2'),
            required=True,
            constraints=[
                constraints.CustomConstraint('neutron.subnet')
            ]
        ),
        LB_METHOD: properties.Schema(
            properties.Schema.STRING,
            _('The algorithm used to distribute load between the members of '
              'the pool.'),
            required=True,
            constraints=[
                constraints.AllowedValues(['ROUND_ROBIN',
                                           'LEAST_CONNECTIONS', 'SOURCE_IP']),
            ],
            update_allowed=True
        ),
        NAME: properties.Schema(
            properties.Schema.STRING,
            _('Name of the pool.')
        ),
        DESCRIPTION: properties.Schema(
            properties.Schema.STRING,
            _('Description of the pool.'),
            update_allowed=True
        ),
        ADMIN_STATE_UP: properties.Schema(
            properties.Schema.BOOLEAN,
            _('The administrative state of this pool.'),
            default=True,
            update_allowed=True
        ),
        PROVIDER: properties.Schema(
            properties.Schema.STRING,
            _('LBaaS provider to implement this load balancer instance.'),
            support_status=support.SupportStatus(version='5.0.0'),
            constraints=[
                constraints.CustomConstraint('neutron.lb.provider')
            ],
        ),
        VIP: properties.Schema(
            properties.Schema.MAP,
            _('IP address and port of the pool.'),
            schema={
                VIP_NAME: properties.Schema(
                    properties.Schema.STRING,
                    _('Name of the vip.')
                ),
                VIP_DESCRIPTION: properties.Schema(
                    properties.Schema.STRING,
                    _('Description of the vip.')
                ),
                VIP_SUBNET: properties.Schema(
                    properties.Schema.STRING,
                    _('Subnet of the vip.'),
                    constraints=[
                        constraints.CustomConstraint('neutron.subnet')
                    ]
                ),
                VIP_ADDRESS: properties.Schema(
                    properties.Schema.STRING,
                    _('IP address of the vip.'),
                    constraints=[
                        constraints.CustomConstraint('ip_addr')
                    ]
                ),
                VIP_CONNECTION_LIMIT: properties.Schema(
                    properties.Schema.INTEGER,
                    _('The maximum number of connections per second '
                      'allowed for the vip.')
                ),
                VIP_PROTOCOL_PORT: properties.Schema(
                    properties.Schema.INTEGER,
                    _('TCP port on which to listen for client traffic '
                      'that is associated with the vip address.'),
                    required=True
                ),
                VIP_SESSION_PERSISTENCE: properties.Schema(
                    properties.Schema.MAP,
                    _('Configuration of session persistence.'),
                    schema={
                        VIP_SESSION_PERSISTENCE_TYPE: properties.Schema(
                            properties.Schema.STRING,
                            _('Method of implementation of session '
                              'persistence feature.'),
                            required=True,
                            constraints=[constraints.AllowedValues(
                                ['SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE']
                            )]
                        ),
                        VIP_SESSION_PERSISTENCE_COOKIE_NAME: properties.Schema(
                            properties.Schema.STRING,
                            _('Name of the cookie, '
                              'required if type is APP_COOKIE.')
                        )
                    }
                ),
                VIP_ADMIN_STATE_UP: properties.Schema(
                    properties.Schema.BOOLEAN,
                    _('The administrative state of this vip.'),
                    default=True
                ),
            },
            required=True
        ),
        MONITORS: properties.Schema(
            properties.Schema.LIST,
            _('List of health monitors associated with the pool.'),
            default=[],
            update_allowed=True
        ),
    }

    attributes_schema = {
        ADMIN_STATE_UP_ATTR: attributes.Schema(
            _('The administrative state of this pool.'),
            type=attributes.Schema.STRING
        ),
        NAME_ATTR: attributes.Schema(
            _('Name of the pool.'),
            type=attributes.Schema.STRING
        ),
        PROTOCOL_ATTR: attributes.Schema(
            _('Protocol to balance.'),
            type=attributes.Schema.STRING
        ),
        SUBNET_ID_ATTR: attributes.Schema(
            _('The subnet for the port on which the members of the pool '
              'will be connected.'),
            type=attributes.Schema.STRING
        ),
        LB_METHOD_ATTR: attributes.Schema(
            _('The algorithm used to distribute load between the members '
              'of the pool.'),
            type=attributes.Schema.STRING
        ),
        DESCRIPTION_ATTR: attributes.Schema(
            _('Description of the pool.'),
            type=attributes.Schema.STRING
        ),
        TENANT_ID: attributes.Schema(
            _('Tenant owning the pool.'),
            type=attributes.Schema.STRING
        ),
        VIP_ATTR: attributes.Schema(
            _('Vip associated with the pool.'),
            type=attributes.Schema.MAP
        ),
        PROVIDER_ATTR: attributes.Schema(
            _('Provider implementing this load balancer instance.'),
            support_status=support.SupportStatus(version='5.0.0'),
            type=attributes.Schema.STRING,
        ),
    }

    def translation_rules(self, props):
        return [
            translation.TranslationRule(
                props,
                translation.TranslationRule.REPLACE,
                [self.SUBNET],
                value_path=[self.SUBNET_ID]
            ),
            translation.TranslationRule(
                props,
                translation.TranslationRule.RESOLVE,
                [self.SUBNET],
                client_plugin=self.client_plugin(),
                finder='find_resourceid_by_name_or_id',
                entity='subnet'
            ),
            translation.TranslationRule(
                props,
                translation.TranslationRule.RESOLVE,
                [self.VIP, self.VIP_SUBNET],
                client_plugin=self.client_plugin(),
                finder='find_resourceid_by_name_or_id',
                entity='subnet'
            )
        ]

    def validate(self):
        res = super(Pool, self).validate()
        if res:
            return res
        session_p = self.properties[self.VIP].get(self.VIP_SESSION_PERSISTENCE)
        if session_p is None:
            # session persistence is not configured, skip validation
            return

        persistence_type = session_p[self.VIP_SESSION_PERSISTENCE_TYPE]
        if persistence_type == 'APP_COOKIE':
            if session_p.get(self.VIP_SESSION_PERSISTENCE_COOKIE_NAME):
                return

            msg = _('Property cookie_name is required, when '
                    'session_persistence type is set to APP_COOKIE.')
            raise exception.StackValidationFailed(message=msg)

    def handle_create(self):
        properties = self.prepare_properties(
            self.properties,
            self.physical_resource_name())
        subnet_id = properties.pop(self.SUBNET)
        properties['subnet_id'] = subnet_id
        vip_properties = properties.pop(self.VIP)
        monitors = properties.pop(self.MONITORS)

        pool = self.client().create_pool({'pool': properties})['pool']
        self.resource_id_set(pool['id'])

        for monitor in monitors:
            self.client().associate_health_monitor(
                pool['id'], {'health_monitor': {'id': monitor}})

        vip_arguments = self.prepare_properties(
            vip_properties,
            '%s.vip' % (self.name,))

        session_p = vip_arguments.get(self.VIP_SESSION_PERSISTENCE)
        if session_p is not None:
            prepared_props = self.prepare_properties(session_p, None)
            vip_arguments['session_persistence'] = prepared_props

        vip_arguments['protocol'] = self.properties[self.PROTOCOL]

        if vip_arguments.get(self.VIP_SUBNET) is None:
            vip_arguments['subnet_id'] = subnet_id
        else:
            vip_arguments['subnet_id'] = vip_arguments.pop(self.VIP_SUBNET)

        vip_arguments['pool_id'] = pool['id']
        vip = self.client().create_vip({'vip': vip_arguments})['vip']

        self.metadata_set({'vip': vip['id']})

    def _show_resource(self):
        return self.client().show_pool(self.resource_id)['pool']

    def check_create_complete(self, data):
        attributes = self._show_resource()
        status = attributes['status']
        if status == 'PENDING_CREATE':
            return False
        elif status == 'ACTIVE':
            vip_attributes = self.client().show_vip(
                self.metadata_get()['vip'])['vip']
            vip_status = vip_attributes['status']
            if vip_status == 'PENDING_CREATE':
                return False
            if vip_status == 'ACTIVE':
                return True
            if vip_status == 'ERROR':
                raise exception.ResourceInError(
                    resource_status=vip_status,
                    status_reason=_('error in vip'))
            raise exception.ResourceUnknownStatus(
                resource_status=vip_status,
                result=_('Pool creation failed due to vip'))
        elif status == 'ERROR':
            raise exception.ResourceInError(
                resource_status=status,
                status_reason=_('error in pool'))
        else:
            raise exception.ResourceUnknownStatus(
                resource_status=status,
                result=_('Pool creation failed'))

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            if self.MONITORS in prop_diff:
                monitors = set(prop_diff.pop(self.MONITORS))
                old_monitors = set(self.properties[self.MONITORS])
                for monitor in old_monitors - monitors:
                    self.client().disassociate_health_monitor(
                        self.resource_id, monitor)
                for monitor in monitors - old_monitors:
                    self.client().associate_health_monitor(
                        self.resource_id, {'health_monitor': {'id': monitor}})

            if prop_diff:
                self.client().update_pool(self.resource_id,
                                          {'pool': prop_diff})

    def _resolve_attribute(self, name):
        if name == self.VIP_ATTR:
            return self.client().show_vip(self.metadata_get()['vip'])['vip']
        return super(Pool, self)._resolve_attribute(name)

    def handle_delete(self):
        if not self.resource_id:
            prg = progress.PoolDeleteProgress(True)
            return prg

        prg = progress.PoolDeleteProgress()
        if not self.metadata_get():
            prg.vip['delete_called'] = True
            prg.vip['deleted'] = True
        return prg

    def _delete_vip(self):
        return self._not_found_in_call(
            self.client().delete_vip, self.metadata_get()['vip'])

    def _check_vip_deleted(self):
        return self._not_found_in_call(
            self.client().show_vip, self.metadata_get()['vip'])

    def _delete_pool(self):
        return self._not_found_in_call(
            self.client().delete_pool, self.resource_id)

    def check_delete_complete(self, prg):
        if not prg.vip['delete_called']:
            prg.vip['deleted'] = self._delete_vip()
            prg.vip['delete_called'] = True
            return False
        if not prg.vip['deleted']:
            prg.vip['deleted'] = self._check_vip_deleted()
            return False
        if not prg.pool['delete_called']:
            prg.pool['deleted'] = self._delete_pool()
            prg.pool['delete_called'] = True
            return prg.pool['deleted']
        if not prg.pool['deleted']:
            prg.pool['deleted'] = super(Pool, self).check_delete_complete(True)
            return prg.pool['deleted']
        return True
示例#15
0
class PoolMember(neutron.NeutronResource):
    """A resource to handle loadbalancer members.

    A pool member represents the application running on backend server.
    """

    required_service_extension = 'lbaas'

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (
        POOL_ID, ADDRESS, PROTOCOL_PORT, WEIGHT, ADMIN_STATE_UP,
    ) = (
        'pool_id', 'address', 'protocol_port', 'weight', 'admin_state_up',
    )

    ATTRIBUTES = (
        ADMIN_STATE_UP_ATTR, TENANT_ID, WEIGHT_ATTR, ADDRESS_ATTR,
        POOL_ID_ATTR, PROTOCOL_PORT_ATTR,
    ) = (
        'admin_state_up', 'tenant_id', 'weight', 'address',
        'pool_id', 'protocol_port',
    )

    properties_schema = {
        POOL_ID: properties.Schema(
            properties.Schema.STRING,
            _('The ID of the load balancing pool.'),
            required=True,
            update_allowed=True
        ),
        ADDRESS: properties.Schema(
            properties.Schema.STRING,
            _('IP address of the pool member on the pool network.'),
            required=True,
            constraints=[
                constraints.CustomConstraint('ip_addr')
            ]
        ),
        PROTOCOL_PORT: properties.Schema(
            properties.Schema.INTEGER,
            _('TCP port on which the pool member listens for requests or '
              'connections.'),
            required=True,
            constraints=[
                constraints.Range(0, 65535),
            ]
        ),
        WEIGHT: properties.Schema(
            properties.Schema.INTEGER,
            _('Weight of pool member in the pool (default to 1).'),
            constraints=[
                constraints.Range(0, 256),
            ],
            update_allowed=True
        ),
        ADMIN_STATE_UP: properties.Schema(
            properties.Schema.BOOLEAN,
            _('The administrative state of the pool member.'),
            default=True
        ),
    }

    attributes_schema = {
        ADMIN_STATE_UP_ATTR: attributes.Schema(
            _('The administrative state of this pool member.'),
            type=attributes.Schema.STRING
        ),
        TENANT_ID: attributes.Schema(
            _('Tenant owning the pool member.'),
            type=attributes.Schema.STRING
        ),
        WEIGHT_ATTR: attributes.Schema(
            _('Weight of the pool member in the pool.'),
            type=attributes.Schema.STRING
        ),
        ADDRESS_ATTR: attributes.Schema(
            _('IP address of the pool member.'),
            type=attributes.Schema.STRING
        ),
        POOL_ID_ATTR: attributes.Schema(
            _('The ID of the load balancing pool.'),
            type=attributes.Schema.STRING
        ),
        PROTOCOL_PORT_ATTR: attributes.Schema(
            _('TCP port on which the pool member listens for requests or '
              'connections.'),
            type=attributes.Schema.STRING
        ),
    }

    def handle_create(self):
        pool = self.properties[self.POOL_ID]
        protocol_port = self.properties[self.PROTOCOL_PORT]
        address = self.properties[self.ADDRESS]
        admin_state_up = self.properties[self.ADMIN_STATE_UP]
        weight = self.properties[self.WEIGHT]

        params = {
            'pool_id': pool,
            'address': address,
            'protocol_port': protocol_port,
            'admin_state_up': admin_state_up
        }

        if weight is not None:
            params['weight'] = weight

        member = self.client().create_member({'member': params})['member']
        self.resource_id_set(member['id'])

    def _show_resource(self):
        return self.client().show_member(self.resource_id)['member']

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            self.client().update_member(
                self.resource_id, {'member': prop_diff})

    def handle_delete(self):
        if not self.resource_id:
            return

        try:
            self.client().delete_member(self.resource_id)
        except Exception as ex:
            self.client_plugin().ignore_not_found(ex)
        else:
            return True
示例#16
0
class KeystoneService(resource.Resource):
    """Heat Template Resource for Keystone Service.

    A resource that allows to create new service and manage it by Keystone.
    """

    support_status = support.SupportStatus(
        version='5.0.0', message=_('Supported versions: keystone v3'))

    default_client_name = 'keystone'

    entity = 'services'

    PROPERTIES = (
        NAME,
        DESCRIPTION,
        TYPE,
        ENABLED,
    ) = (
        'name',
        'description',
        'type',
        'enabled',
    )

    properties_schema = {
        NAME:
        properties.Schema(properties.Schema.STRING,
                          _('Name of keystone service.'),
                          update_allowed=True),
        DESCRIPTION:
        properties.Schema(properties.Schema.STRING,
                          _('Description of keystone service.'),
                          update_allowed=True),
        TYPE:
        properties.Schema(properties.Schema.STRING,
                          _('Type of keystone Service.'),
                          update_allowed=True,
                          required=True),
        ENABLED:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('This service is enabled or disabled.'),
            default=True,
            update_allowed=True,
            support_status=support.SupportStatus(version='6.0.0'))
    }

    def client(self):
        return super(KeystoneService, self).client().client

    def handle_create(self):
        name = (self.properties[self.NAME] or self.physical_resource_name())
        description = self.properties[self.DESCRIPTION]
        type = self.properties[self.TYPE]
        enabled = self.properties[self.ENABLED]

        service = self.client().services.create(name=name,
                                                description=description,
                                                type=type,
                                                enabled=enabled)

        self.resource_id_set(service.id)

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            name = None
            # Don't update the name if no change
            if self.NAME in prop_diff:
                name = prop_diff[self.NAME] or self.physical_resource_name()
            description = prop_diff.get(self.DESCRIPTION)
            type = prop_diff.get(self.TYPE)
            enabled = prop_diff.get(self.ENABLED)

            self.client().services.update(service=self.resource_id,
                                          name=name,
                                          description=description,
                                          type=type,
                                          enabled=enabled)
示例#17
0
class Pool(neutron.NeutronResource):
    """A resource for managing LBaaS v2 Pools.

    This resources manages Neutron-LBaaS v2 Pools, which represent a group
    of nodes. Pools define the subnet where nodes reside, balancing algorithm,
    and the nodes themselves.
    """

    support_status = support.SupportStatus(version='6.0.0')

    required_service_extension = 'lbaasv2'

    PROPERTIES = (
        ADMIN_STATE_UP,
        DESCRIPTION,
        SESSION_PERSISTENCE,
        NAME,
        LB_ALGORITHM,
        LISTENER,
        PROTOCOL,
        SESSION_PERSISTENCE_TYPE,
        SESSION_PERSISTENCE_COOKIE_NAME,
    ) = ('admin_state_up', 'description', 'session_persistence', 'name',
         'lb_algorithm', 'listener', 'protocol', 'type', 'cookie_name')

    SESSION_PERSISTENCE_TYPES = (SOURCE_IP, HTTP_COOKIE,
                                 APP_COOKIE) = ('SOURCE_IP', 'HTTP_COOKIE',
                                                'APP_COOKIE')

    ATTRIBUTES = (HEALTHMONITOR_ID_ATTR, LISTENERS_ATTR,
                  MEMBERS_ATTR) = ('healthmonitor_id', 'listeners', 'members')

    properties_schema = {
        ADMIN_STATE_UP:
        properties.Schema(properties.Schema.BOOLEAN,
                          _('The administrative state of this pool.'),
                          default=True,
                          update_allowed=True,
                          constraints=[constraints.AllowedValues(['True'])]),
        DESCRIPTION:
        properties.Schema(properties.Schema.STRING,
                          _('Description of this pool.'),
                          update_allowed=True,
                          default=''),
        SESSION_PERSISTENCE:
        properties.Schema(
            properties.Schema.MAP,
            _('Configuration of session persistence.'),
            schema={
                SESSION_PERSISTENCE_TYPE:
                properties.Schema(
                    properties.Schema.STRING,
                    _('Method of implementation of session '
                      'persistence feature.'),
                    required=True,
                    constraints=[
                        constraints.AllowedValues(SESSION_PERSISTENCE_TYPES)
                    ]),
                SESSION_PERSISTENCE_COOKIE_NAME:
                properties.Schema(
                    properties.Schema.STRING,
                    _('Name of the cookie, '
                      'required if type is APP_COOKIE.'))
            },
        ),
        NAME:
        properties.Schema(properties.Schema.STRING,
                          _('Name of this pool.'),
                          update_allowed=True),
        LB_ALGORITHM:
        properties.Schema(
            properties.Schema.STRING,
            _('The algorithm used to distribute load between the members of '
              'the pool.'),
            required=True,
            constraints=[
                constraints.AllowedValues(
                    ['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP']),
            ],
            update_allowed=True,
        ),
        LISTENER:
        properties.Schema(
            properties.Schema.STRING,
            _('Listner name or ID to be associated with this pool.'),
            required=True),
        PROTOCOL:
        properties.Schema(properties.Schema.STRING,
                          _('Protocol of the pool.'),
                          required=True,
                          constraints=[
                              constraints.AllowedValues(['TCP', 'HTTP']),
                          ]),
    }

    attributes_schema = {
        HEALTHMONITOR_ID_ATTR:
        attributes.Schema(
            _('ID of the health monitor associated with this pool.'),
            type=attributes.Schema.STRING),
        LISTENERS_ATTR:
        attributes.Schema(_('Listener associated with this pool.'),
                          type=attributes.Schema.STRING),
        MEMBERS_ATTR:
        attributes.Schema(_('Members associated with this pool.'),
                          type=attributes.Schema.LIST),
    }

    def __init__(self, name, definition, stack):
        super(Pool, self).__init__(name, definition, stack)
        self._lb_id = None

    @property
    def lb_id(self):
        if self._lb_id is None:
            listener_id = self.client_plugin().find_resourceid_by_name_or_id(
                'listener', self.properties[self.LISTENER])
            listener = self.client().show_listener(listener_id)['listener']

            self._lb_id = listener['loadbalancers'][0]['id']
        return self._lb_id

    def validate(self):
        res = super(Pool, self).validate()
        if res:
            return res

        if self.properties[self.SESSION_PERSISTENCE] is not None:
            session_p = self.properties[self.SESSION_PERSISTENCE]
            persistence_type = session_p[self.SESSION_PERSISTENCE_TYPE]
            if persistence_type == self.APP_COOKIE:
                if not session_p.get(self.SESSION_PERSISTENCE_COOKIE_NAME):
                    msg = (_('Property %(cookie)s is required when %(sp)s '
                             'type is set to %(app)s.') %
                           {
                               'cookie': self.SESSION_PERSISTENCE_COOKIE_NAME,
                               'sp': self.SESSION_PERSISTENCE,
                               'app': self.APP_COOKIE
                           })
                    raise exception.StackValidationFailed(message=msg)
            elif persistence_type == self.SOURCE_IP:
                if session_p.get(self.SESSION_PERSISTENCE_COOKIE_NAME):
                    msg = (_('Property %(cookie)s must NOT be specified when '
                             '%(sp)s type is set to %(ip)s.') %
                           {
                               'cookie': self.SESSION_PERSISTENCE_COOKIE_NAME,
                               'sp': self.SESSION_PERSISTENCE,
                               'ip': self.SOURCE_IP
                           })
                    raise exception.StackValidationFailed(message=msg)

    def _check_lb_status(self):
        return self.client_plugin().check_lb_status(self.lb_id)

    def handle_create(self):
        properties = self.prepare_properties(self.properties,
                                             self.physical_resource_name())

        self.client_plugin().resolve_listener(properties, self.LISTENER,
                                              'listener_id')

        session_p = properties.get(self.SESSION_PERSISTENCE)
        if session_p is not None:
            session_props = self.prepare_properties(session_p, None)
            properties[self.SESSION_PERSISTENCE] = session_props

        return properties

    def check_create_complete(self, properties):
        if self.resource_id is None:
            try:
                pool = self.client().create_lbaas_pool({'pool':
                                                        properties})['pool']
                self.resource_id_set(pool['id'])
            except Exception as ex:
                if self.client_plugin().is_invalid(ex):
                    return False
                raise

        return self._check_lb_status()

    def _show_resource(self):
        return self.client().show_lbaas_pool(self.resource_id)['pool']

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        self._update_called = False
        return prop_diff

    def check_update_complete(self, prop_diff):
        if not prop_diff:
            return True

        if not self._update_called:
            try:
                self.client().update_lbaas_pool(self.resource_id,
                                                {'pool': prop_diff})
                self._update_called = True
            except Exception as ex:
                if self.client_plugin().is_invalid(ex):
                    return False
                raise

        return self._check_lb_status()

    def handle_delete(self):
        self._delete_called = False

    def check_delete_complete(self, data):
        if self.resource_id is None:
            return True

        if not self._delete_called:
            try:
                self.client().delete_lbaas_pool(self.resource_id)
                self._delete_called = True
            except Exception as ex:
                if self.client_plugin().is_invalid(ex):
                    return False
                elif self.client_plugin().is_not_found(ex):
                    return True
                raise

        return self._check_lb_status()
示例#18
0
class Subnet(neutron.NeutronResource):
    """A resource for managing Neutron subnets.

    A subnet represents an IP address block that can be used for assigning IP
    addresses to virtual instances. Each subnet must have a CIDR and must be
    associated with a network. IPs can be either selected from the whole subnet
    CIDR, or from "allocation pools" that can be specified by the user.
    """

    PROPERTIES = (
        NETWORK_ID,
        NETWORK,
        SUBNETPOOL,
        PREFIXLEN,
        CIDR,
        VALUE_SPECS,
        NAME,
        IP_VERSION,
        DNS_NAMESERVERS,
        GATEWAY_IP,
        ENABLE_DHCP,
        ALLOCATION_POOLS,
        TENANT_ID,
        HOST_ROUTES,
        IPV6_RA_MODE,
        IPV6_ADDRESS_MODE,
    ) = (
        'network_id',
        'network',
        'subnetpool',
        'prefixlen',
        'cidr',
        'value_specs',
        'name',
        'ip_version',
        'dns_nameservers',
        'gateway_ip',
        'enable_dhcp',
        'allocation_pools',
        'tenant_id',
        'host_routes',
        'ipv6_ra_mode',
        'ipv6_address_mode',
    )

    _ALLOCATION_POOL_KEYS = (
        ALLOCATION_POOL_START,
        ALLOCATION_POOL_END,
    ) = (
        'start',
        'end',
    )

    _HOST_ROUTES_KEYS = (
        ROUTE_DESTINATION,
        ROUTE_NEXTHOP,
    ) = (
        'destination',
        'nexthop',
    )

    _IPV6_DHCP_MODES = (
        DHCPV6_STATEFUL,
        DHCPV6_STATELESS,
        SLAAC,
    ) = (
        'dhcpv6-stateful',
        'dhcpv6-stateless',
        'slaac',
    )

    ATTRIBUTES = (
        NAME_ATTR,
        NETWORK_ID_ATTR,
        TENANT_ID_ATTR,
        ALLOCATION_POOLS_ATTR,
        GATEWAY_IP_ATTR,
        HOST_ROUTES_ATTR,
        IP_VERSION_ATTR,
        CIDR_ATTR,
        DNS_NAMESERVERS_ATTR,
        ENABLE_DHCP_ATTR,
    ) = (
        'name',
        'network_id',
        'tenant_id',
        'allocation_pools',
        'gateway_ip',
        'host_routes',
        'ip_version',
        'cidr',
        'dns_nameservers',
        'enable_dhcp',
    )

    properties_schema = {
        NETWORK_ID:
        properties.Schema(
            properties.Schema.STRING,
            support_status=support.SupportStatus(
                status=support.HIDDEN,
                version='5.0.0',
                message=_('Use property %s.') % NETWORK,
                previous_status=support.SupportStatus(
                    status=support.DEPRECATED, version='2014.2')),
            constraints=[constraints.CustomConstraint('neutron.network')],
        ),
        NETWORK:
        properties.Schema(
            properties.Schema.STRING,
            _('The ID of the attached network.'),
            required=True,
            constraints=[constraints.CustomConstraint('neutron.network')],
            support_status=support.SupportStatus(version='2014.2')),
        SUBNETPOOL:
        properties.Schema(
            properties.Schema.STRING,
            _('The name or ID of the subnet pool.'),
            constraints=[constraints.CustomConstraint('neutron.subnetpool')],
            support_status=support.SupportStatus(version='6.0.0'),
        ),
        PREFIXLEN:
        properties.Schema(
            properties.Schema.INTEGER,
            _('Prefix length for subnet allocation from subnet pool.'),
            constraints=[constraints.Range(min=0)],
            support_status=support.SupportStatus(version='6.0.0'),
        ),
        CIDR:
        properties.Schema(
            properties.Schema.STRING,
            _('The CIDR.'),
            constraints=[constraints.CustomConstraint('net_cidr')]),
        VALUE_SPECS:
        properties.Schema(properties.Schema.MAP,
                          _('Extra parameters to include in the request.'),
                          default={},
                          update_allowed=True),
        NAME:
        properties.Schema(properties.Schema.STRING,
                          _('The name of the subnet.'),
                          update_allowed=True),
        IP_VERSION:
        properties.Schema(properties.Schema.INTEGER,
                          _('The IP version, which is 4 or 6.'),
                          default=4,
                          constraints=[
                              constraints.AllowedValues([4, 6]),
                          ]),
        DNS_NAMESERVERS:
        properties.Schema(properties.Schema.LIST,
                          _('A specified set of DNS name servers to be used.'),
                          default=[],
                          update_allowed=True),
        GATEWAY_IP:
        properties.Schema(
            properties.Schema.STRING,
            _('The gateway IP address. Set to any of [ null | ~ | "" ] '
              'to create/update a subnet without a gateway. '
              'If omitted when creation, neutron will assign the first '
              'free IP address within the subnet to the gateway '
              'automatically. If remove this from template when update, '
              'the old gateway IP address will be detached.'),
            update_allowed=True),
        ENABLE_DHCP:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('Set to true if DHCP is enabled and false if DHCP is disabled.'),
            default=True,
            update_allowed=True),
        ALLOCATION_POOLS:
        properties.Schema(
            properties.Schema.LIST,
            _('The start and end addresses for the allocation pools.'),
            schema=properties.Schema(
                properties.Schema.MAP,
                schema={
                    ALLOCATION_POOL_START:
                    properties.Schema(
                        properties.Schema.STRING,
                        _('Start address for the allocation pool.'),
                        required=True,
                        constraints=[constraints.CustomConstraint('ip_addr')]),
                    ALLOCATION_POOL_END:
                    properties.Schema(
                        properties.Schema.STRING,
                        _('End address for the allocation pool.'),
                        required=True,
                        constraints=[constraints.CustomConstraint('ip_addr')]),
                },
            ),
            update_allowed=True),
        TENANT_ID:
        properties.Schema(
            properties.Schema.STRING,
            _('The ID of the tenant who owns the network. Only administrative '
              'users can specify a tenant ID other than their own.')),
        HOST_ROUTES:
        properties.Schema(
            properties.Schema.LIST,
            _('A list of host route dictionaries for the subnet.'),
            schema=properties.Schema(
                properties.Schema.MAP,
                schema={
                    ROUTE_DESTINATION:
                    properties.Schema(
                        properties.Schema.STRING,
                        _('The destination for static route.'),
                        required=True,
                        constraints=[constraints.CustomConstraint('net_cidr')
                                     ]),
                    ROUTE_NEXTHOP:
                    properties.Schema(
                        properties.Schema.STRING,
                        _('The next hop for the destination.'),
                        required=True,
                        constraints=[constraints.CustomConstraint('ip_addr')]),
                },
            ),
            update_allowed=True),
        IPV6_RA_MODE:
        properties.Schema(
            properties.Schema.STRING,
            _('IPv6 RA (Router Advertisement) mode.'),
            constraints=[
                constraints.AllowedValues(
                    [DHCPV6_STATEFUL, DHCPV6_STATELESS, SLAAC]),
            ],
            support_status=support.SupportStatus(version='2015.1')),
        IPV6_ADDRESS_MODE:
        properties.Schema(
            properties.Schema.STRING,
            _('IPv6 address mode.'),
            constraints=[
                constraints.AllowedValues(
                    [DHCPV6_STATEFUL, DHCPV6_STATELESS, SLAAC]),
            ],
            support_status=support.SupportStatus(version='2015.1')),
    }

    attributes_schema = {
        NAME_ATTR:
        attributes.Schema(_("Friendly name of the subnet."),
                          type=attributes.Schema.STRING),
        NETWORK_ID_ATTR:
        attributes.Schema(_("Parent network of the subnet."),
                          type=attributes.Schema.STRING),
        TENANT_ID_ATTR:
        attributes.Schema(_("Tenant owning the subnet."),
                          type=attributes.Schema.STRING),
        ALLOCATION_POOLS_ATTR:
        attributes.Schema(_("Ip allocation pools and their ranges."),
                          type=attributes.Schema.LIST),
        GATEWAY_IP_ATTR:
        attributes.Schema(_("Ip of the subnet's gateway."),
                          type=attributes.Schema.STRING),
        HOST_ROUTES_ATTR:
        attributes.Schema(_("Additional routes for this subnet."),
                          type=attributes.Schema.LIST),
        IP_VERSION_ATTR:
        attributes.Schema(_("Ip version for the subnet."),
                          type=attributes.Schema.STRING),
        CIDR_ATTR:
        attributes.Schema(_("CIDR block notation for this subnet."),
                          type=attributes.Schema.STRING),
        DNS_NAMESERVERS_ATTR:
        attributes.Schema(_("List of dns nameservers."),
                          type=attributes.Schema.LIST),
        ENABLE_DHCP_ATTR:
        attributes.Schema(
            _("'true' if DHCP is enabled for this subnet; 'false' otherwise."),
            type=attributes.Schema.STRING),
    }

    def translation_rules(self, props):
        return [
            translation.TranslationRule(props,
                                        translation.TranslationRule.REPLACE,
                                        [self.NETWORK],
                                        value_path=[self.NETWORK_ID]),
            translation.TranslationRule(props,
                                        translation.TranslationRule.RESOLVE,
                                        [self.NETWORK],
                                        client_plugin=self.client_plugin(),
                                        finder='find_resourceid_by_name_or_id',
                                        entity='network'),
            translation.TranslationRule(props,
                                        translation.TranslationRule.RESOLVE,
                                        [self.SUBNETPOOL],
                                        client_plugin=self.client_plugin(),
                                        finder='find_resourceid_by_name_or_id',
                                        entity='subnetpool')
        ]

    @classmethod
    def _null_gateway_ip(cls, props):
        if cls.GATEWAY_IP not in props:
            return
        # Specifying null in the gateway_ip will result in
        # a property containing an empty string.
        # A null gateway_ip has special meaning in the API
        # so this needs to be set back to None.
        # See bug https://bugs.launchpad.net/heat/+bug/1226666
        if props.get(cls.GATEWAY_IP) == '':
            props[cls.GATEWAY_IP] = None

    def validate(self):
        super(Subnet, self).validate()
        subnetpool = self.properties[self.SUBNETPOOL]
        prefixlen = self.properties[self.PREFIXLEN]
        cidr = self.properties[self.CIDR]
        if subnetpool and cidr:
            raise exception.ResourcePropertyConflict(self.SUBNETPOOL,
                                                     self.CIDR)
        if not subnetpool and not cidr:
            raise exception.PropertyUnspecifiedError(self.SUBNETPOOL,
                                                     self.CIDR)
        if prefixlen and cidr:
            raise exception.ResourcePropertyConflict(self.PREFIXLEN, self.CIDR)
        ra_mode = self.properties[self.IPV6_RA_MODE]
        address_mode = self.properties[self.IPV6_ADDRESS_MODE]

        if (self.properties[self.IP_VERSION] == 4) and (ra_mode
                                                        or address_mode):
            msg = _('ipv6_ra_mode and ipv6_address_mode are not supported '
                    'for ipv4.')
            raise exception.StackValidationFailed(message=msg)
        if ra_mode and address_mode and (ra_mode != address_mode):
            msg = _('When both ipv6_ra_mode and ipv6_address_mode are set, '
                    'they must be equal.')
            raise exception.StackValidationFailed(message=msg)

        gateway_ip = self.properties.get(self.GATEWAY_IP)
        if (gateway_ip and gateway_ip not in ['~', '']
                and not netutils.is_valid_ip(gateway_ip)):
            msg = (_('Gateway IP address "%(gateway)s" is in '
                     'invalid format.'), gateway_ip)
            raise exception.StackValidationFailed(message=msg)

    def handle_create(self):
        props = self.prepare_properties(self.properties,
                                        self.physical_resource_name())
        props['network_id'] = props.pop(self.NETWORK)
        if self.SUBNETPOOL in props and props[self.SUBNETPOOL]:
            props['subnetpool_id'] = props.pop('subnetpool')
        self._null_gateway_ip(props)
        subnet = self.client().create_subnet({'subnet': props})['subnet']
        self.resource_id_set(subnet['id'])

    def handle_delete(self):
        if not self.resource_id:
            return

        try:
            self.client().delete_subnet(self.resource_id)
        except Exception as ex:
            self.client_plugin().ignore_not_found(ex)
        else:
            return True

    def _show_resource(self):
        return self.client().show_subnet(self.resource_id)['subnet']

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            self.prepare_update_properties(prop_diff)
            if (self.ALLOCATION_POOLS in prop_diff
                    and prop_diff[self.ALLOCATION_POOLS] is None):
                prop_diff[self.ALLOCATION_POOLS] = []

            # If the new value is '', set to None
            self._null_gateway_ip(prop_diff)

            self.client().update_subnet(self.resource_id,
                                        {'subnet': prop_diff})

    def is_allow_replace(self):
        return True
示例#19
0
class QoSPolicy(neutron.NeutronResource):
    """A resource for Neutron QoS Policy.

    This QoS policy can be associated with neutron resources,
    such as port and network, to provide QoS capabilities.

    The default policy usage of this resource is limited to
    administrators only.
    """

    required_service_extension = 'qos'

    support_status = support.SupportStatus(version='6.0.0')

    PROPERTIES = (
        NAME, DESCRIPTION, SHARED, TENANT_ID,
    ) = (
        'name', 'description', 'shared', 'tenant_id',
    )

    ATTRIBUTES = (
        RULES_ATTR,
    ) = (
        'rules',
    )

    properties_schema = {
        NAME: properties.Schema(
            properties.Schema.STRING,
            _('The name for the QoS policy.'),
            update_allowed=True
        ),
        DESCRIPTION: properties.Schema(
            properties.Schema.STRING,
            _('The description for the QoS policy.'),
            update_allowed=True
        ),
        SHARED: properties.Schema(
            properties.Schema.BOOLEAN,
            _('Whether this QoS policy should be shared to other tenants.'),
            default=False,
            update_allowed=True
        ),
        TENANT_ID: properties.Schema(
            properties.Schema.STRING,
            _('The owner tenant ID of this QoS policy.')
        ),
    }

    attributes_schema = {
        RULES_ATTR: attributes.Schema(
            _("A list of all rules for the QoS policy."),
            type=attributes.Schema.LIST
        )
    }

    def handle_create(self):
        props = self.prepare_properties(
            self.properties,
            self.physical_resource_name())

        policy = self.client().create_qos_policy({'policy': props})['policy']
        self.resource_id_set(policy['id'])

    def handle_delete(self):
        if self.resource_id is None:
            return

        with self.client_plugin().ignore_not_found:
            self.client().delete_qos_policy(self.resource_id)

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            self.prepare_update_properties(prop_diff)
            self.client().update_qos_policy(
                self.resource_id,
                {'policy': prop_diff})

    def _show_resource(self):
        return self.client().show_qos_policy(
            self.resource_id)['policy']
示例#20
0
class RouteTable(resource.Resource):

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (
        VPC_ID,
        TAGS,
    ) = (
        'VpcId',
        'Tags',
    )

    _TAG_KEYS = (
        TAG_KEY,
        TAG_VALUE,
    ) = (
        'Key',
        'Value',
    )

    properties_schema = {
        VPC_ID:
        properties.Schema(properties.Schema.STRING,
                          _('VPC ID for where the route table is created.'),
                          required=True),
        TAGS:
        properties.Schema(
            properties.Schema.LIST,
            schema=properties.Schema(
                properties.Schema.MAP,
                _('List of tags to be attached to this resource.'),
                schema={
                    TAG_KEY:
                    properties.Schema(properties.Schema.STRING, required=True),
                    TAG_VALUE:
                    properties.Schema(properties.Schema.STRING, required=True),
                },
                implemented=False,
            )),
    }

    default_client_name = 'neutron'

    def handle_create(self):
        client = self.client()
        props = {'name': self.physical_resource_name()}
        router = client.create_router({'router': props})['router']
        self.resource_id_set(router['id'])

    def check_create_complete(self, *args):
        client = self.client()
        attributes = client.show_router(self.resource_id)['router']
        if not neutron.NeutronResource.is_built(attributes):
            return False

        network_id = self.properties.get(self.VPC_ID)
        default_router = vpc.VPC.router_for_vpc(client, network_id)
        if default_router and default_router.get('external_gateway_info'):
            # the default router for the VPC is connected
            # to the external router, so do it for this too.
            external_network_id = default_router['external_gateway_info'][
                'network_id']
            client.add_gateway_router(self.resource_id,
                                      {'network_id': external_network_id})
        return True

    def handle_delete(self):
        client = self.client()

        router_id = self.resource_id
        with self.client_plugin().ignore_not_found:
            client.delete_router(router_id)

        # just in case this router has been added to a gateway, remove it
        with self.client_plugin().ignore_not_found:
            client.remove_gateway_router(router_id)
示例#21
0
class SoftwareDeployment(signal_responder.SignalResponder):
    """This resource associates a server with some configuration.

    The configuration is to be deployed to that server.

    A deployment allows input values to be specified which map to the inputs
    schema defined in the config resource. These input values are interpreted
    by the configuration tool in a tool-specific manner.

    Whenever this resource goes to an IN_PROGRESS state, it creates an
    ephemeral config that includes the inputs values plus a number of extra
    inputs which have names prefixed with deploy_. The extra inputs relate
    to the current state of the stack, along with the information and
    credentials required to signal back the deployment results.

    Unless signal_transport=NO_SIGNAL, this resource will remain in an
    IN_PROGRESS state until the server signals it with the output values
    for that deployment. Those output values are then available as resource
    attributes, along with the default attributes deploy_stdout,
    deploy_stderr and deploy_status_code.

    Specifying actions other than the default CREATE and UPDATE will result
    in the deployment being triggered in those actions. For example this would
    allow cleanup configuration to be performed during actions SUSPEND and
    DELETE. A config could be designed to only work with some specific
    actions, or a config can read the value of the deploy_action input to
    allow conditional logic to perform different configuration for different
    actions.
    """

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (CONFIG, SERVER, INPUT_VALUES, DEPLOY_ACTIONS, NAME,
                  SIGNAL_TRANSPORT) = ('config', 'server', 'input_values',
                                       'actions', 'name', 'signal_transport')

    ALLOWED_DEPLOY_ACTIONS = (
        resource.Resource.CREATE,
        resource.Resource.UPDATE,
        resource.Resource.DELETE,
        resource.Resource.SUSPEND,
        resource.Resource.RESUME,
    )

    ATTRIBUTES = (STDOUT, STDERR,
                  STATUS_CODE) = ('deploy_stdout', 'deploy_stderr',
                                  'deploy_status_code')

    DERIVED_CONFIG_INPUTS = (
        DEPLOY_SERVER_ID, DEPLOY_ACTION, DEPLOY_SIGNAL_ID, DEPLOY_STACK_ID,
        DEPLOY_RESOURCE_NAME, DEPLOY_AUTH_URL, DEPLOY_USERNAME,
        DEPLOY_PASSWORD, DEPLOY_PROJECT_ID, DEPLOY_USER_ID, DEPLOY_SIGNAL_VERB,
        DEPLOY_SIGNAL_TRANSPORT,
        DEPLOY_QUEUE_ID) = ('deploy_server_id', 'deploy_action',
                            'deploy_signal_id', 'deploy_stack_id',
                            'deploy_resource_name', 'deploy_auth_url',
                            'deploy_username', 'deploy_password',
                            'deploy_project_id', 'deploy_user_id',
                            'deploy_signal_verb', 'deploy_signal_transport',
                            'deploy_queue_id')

    SIGNAL_TRANSPORTS = (CFN_SIGNAL, TEMP_URL_SIGNAL, HEAT_SIGNAL, NO_SIGNAL,
                         ZAQAR_SIGNAL) = ('CFN_SIGNAL', 'TEMP_URL_SIGNAL',
                                          'HEAT_SIGNAL', 'NO_SIGNAL',
                                          'ZAQAR_SIGNAL')

    properties_schema = {
        CONFIG:
        properties.Schema(
            properties.Schema.STRING,
            _('ID of software configuration resource to execute when '
              'applying to the server.'),
            update_allowed=True),
        SERVER:
        properties.Schema(
            properties.Schema.STRING,
            _('ID of resource to apply configuration to. '
              'Normally this should be a Nova server ID.'),
            required=True,
        ),
        INPUT_VALUES:
        properties.Schema(
            properties.Schema.MAP,
            _('Input values to apply to the software configuration on this '
              'server.'),
            update_allowed=True),
        DEPLOY_ACTIONS:
        properties.Schema(
            properties.Schema.LIST,
            _('Which lifecycle actions of the deployment resource will result '
              'in this deployment being triggered.'),
            update_allowed=True,
            default=[resource.Resource.CREATE, resource.Resource.UPDATE],
            constraints=[constraints.AllowedValues(ALLOWED_DEPLOY_ACTIONS)]),
        NAME:
        properties.Schema(
            properties.Schema.STRING,
            _('Name of the derived config associated with this deployment. '
              'This is used to apply a sort order to the list of '
              'configurations currently deployed to a server.'),
            update_allowed=True),
        SIGNAL_TRANSPORT:
        properties.Schema(
            properties.Schema.STRING,
            _('How the server should signal to heat with the deployment '
              'output values. CFN_SIGNAL will allow an HTTP POST to a CFN '
              'keypair signed URL. TEMP_URL_SIGNAL will create a '
              'Swift TempURL to be signaled via HTTP PUT. HEAT_SIGNAL '
              'will allow calls to the Heat API resource-signal using the '
              'provided keystone credentials. ZAQAR_SIGNAL will create a '
              'dedicated zaqar queue to be signaled using the provided '
              'keystone credentials. NO_SIGNAL will result in the resource '
              'going to the COMPLETE state without waiting for any signal.'),
            default=cfg.CONF.default_deployment_signal_transport,
            constraints=[
                constraints.AllowedValues(SIGNAL_TRANSPORTS),
            ]),
    }

    attributes_schema = {
        STDOUT:
        attributes.Schema(
            _("Captured stdout from the configuration execution."),
            type=attributes.Schema.STRING),
        STDERR:
        attributes.Schema(
            _("Captured stderr from the configuration execution."),
            type=attributes.Schema.STRING),
        STATUS_CODE:
        attributes.Schema(
            _("Returned status code from the configuration execution."),
            type=attributes.Schema.STRING),
    }

    default_client_name = 'heat'

    no_signal_actions = ()

    # No need to make metadata_update() calls since deployments have a
    # dedicated API for changing state on signals
    signal_needs_metadata_updates = False

    def _signal_transport_cfn(self):
        return self.properties[self.SIGNAL_TRANSPORT] == self.CFN_SIGNAL

    def _signal_transport_heat(self):
        return self.properties[self.SIGNAL_TRANSPORT] == self.HEAT_SIGNAL

    def _signal_transport_none(self):
        return self.properties[self.SIGNAL_TRANSPORT] == self.NO_SIGNAL

    def _signal_transport_temp_url(self):
        return self.properties[self.SIGNAL_TRANSPORT] == self.TEMP_URL_SIGNAL

    def _signal_transport_zaqar(self):
        return self.properties.get(self.SIGNAL_TRANSPORT) == self.ZAQAR_SIGNAL

    def _build_properties(self, config_id, action):
        props = {
            'config_id': config_id,
            'action': action,
            'input_values': self.properties.get(self.INPUT_VALUES)
        }

        if self._signal_transport_none():
            props['status'] = SoftwareDeployment.COMPLETE
            props['status_reason'] = _('Not waiting for outputs signal')
        else:
            props['status'] = SoftwareDeployment.IN_PROGRESS
            props['status_reason'] = _('Deploy data available')
        return props

    def _delete_derived_config(self, derived_config_id):
        try:
            self.rpc_client().delete_software_config(self.context,
                                                     derived_config_id)
        except Exception as ex:
            self.rpc_client().ignore_error_named(ex, 'NotFound')

    def _get_derived_config(self, action, source_config):

        derived_params = self._build_derived_config_params(
            action, source_config)
        derived_config = self.rpc_client().create_software_config(
            self.context, **derived_params)
        return derived_config[rpc_api.SOFTWARE_CONFIG_ID]

    def _handle_action(self, action):
        if self.properties.get(self.CONFIG):
            config = self.rpc_client().show_software_config(
                self.context, self.properties.get(self.CONFIG))
        else:
            config = {}

        if config.get(rpc_api.SOFTWARE_CONFIG_GROUP) == 'component':
            valid_actions = set()
            for conf in config['config']['configs']:
                valid_actions.update(conf['actions'])
            if action not in valid_actions:
                return
        elif action not in self.properties[self.DEPLOY_ACTIONS]:
            return

        props = self._build_properties(
            self._get_derived_config(action, config), action)

        if self.resource_id is None:
            resource_id = str(uuid.uuid4())
            self.resource_id_set(resource_id)
            sd = self.rpc_client().create_software_deployment(
                self.context,
                deployment_id=resource_id,
                server_id=self.properties[SoftwareDeployment.SERVER],
                stack_user_project_id=self.stack.stack_user_project_id,
                **props)
        else:
            sd = self.rpc_client().show_software_deployment(
                self.context, self.resource_id)
            prev_derived_config = sd[rpc_api.SOFTWARE_DEPLOYMENT_CONFIG_ID]
            sd = self.rpc_client().update_software_deployment(
                self.context, deployment_id=self.resource_id, **props)
            if prev_derived_config:
                self._delete_derived_config(prev_derived_config)
        if not self._signal_transport_none():
            # NOTE(pshchelo): sd is a simple dict, easy to serialize,
            # does not need fixing re LP bug #1393268
            return sd

    def _check_complete(self):
        sd = self.rpc_client().check_software_deployment(
            self.context, self.resource_id, self.stack.time_remaining())
        status = sd[rpc_api.SOFTWARE_DEPLOYMENT_STATUS]
        if status == SoftwareDeployment.COMPLETE:
            return True
        elif status == SoftwareDeployment.FAILED:
            status_reason = sd[rpc_api.SOFTWARE_DEPLOYMENT_STATUS_REASON]
            message = _("Deployment to server failed: %s") % status_reason
            LOG.info(message)
            raise exception.Error(message)

    def empty_config(self):
        return ''

    def _build_derived_config_params(self, action, source):
        scl = sc.SoftwareConfig
        derived_inputs = self._build_derived_inputs(action, source)
        derived_options = self._build_derived_options(action, source)
        derived_config = self._build_derived_config(action, source,
                                                    derived_inputs,
                                                    derived_options)
        derived_name = self.properties.get(self.NAME) or source.get(scl.NAME)
        return {
            scl.GROUP: source.get(scl.GROUP) or 'Heat::Ungrouped',
            scl.CONFIG: derived_config or self.empty_config(),
            scl.OPTIONS: derived_options,
            scl.INPUTS: derived_inputs,
            scl.OUTPUTS: source.get(scl.OUTPUTS),
            scl.NAME: derived_name or self.physical_resource_name()
        }

    def _build_derived_config(self, action, source, derived_inputs,
                              derived_options):
        return source.get(sc.SoftwareConfig.CONFIG)

    def _build_derived_options(self, action, source):
        return source.get(sc.SoftwareConfig.OPTIONS)

    def _build_derived_inputs(self, action, source):
        scl = sc.SoftwareConfig
        inputs = copy.deepcopy(source.get(scl.INPUTS)) or []
        input_values = dict(self.properties.get(self.INPUT_VALUES) or {})

        for inp in inputs:
            input_key = inp[scl.NAME]
            inp['value'] = input_values.pop(input_key, inp[scl.DEFAULT])

        # for any input values that do not have a declared input, add
        # a derived declared input so that they can be used as config
        # inputs
        for inpk, inpv in input_values.items():
            inputs.append({scl.NAME: inpk, scl.TYPE: 'String', 'value': inpv})

        inputs.extend([{
            scl.NAME: self.DEPLOY_SERVER_ID,
            scl.DESCRIPTION: _('ID of the server being deployed to'),
            scl.TYPE: 'String',
            'value': self.properties[self.SERVER]
        }, {
            scl.NAME:
            self.DEPLOY_ACTION,
            scl.DESCRIPTION:
            _('Name of the current action being deployed'),
            scl.TYPE:
            'String',
            'value':
            action
        }, {
            scl.NAME:
            self.DEPLOY_STACK_ID,
            scl.DESCRIPTION:
            _('ID of the stack this deployment belongs to'),
            scl.TYPE:
            'String',
            'value':
            self.stack.identifier().stack_path()
        }, {
            scl.NAME:
            self.DEPLOY_RESOURCE_NAME,
            scl.DESCRIPTION:
            _('Name of this deployment resource in the '
              'stack'),
            scl.TYPE:
            'String',
            'value':
            self.name
        }, {
            scl.NAME:
            self.DEPLOY_SIGNAL_TRANSPORT,
            scl.DESCRIPTION:
            _('How the server should signal to heat with '
              'the deployment output values.'),
            scl.TYPE:
            'String',
            'value':
            self.properties[self.SIGNAL_TRANSPORT]
        }])
        if self._signal_transport_cfn():
            inputs.append({
                scl.NAME:
                self.DEPLOY_SIGNAL_ID,
                scl.DESCRIPTION:
                _('ID of signal to use for signaling '
                  'output values'),
                scl.TYPE:
                'String',
                'value':
                self._get_ec2_signed_url()
            })
            inputs.append({
                scl.NAME:
                self.DEPLOY_SIGNAL_VERB,
                scl.DESCRIPTION:
                _('HTTP verb to use for signaling '
                  'output values'),
                scl.TYPE:
                'String',
                'value':
                'POST'
            })
        elif self._signal_transport_temp_url():
            inputs.append({
                scl.NAME:
                self.DEPLOY_SIGNAL_ID,
                scl.DESCRIPTION:
                _('ID of signal to use for signaling '
                  'output values'),
                scl.TYPE:
                'String',
                'value':
                self._get_swift_signal_url()
            })
            inputs.append({
                scl.NAME:
                self.DEPLOY_SIGNAL_VERB,
                scl.DESCRIPTION:
                _('HTTP verb to use for signaling '
                  'output values'),
                scl.TYPE:
                'String',
                'value':
                'PUT'
            })
        elif self._signal_transport_heat() or self._signal_transport_zaqar():
            creds = self._get_heat_signal_credentials()
            inputs.extend([{
                scl.NAME: self.DEPLOY_AUTH_URL,
                scl.DESCRIPTION: _('URL for API authentication'),
                scl.TYPE: 'String',
                'value': creds['auth_url']
            }, {
                scl.NAME:
                self.DEPLOY_USERNAME,
                scl.DESCRIPTION:
                _('Username for API authentication'),
                scl.TYPE:
                'String',
                'value':
                creds['username']
            }, {
                scl.NAME: self.DEPLOY_USER_ID,
                scl.DESCRIPTION: _('User ID for API authentication'),
                scl.TYPE: 'String',
                'value': creds['user_id']
            }, {
                scl.NAME:
                self.DEPLOY_PASSWORD,
                scl.DESCRIPTION:
                _('Password for API authentication'),
                scl.TYPE:
                'String',
                'value':
                creds['password']
            }, {
                scl.NAME:
                self.DEPLOY_PROJECT_ID,
                scl.DESCRIPTION:
                _('ID of project for API authentication'),
                scl.TYPE:
                'String',
                'value':
                creds['project_id']
            }])
        if self._signal_transport_zaqar():
            inputs.append({
                scl.NAME:
                self.DEPLOY_QUEUE_ID,
                scl.DESCRIPTION:
                _('ID of queue to use for signaling '
                  'output values'),
                scl.TYPE:
                'String',
                'value':
                self._get_zaqar_signal_queue_id()
            })

        return inputs

    def handle_create(self):
        return self._handle_action(self.CREATE)

    def check_create_complete(self, sd):
        if not sd:
            return True
        return self._check_complete()

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            self.properties = json_snippet.properties(self.properties_schema,
                                                      self.context)

        return self._handle_action(self.UPDATE)

    def check_update_complete(self, sd):
        if not sd:
            return True
        return self._check_complete()

    def handle_delete(self):
        try:
            return self._handle_action(self.DELETE)
        except Exception as ex:
            self.rpc_client().ignore_error_named(ex, 'NotFound')

    def check_delete_complete(self, sd=None):
        if not sd or self._check_complete():
            self._delete_resource()
            return True

    def _delete_resource(self):
        self._delete_signals()
        self._delete_user()

        derived_config_id = None
        if self.resource_id is not None:
            try:
                sd = self.rpc_client().show_software_deployment(
                    self.context, self.resource_id)
                derived_config_id = sd[rpc_api.SOFTWARE_DEPLOYMENT_CONFIG_ID]
                self.rpc_client().delete_software_deployment(
                    self.context, self.resource_id)
            except Exception as ex:
                self.rpc_client().ignore_error_named(ex, 'NotFound')

        if derived_config_id:
            self._delete_derived_config(derived_config_id)

    def handle_suspend(self):
        return self._handle_action(self.SUSPEND)

    def check_suspend_complete(self, sd):
        if not sd:
            return True
        return self._check_complete()

    def handle_resume(self):
        return self._handle_action(self.RESUME)

    def check_resume_complete(self, sd):
        if not sd:
            return True
        return self._check_complete()

    def handle_signal(self, details):
        return self.rpc_client().signal_software_deployment(
            self.context, self.resource_id, details,
            timeutils.utcnow().isoformat())

    def get_attribute(self, key, *path):
        """Resource attributes map to deployment outputs values."""
        sd = self.rpc_client().show_software_deployment(
            self.context, self.resource_id)
        ov = sd[rpc_api.SOFTWARE_DEPLOYMENT_OUTPUT_VALUES] or {}
        if key in ov:
            attribute = ov.get(key)
            return attributes.select_from_attribute(attribute, path)

        # Since there is no value for this key yet, check the output schemas
        # to find out if the key is valid
        sc = self.rpc_client().show_software_config(
            self.context, self.properties[self.CONFIG])
        outputs = sc[rpc_api.SOFTWARE_CONFIG_OUTPUTS] or []
        output_keys = [output['name'] for output in outputs]
        if key not in output_keys and key not in self.ATTRIBUTES:
            raise exception.InvalidTemplateAttribute(resource=self.name,
                                                     key=key)
        return None

    def validate(self):
        """Validate any of the provided params.

        :raises StackValidationFailed: if any property failed validation.
        """
        super(SoftwareDeployment, self).validate()
        server = self.properties[self.SERVER]
        if server:
            res = self.stack.resource_by_refid(server)
            if res:
                if not (res.properties.get('user_data_format')
                        == 'SOFTWARE_CONFIG'):
                    raise exception.StackValidationFailed(message=_(
                        "Resource %s's property user_data_format should be "
                        "set to SOFTWARE_CONFIG since there are software "
                        "deployments on it.") % server)
示例#22
0
class ResourceGroup(stack_resource.StackResource):
    """Creates one or more identically configured nested resources.

    In addition to the `refs` attribute, this resource implements synthetic
    attributes that mirror those of the resources in the group. When
    getting an attribute from this resource, however, a list of attribute
    values for each resource in the group is returned. To get attribute values
    for a single resource in the group, synthetic attributes of the form
    `resource.{resource index}.{attribute name}` can be used. The resource ID
    of a particular resource in the group can be obtained via the synthetic
    attribute `resource.{resource index}`. Note, that if you get attribute
    without `{resource index}`, e.g. `[resource, {attribute_name}]`, you'll get
    a list of this attribute's value for all resources in group.

    While each resource in the group will be identically configured, this
    resource does allow for some index-based customization of the properties
    of the resources in the group. For example::

      resources:
        my_indexed_group:
          type: OS::Heat::ResourceGroup
          properties:
            count: 3
            resource_def:
              type: OS::Nova::Server
              properties:
                # create a unique name for each server
                # using its index in the group
                name: my_server_%index%
                image: CentOS 6.5
                flavor: 4GB Performance

    would result in a group of three servers having the same image and flavor,
    but names of `my_server_0`, `my_server_1`, and `my_server_2`. The variable
    used for substitution can be customized by using the `index_var` property.
    """

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (
        COUNT,
        INDEX_VAR,
        RESOURCE_DEF,
        REMOVAL_POLICIES,
    ) = (
        'count',
        'index_var',
        'resource_def',
        'removal_policies',
    )

    _RESOURCE_DEF_KEYS = (
        RESOURCE_DEF_TYPE,
        RESOURCE_DEF_PROPERTIES,
        RESOURCE_DEF_METADATA,
    ) = (
        'type',
        'properties',
        'metadata',
    )

    _REMOVAL_POLICIES_KEYS = (REMOVAL_RSRC_LIST, ) = ('resource_list', )

    _ROLLING_UPDATES_SCHEMA_KEYS = (
        MIN_IN_SERVICE,
        MAX_BATCH_SIZE,
        PAUSE_TIME,
    ) = (
        'min_in_service',
        'max_batch_size',
        'pause_time',
    )

    _BATCH_CREATE_SCHEMA_KEYS = (
        MAX_BATCH_SIZE,
        PAUSE_TIME,
    ) = (
        'max_batch_size',
        'pause_time',
    )

    _UPDATE_POLICY_SCHEMA_KEYS = (
        ROLLING_UPDATE,
        BATCH_CREATE,
    ) = (
        'rolling_update',
        'batch_create',
    )

    ATTRIBUTES = (
        REFS,
        ATTR_ATTRIBUTES,
    ) = (
        'refs',
        'attributes',
    )

    properties_schema = {
        COUNT:
        properties.Schema(properties.Schema.INTEGER,
                          _('The number of resources to create.'),
                          default=1,
                          constraints=[
                              constraints.Range(min=0),
                          ],
                          update_allowed=True),
        INDEX_VAR:
        properties.Schema(
            properties.Schema.STRING,
            _('A variable that this resource will use to replace with the '
              'current index of a given resource in the group. Can be used, '
              'for example, to customize the name property of grouped '
              'servers in order to differentiate them when listed with '
              'nova client.'),
            default="%index%",
            constraints=[constraints.Length(min=3)],
            support_status=support.SupportStatus(version='2014.2')),
        RESOURCE_DEF:
        properties.Schema(
            properties.Schema.MAP,
            _('Resource definition for the resources in the group. The value '
              'of this property is the definition of a resource just as if '
              'it had been declared in the template itself.'),
            schema={
                RESOURCE_DEF_TYPE:
                properties.Schema(properties.Schema.STRING,
                                  _('The type of the resources in the group.'),
                                  required=True),
                RESOURCE_DEF_PROPERTIES:
                properties.Schema(
                    properties.Schema.MAP,
                    _('Property values for the resources in the group.')),
                RESOURCE_DEF_METADATA:
                properties.Schema(
                    properties.Schema.MAP,
                    _('Supplied metadata for the resources in the group.'),
                    support_status=support.SupportStatus(version='5.0.0')),
            },
            required=True,
            update_allowed=True),
        REMOVAL_POLICIES:
        properties.Schema(
            properties.Schema.LIST,
            _('Policies for removal of resources on update.'),
            schema=properties.Schema(
                properties.Schema.MAP,
                _('Policy to be processed when doing an update which '
                  'requires removal of specific resources.'),
                schema={
                    REMOVAL_RSRC_LIST:
                    properties.Schema(
                        properties.Schema.LIST,
                        _("List of resources to be removed "
                          "when doing an update which requires removal of "
                          "specific resources. "
                          "The resource may be specified several ways: "
                          "(1) The resource name, as in the nested stack, "
                          "(2) The resource reference returned from "
                          "get_resource in a template, as available via "
                          "the 'refs' attribute. "
                          "Note this is destructive on update when specified; "
                          "even if the count is not being reduced, and once "
                          "a resource name is removed, it's name is never "
                          "reused in subsequent updates."),
                        default=[]),
                },
            ),
            update_allowed=True,
            default=[],
            support_status=support.SupportStatus(version='2015.1')),
    }

    attributes_schema = {
        REFS:
        attributes.Schema(
            _("A list of resource IDs for the resources in the group."),
            type=attributes.Schema.LIST),
        ATTR_ATTRIBUTES:
        attributes.Schema(
            _("A map of resource names to the specified attribute of each "
              "individual resource. "
              "Requires heat_template_version: 2014-10-16."),
            support_status=support.SupportStatus(version='2014.2'),
            type=attributes.Schema.MAP),
    }

    rolling_update_schema = {
        MIN_IN_SERVICE:
        properties.Schema(properties.Schema.INTEGER,
                          _('The minimum number of resources in service while '
                            'rolling updates are being executed.'),
                          constraints=[constraints.Range(min=0)],
                          default=0),
        MAX_BATCH_SIZE:
        properties.Schema(
            properties.Schema.INTEGER,
            _('The maximum number of resources to replace at once.'),
            constraints=[constraints.Range(min=1)],
            default=1),
        PAUSE_TIME:
        properties.Schema(properties.Schema.NUMBER,
                          _('The number of seconds to wait between batches of '
                            'updates.'),
                          constraints=[constraints.Range(min=0)],
                          default=0),
    }

    batch_create_schema = {
        MAX_BATCH_SIZE:
        properties.Schema(
            properties.Schema.INTEGER,
            _('The maximum number of resources to create at once.'),
            constraints=[constraints.Range(min=1)],
            default=1),
        PAUSE_TIME:
        properties.Schema(properties.Schema.NUMBER,
                          _('The number of seconds to wait between batches.'),
                          constraints=[constraints.Range(min=0)],
                          default=0),
    }

    update_policy_schema = {
        ROLLING_UPDATE:
        properties.Schema(
            properties.Schema.MAP,
            schema=rolling_update_schema,
            support_status=support.SupportStatus(version='5.0.0')),
        BATCH_CREATE:
        properties.Schema(
            properties.Schema.MAP,
            schema=batch_create_schema,
            support_status=support.SupportStatus(version='5.0.0'))
    }

    def get_size(self):
        return self.properties.get(self.COUNT)

    def validate_nested_stack(self):
        # Only validate the resource definition (which may be a
        # nested template) if count is non-zero, to enable folks
        # to disable features via a zero count if they wish
        if not self.get_size():
            return

        test_tmpl = self._assemble_nested(["0"], include_all=True)
        res_def = next(
            six.itervalues(test_tmpl.resource_definitions(self.stack)))
        # make sure we can resolve the nested resource type
        self.stack.env.get_class_to_instantiate(res_def.resource_type)

        try:
            name = "%s-%s" % (self.stack.name, self.name)
            nested_stack = self._parse_nested_stack(name, test_tmpl,
                                                    self.child_params())
            nested_stack.strict_validate = False
            nested_stack.validate()
        except Exception as ex:
            msg = _("Failed to validate: %s") % six.text_type(ex)
            raise exception.StackValidationFailed(message=msg)

    def _name_blacklist(self):
        """Resolve the remove_policies to names for removal."""

        nested = self.nested()

        # To avoid reusing names after removal, we store a comma-separated
        # blacklist in the resource data
        db_rsrc_names = self.data().get('name_blacklist')
        if db_rsrc_names:
            current_blacklist = db_rsrc_names.split(',')
        else:
            current_blacklist = []

        # Now we iterate over the removal policies, and update the blacklist
        # with any additional names
        rsrc_names = set(current_blacklist)

        if nested:
            for r in self.properties[self.REMOVAL_POLICIES]:
                if self.REMOVAL_RSRC_LIST in r:
                    # Tolerate string or int list values
                    for n in r[self.REMOVAL_RSRC_LIST]:
                        str_n = six.text_type(n)
                        if str_n in nested:
                            rsrc_names.add(str_n)
                            continue
                        rsrc = nested.resource_by_refid(str_n)
                        if rsrc:
                            rsrc_names.add(rsrc.name)

        # If the blacklist has changed, update the resource data
        if rsrc_names != set(current_blacklist):
            self.data_set('name_blacklist', ','.join(rsrc_names))
        return rsrc_names

    def _resource_names(self, size=None):
        name_blacklist = self._name_blacklist()
        if size is None:
            size = self.get_size()

        def is_blacklisted(name):
            return name in name_blacklist

        candidates = six.moves.map(six.text_type, itertools.count())

        return itertools.islice(
            six.moves.filterfalse(is_blacklisted, candidates), size)

    def _count_black_listed(self):
        """Return the number of current resource names that are blacklisted."""
        existing_members = grouputils.get_member_names(self)
        return len(self._name_blacklist() & set(existing_members))

    def handle_create(self):
        if self.update_policy.get(self.BATCH_CREATE):
            batch_create = self.update_policy[self.BATCH_CREATE]
            max_batch_size = batch_create[self.MAX_BATCH_SIZE]
            pause_sec = batch_create[self.PAUSE_TIME]
            checkers = self._replace(0, max_batch_size, pause_sec)
            checkers[0].start()
            return checkers
        else:
            names = self._resource_names()
            self.create_with_template(self._assemble_nested(names),
                                      self.child_params(),
                                      self.stack.timeout_secs())

    def check_create_complete(self, checkers=None):
        if checkers is None:
            return super(ResourceGroup, self).check_create_complete()
        for checker in checkers:
            if not checker.started():
                checker.start()
            if not checker.step():
                return False
        return True

    def _run_to_completion(self, template, timeout):
        updater = self.update_with_template(template, {}, timeout)

        while not super(ResourceGroup, self).check_update_complete(updater):
            yield

    def _run_update(self, total_capacity, max_updates, timeout):
        template = self._assemble_for_rolling_update(total_capacity,
                                                     max_updates)
        return self._run_to_completion(template, timeout)

    def check_update_complete(self, checkers):
        for checker in checkers:
            if not checker.started():
                checker.start()
            if not checker.step():
                return False
        return True

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if tmpl_diff:
            # parse update policy
            if rsrc_defn.UPDATE_POLICY in tmpl_diff:
                up = json_snippet.update_policy(self.update_policy_schema,
                                                self.context)
                self.update_policy = up

        checkers = []
        self.properties = json_snippet.properties(self.properties_schema,
                                                  self.context)
        if prop_diff and self.RESOURCE_DEF in prop_diff:
            updaters = self._try_rolling_update()
            if updaters:
                checkers.extend(updaters)

        if not checkers:
            resizer = scheduler.TaskRunner(
                self._run_to_completion,
                self._assemble_nested(self._resource_names()),
                self.stack.timeout_mins)
            checkers.append(resizer)

        checkers[0].start()
        return checkers

    def get_attribute(self, key, *path):
        if key.startswith("resource."):
            return grouputils.get_nested_attrs(self, key, False, *path)

        names = self._resource_names()
        if key == self.REFS:
            vals = [grouputils.get_rsrc_id(self, key, False, n) for n in names]
            return attributes.select_from_attribute(vals, path)
        if key == self.ATTR_ATTRIBUTES:
            if not path:
                raise exception.InvalidTemplateAttribute(resource=self.name,
                                                         key=key)
            return dict(
                (n, grouputils.get_rsrc_attr(self, key, False, n, *path))
                for n in names)

        path = [key] + list(path)
        return [
            grouputils.get_rsrc_attr(self, key, False, n, *path) for n in names
        ]

    def build_resource_definition(self, res_name, res_defn):
        res_def = copy.deepcopy(res_defn)
        props = res_def.get(self.RESOURCE_DEF_PROPERTIES)
        if props:
            repl_props = self._handle_repl_val(res_name, props)
            res_def[self.RESOURCE_DEF_PROPERTIES] = repl_props
        return template.HOTemplate20130523.rsrc_defn_from_snippet(
            res_name, res_def)

    def get_resource_def(self, include_all=False):
        """Returns the resource definition portion of the group.

        :param include_all: if False, only properties for the resource
               definition that are not empty will be included
        :type include_all: bool
        :return: resource definition for the group
        :rtype: dict
        """

        # At this stage, we don't mind if all of the parameters have values
        # assigned. Pass in a custom resolver to the properties to not
        # error when a parameter does not have a user entered value.
        def ignore_param_resolve(snippet):
            while isinstance(snippet, function.Function):
                try:
                    snippet = snippet.result()
                except exception.UserParameterMissing:
                    return None

            if isinstance(snippet, collections.Mapping):
                return dict(
                    (k, ignore_param_resolve(v)) for k, v in snippet.items())
            elif (not isinstance(snippet, six.string_types)
                  and isinstance(snippet, collections.Iterable)):
                return [ignore_param_resolve(v) for v in snippet]

            return snippet

        self.properties.resolve = ignore_param_resolve

        res_def = self.properties[self.RESOURCE_DEF]
        if not include_all:
            return self._clean_props(res_def)
        return res_def

    def _clean_props(self, res_defn):
        res_def = copy.deepcopy(res_defn)
        props = res_def.get(self.RESOURCE_DEF_PROPERTIES)
        if props:
            clean = dict((k, v) for k, v in props.items() if v is not None)
            props = clean
            res_def[self.RESOURCE_DEF_PROPERTIES] = props
        return res_def

    def _handle_repl_val(self, res_name, val):
        repl_var = self.properties[self.INDEX_VAR]

        def recurse(x):
            return self._handle_repl_val(res_name, x)

        if isinstance(val, six.string_types):
            return val.replace(repl_var, res_name)
        elif isinstance(val, collections.Mapping):
            return {k: recurse(v) for k, v in val.items()}
        elif isinstance(val, collections.Sequence):
            return [recurse(v) for v in val]
        return val

    def _assemble_nested(self,
                         names,
                         include_all=False,
                         template_version=('heat_template_version',
                                           '2015-04-30')):

        def_dict = self.get_resource_def(include_all)
        definitions = [(k, self.build_resource_definition(k, def_dict))
                       for k in names]
        return scl_template.make_template(definitions,
                                          version=template_version)

    def _assemble_for_rolling_update(self,
                                     total_capacity,
                                     max_updates,
                                     include_all=False,
                                     template_version=('heat_template_version',
                                                       '2015-04-30')):
        names = list(self._resource_names(total_capacity))
        name_blacklist = self._name_blacklist()

        valid_resources = [(n, d)
                           for n, d in grouputils.get_member_definitions(self)
                           if n not in name_blacklist]

        targ_cap = self.get_size()

        def replace_priority(res_item):
            name, defn = res_item
            try:
                index = names.index(name)
            except ValueError:
                # High priority - delete immediately
                return 0
            else:
                if index < targ_cap:
                    # Update higher indices first
                    return targ_cap - index
                else:
                    # Low priority - don't update
                    return total_capacity

        old_resources = sorted(valid_resources, key=replace_priority)
        existing_names = set(n for n, d in valid_resources)
        new_names = six.moves.filterfalse(lambda n: n in existing_names, names)
        res_def = self.get_resource_def(include_all)
        definitions = scl_template.member_definitions(
            old_resources, res_def, total_capacity, max_updates,
            lambda: next(new_names), self.build_resource_definition)
        return scl_template.make_template(definitions,
                                          version=template_version)

    def _try_rolling_update(self):
        if self.update_policy[self.ROLLING_UPDATE]:
            policy = self.update_policy[self.ROLLING_UPDATE]
            return self._replace(policy[self.MIN_IN_SERVICE],
                                 policy[self.MAX_BATCH_SIZE],
                                 policy[self.PAUSE_TIME])

    def _update_timeout(self, batch_cnt, pause_sec):
        total_pause_time = pause_sec * max(batch_cnt - 1, 0)
        if total_pause_time >= self.stack.timeout_secs():
            msg = _('The current %s will result in stack update '
                    'timeout.') % rsrc_defn.UPDATE_POLICY
            raise ValueError(msg)
        return self.stack.timeout_secs() - total_pause_time

    @staticmethod
    def _get_batches(targ_cap, curr_cap, batch_size, min_in_service):
        updated = 0

        while rolling_update.needs_update(targ_cap, curr_cap, updated):
            new_cap, total_new = rolling_update.next_batch(
                targ_cap, curr_cap, updated, batch_size, min_in_service)

            yield new_cap, total_new

            updated += total_new - max(new_cap - max(curr_cap, targ_cap), 0)
            curr_cap = new_cap

    def _replace(self, min_in_service, batch_size, pause_sec):
        def pause_between_batch(pause_sec):
            duration = timeutils.Duration(pause_sec)
            while not duration.expired():
                yield

        # blacklist count existing
        num_blacklist = self._count_black_listed()

        # current capacity not including existing blacklisted
        curr_cap = len(self.nested()) - num_blacklist if self.nested() else 0

        batches = list(
            self._get_batches(self.get_size(), curr_cap, batch_size,
                              min_in_service))
        update_timeout = self._update_timeout(len(batches), pause_sec)

        def tasks():
            for index, (curr_cap, max_upd) in enumerate(batches):
                yield scheduler.TaskRunner(self._run_update, curr_cap, max_upd,
                                           update_timeout)

                if index < (len(batches) - 1) and pause_sec > 0:
                    yield scheduler.TaskRunner(pause_between_batch, pause_sec)

        return list(tasks())

    def child_template(self):
        names = self._resource_names()
        return self._assemble_nested(names)

    def child_params(self):
        return {}

    def handle_adopt(self, resource_data):
        names = self._resource_names()
        if names:
            return self.create_with_template(self._assemble_nested(names), {},
                                             adopt_data=resource_data)
示例#23
0
class PoolMember(neutron.NeutronResource):
    """A resource for managing LBaaS v2 Pool Members.

    A pool member represents a single backend node.
    """

    support_status = support.SupportStatus(version='6.0.0')

    required_service_extension = 'lbaasv2'

    PROPERTIES = (
        POOL, ADDRESS, PROTOCOL_PORT, WEIGHT, ADMIN_STATE_UP,
        SUBNET,
    ) = (
        'pool', 'address', 'protocol_port', 'weight', 'admin_state_up',
        'subnet'
    )

    ATTRIBUTES = (
        ADDRESS_ATTR, POOL_ID_ATTR
    ) = (
        'address', 'pool_id'
    )

    properties_schema = {
        POOL: properties.Schema(
            properties.Schema.STRING,
            _('Name or ID of the load balancing pool.'),
            required=True
        ),
        ADDRESS: properties.Schema(
            properties.Schema.STRING,
            _('IP address of the pool member on the pool network.'),
            required=True,
            constraints=[
                constraints.CustomConstraint('ip_addr')
            ]
        ),
        PROTOCOL_PORT: properties.Schema(
            properties.Schema.INTEGER,
            _('Port on which the pool member listens for requests or '
              'connections.'),
            required=True,
            constraints=[
                constraints.Range(1, 65535),
            ]
        ),
        WEIGHT: properties.Schema(
            properties.Schema.INTEGER,
            _('Weight of pool member in the pool (default to 1).'),
            default=1,
            constraints=[
                constraints.Range(0, 256),
            ],
            update_allowed=True
        ),
        ADMIN_STATE_UP: properties.Schema(
            properties.Schema.BOOLEAN,
            _('The administrative state of the pool member.'),
            default=True,
            update_allowed=True,
            constraints=[constraints.AllowedValues(['True'])]
        ),
        SUBNET: properties.Schema(
            properties.Schema.STRING,
            _('Subnet name or ID of this member.'),
            constraints=[
                constraints.CustomConstraint('neutron.subnet')
            ]
        ),
    }

    attributes_schema = {
        ADDRESS_ATTR: attributes.Schema(
            _('The IP address of the pool member.'),
            type=attributes.Schema.STRING
        ),
        POOL_ID_ATTR: attributes.Schema(
            _('The ID of the pool to which the pool member belongs.'),
            type=attributes.Schema.STRING
        )
    }

    def __init__(self, name, definition, stack):
        super(PoolMember, self).__init__(name, definition, stack)
        self._pool_id = None
        self._lb_id = None

    @property
    def pool_id(self):
        if self._pool_id is None:
            self._pool_id = self.client_plugin().find_resourceid_by_name_or_id(
                self.POOL,
                self.properties[self.POOL],
                cmd_resource='lbaas_pool')
        return self._pool_id

    @property
    def lb_id(self):
        if self._lb_id is None:
            pool = self.client().show_lbaas_pool(self.pool_id)['pool']

            listener_id = pool['listeners'][0]['id']
            listener = self.client().show_listener(listener_id)['listener']

            self._lb_id = listener['loadbalancers'][0]['id']
        return self._lb_id

    def _check_lb_status(self):
        return self.client_plugin().check_lb_status(self.lb_id)

    def handle_create(self):
        properties = self.prepare_properties(
            self.properties,
            self.physical_resource_name())

        self.client_plugin().resolve_pool(
            properties, self.POOL, 'pool_id')
        properties.pop('pool_id')

        if self.SUBNET in properties:
            self.client_plugin().resolve_subnet(
                properties, self.SUBNET, 'subnet_id')

        return properties

    def check_create_complete(self, properties):
        if self.resource_id is None:
            try:
                member = self.client().create_lbaas_member(
                    self.pool_id, {'member': properties})['member']
                self.resource_id_set(member['id'])
            except Exception as ex:
                if self.client_plugin().is_invalid(ex):
                    return False
                raise

        return self._check_lb_status()

    def _show_resource(self):
        member = self.client().show_lbaas_member(self.resource_id,
                                                 self.pool_id)
        return member['member']

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        self._update_called = False
        return prop_diff

    def check_update_complete(self, prop_diff):
        if not prop_diff:
            return True

        if not self._update_called:
            try:
                self.client().update_lbaas_member(self.resource_id,
                                                  self.pool_id,
                                                  {'member': prop_diff})
                self._update_called = True
            except Exception as ex:
                if self.client_plugin().is_invalid(ex):
                    return False
                raise

        return self._check_lb_status()

    def handle_delete(self):
        self._delete_called = False

    def check_delete_complete(self, data):
        if self.resource_id is None:
            return True

        if not self._delete_called:
            try:
                self.client().delete_lbaas_member(self.resource_id,
                                                  self.pool_id)
                self._delete_called = True
            except Exception as ex:
                if self.client_plugin().is_invalid(ex):
                    return False
                elif self.client_plugin().is_not_found(ex):
                    return True
                raise

        return self._check_lb_status()
示例#24
0
class RouterInterface(neutron.NeutronResource):
    """A resource for managing Neutron router interfaces.

    Router interfaces associate routers with existing subnets or ports.
    """

    required_service_extension = 'router'

    PROPERTIES = (ROUTER, ROUTER_ID, SUBNET_ID, SUBNET, PORT_ID,
                  PORT) = ('router', 'router_id', 'subnet_id', 'subnet',
                           'port_id', 'port')

    properties_schema = {
        ROUTER:
        properties.Schema(
            properties.Schema.STRING,
            _('The router.'),
            required=True,
            constraints=[constraints.CustomConstraint('neutron.router')],
        ),
        ROUTER_ID:
        properties.Schema(
            properties.Schema.STRING,
            _('ID of the router.'),
            support_status=support.SupportStatus(
                status=support.HIDDEN,
                version='6.0.0',
                previous_status=support.SupportStatus(
                    status=support.DEPRECATED,
                    message=_('Use property %s.') % ROUTER,
                    version='2015.1',
                    previous_status=support.SupportStatus(version='2013.1'))),
            constraints=[constraints.CustomConstraint('neutron.router')],
        ),
        SUBNET_ID:
        properties.Schema(
            properties.Schema.STRING,
            support_status=support.SupportStatus(
                status=support.HIDDEN,
                message=_('Use property %s.') % SUBNET,
                version='5.0.0',
                previous_status=support.SupportStatus(
                    status=support.DEPRECATED, version='2014.2')),
            constraints=[constraints.CustomConstraint('neutron.subnet')]),
        SUBNET:
        properties.Schema(
            properties.Schema.STRING,
            _('The subnet, either subnet or port should be '
              'specified.'),
            constraints=[constraints.CustomConstraint('neutron.subnet')]),
        PORT_ID:
        properties.Schema(
            properties.Schema.STRING,
            _('The port id, either subnet or port_id should be specified.'),
            support_status=support.SupportStatus(
                status=support.HIDDEN,
                version='6.0.0',
                previous_status=support.SupportStatus(
                    status=support.DEPRECATED,
                    message=_('Use property %s.') % PORT,
                    version='2015.1',
                    previous_status=support.SupportStatus(version='2014.1'))),
            constraints=[constraints.CustomConstraint('neutron.port')]),
        PORT:
        properties.Schema(
            properties.Schema.STRING,
            _('The port, either subnet or port should be specified.'),
            support_status=support.SupportStatus(version='2015.1'),
            constraints=[constraints.CustomConstraint('neutron.port')])
    }

    def translation_rules(self, props):
        return [
            translation.TranslationRule(props,
                                        translation.TranslationRule.REPLACE,
                                        [self.PORT],
                                        value_path=[self.PORT_ID]),
            translation.TranslationRule(props,
                                        translation.TranslationRule.REPLACE,
                                        [self.ROUTER],
                                        value_path=[self.ROUTER_ID]),
            translation.TranslationRule(props,
                                        translation.TranslationRule.REPLACE,
                                        [self.SUBNET],
                                        value_path=[self.SUBNET_ID]),
            translation.TranslationRule(props,
                                        translation.TranslationRule.RESOLVE,
                                        [self.PORT],
                                        client_plugin=self.client_plugin(),
                                        finder='find_resourceid_by_name_or_id',
                                        entity='port'),
            translation.TranslationRule(props,
                                        translation.TranslationRule.RESOLVE,
                                        [self.ROUTER],
                                        client_plugin=self.client_plugin(),
                                        finder='find_resourceid_by_name_or_id',
                                        entity='router'),
            translation.TranslationRule(props,
                                        translation.TranslationRule.RESOLVE,
                                        [self.SUBNET],
                                        client_plugin=self.client_plugin(),
                                        finder='find_resourceid_by_name_or_id',
                                        entity='subnet')
        ]

    def validate(self):
        """Validate any of the provided params."""
        super(RouterInterface, self).validate()

        prop_subnet_exists = self.properties.get(self.SUBNET) is not None

        prop_port_exists = self.properties.get(self.PORT) is not None

        if prop_subnet_exists and prop_port_exists:
            raise exception.ResourcePropertyConflict(self.SUBNET, self.PORT)

        if not prop_subnet_exists and not prop_port_exists:
            raise exception.PropertyUnspecifiedError(self.SUBNET, self.PORT)

    def handle_create(self):
        router_id = dict(self.properties).get(self.ROUTER)
        key = 'subnet_id'
        value = dict(self.properties).get(self.SUBNET)
        if not value:
            key = 'port_id'
            value = dict(self.properties).get(self.PORT)
        self.client().add_interface_router(router_id, {key: value})
        self.resource_id_set('%s:%s=%s' % (router_id, key, value))

    def handle_delete(self):
        if not self.resource_id:
            return
        tokens = self.resource_id.replace('=', ':').split(':')
        if len(tokens) == 2:  # compatible with old data
            tokens.insert(1, 'subnet_id')
        (router_id, key, value) = tokens
        with self.client_plugin().ignore_not_found:
            self.client().remove_interface_router(router_id, {key: value})
示例#25
0
class RouterGateway(neutron.NeutronResource):

    support_status = support.SupportStatus(
        status=support.HIDDEN,
        message=_('Use the `external_gateway_info` property in '
                  'the router resource to set up the gateway.'),
        version='5.0.0',
        previous_status=support.SupportStatus(status=support.DEPRECATED,
                                              version='2014.1'))

    PROPERTIES = (
        ROUTER_ID,
        NETWORK_ID,
        NETWORK,
    ) = ('router_id', 'network_id', 'network')

    properties_schema = {
        ROUTER_ID:
        properties.Schema(
            properties.Schema.STRING,
            _('ID of the router.'),
            required=True,
            constraints=[constraints.CustomConstraint('neutron.router')]),
        NETWORK_ID:
        properties.Schema(
            properties.Schema.STRING,
            support_status=support.SupportStatus(
                status=support.DEPRECATED,
                message=_('Use property %s.') % NETWORK,
                version='2014.2'),
            constraints=[constraints.CustomConstraint('neutron.network')],
        ),
        NETWORK:
        properties.Schema(
            properties.Schema.STRING,
            _('external network for the gateway.'),
            constraints=[constraints.CustomConstraint('neutron.network')],
        ),
    }

    def translation_rules(self, props):
        return [
            translation.TranslationRule(props,
                                        translation.TranslationRule.REPLACE,
                                        [self.NETWORK],
                                        value_path=[self.NETWORK_ID]),
            translation.TranslationRule(props,
                                        translation.TranslationRule.RESOLVE,
                                        [self.NETWORK],
                                        client_plugin=self.client_plugin(),
                                        finder='find_resourceid_by_name_or_id',
                                        entity='network')
        ]

    def add_dependencies(self, deps):
        super(RouterGateway, self).add_dependencies(deps)
        for resource in six.itervalues(self.stack):
            # depend on any RouterInterface in this template with the same
            # router_id as this router_id
            if resource.has_interface('OS::Neutron::RouterInterface'):
                # Since RouterInterface translates router_id property to
                # router, we should correctly resolve it for RouterGateway.
                dep_router_id = self.client_plugin().resolve_router(
                    {
                        RouterInterface.ROUTER:
                        resource.properties.get(RouterInterface.ROUTER),
                        RouterInterface.ROUTER_ID:
                        None
                    }, RouterInterface.ROUTER, RouterInterface.ROUTER_ID)
                router_id = self.properties[self.ROUTER_ID]
                if dep_router_id == router_id:
                    deps += (self, resource)
            # depend on any subnet in this template with the same network_id
            # as this network_id, as the gateway implicitly creates a port
            # on that subnet
            if resource.has_interface('OS::Neutron::Subnet'):
                dep_network = resource.properties.get(subnet.Subnet.NETWORK)
                network = self.properties[self.NETWORK]
                if dep_network == network:
                    deps += (self, resource)

    def handle_create(self):
        router_id = self.properties[self.ROUTER_ID]
        network_id = dict(self.properties).get(self.NETWORK)
        self.client().add_gateway_router(router_id, {'network_id': network_id})
        self.resource_id_set('%s:%s' % (router_id, network_id))

    def handle_delete(self):
        if not self.resource_id:
            return

        (router_id, network_id) = self.resource_id.split(':')
        with self.client_plugin().ignore_not_found:
            self.client().remove_gateway_router(router_id)
示例#26
0
class RandomString(resource.Resource):
    """A resource which generates a random string.

    This is useful for configuring passwords and secrets on services. Random
    string can be generated from specified character sequences, which means
    that all characters will be randomly chosen from specified sequences, or
    with some classes, e.g. letterdigits, which means that all character will
    be randomly chosen from union of ascii letters and digits. Output string
    will be randomly generated string with specified length (or with length of
    32, if length property doesn't specified).
    """

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (
        LENGTH,
        SEQUENCE,
        CHARACTER_CLASSES,
        CHARACTER_SEQUENCES,
        SALT,
    ) = (
        'length',
        'sequence',
        'character_classes',
        'character_sequences',
        'salt',
    )

    _CHARACTER_CLASSES_KEYS = (
        CHARACTER_CLASSES_CLASS,
        CHARACTER_CLASSES_MIN,
    ) = (
        'class',
        'min',
    )

    _CHARACTER_SEQUENCES = (
        CHARACTER_SEQUENCES_SEQUENCE,
        CHARACTER_SEQUENCES_MIN,
    ) = (
        'sequence',
        'min',
    )

    ATTRIBUTES = (VALUE, ) = ('value', )

    properties_schema = {
        LENGTH:
        properties.Schema(properties.Schema.INTEGER,
                          _('Length of the string to generate.'),
                          default=32,
                          constraints=[
                              constraints.Range(1, 512),
                          ]),
        SEQUENCE:
        properties.Schema(
            properties.Schema.STRING,
            _('Sequence of characters to build the random string from.'),
            constraints=[
                constraints.AllowedValues([
                    'lettersdigits', 'letters', 'lowercase', 'uppercase',
                    'digits', 'hexdigits', 'octdigits'
                ]),
            ],
            support_status=support.SupportStatus(
                status=support.HIDDEN,
                version='5.0.0',
                previous_status=support.SupportStatus(
                    status=support.DEPRECATED,
                    message=_('Use property %s.') % CHARACTER_CLASSES,
                    version='2014.2'))),
        CHARACTER_CLASSES:
        properties.Schema(
            properties.Schema.LIST,
            _('A list of character class and their constraints to generate '
              'the random string from.'),
            schema=properties.Schema(
                properties.Schema.MAP,
                schema={
                    CHARACTER_CLASSES_CLASS:
                    properties.Schema(
                        properties.Schema.STRING,
                        (_('A character class and its corresponding %(min)s '
                           'constraint to generate the random string from.') %
                         {
                             'min': CHARACTER_CLASSES_MIN
                         }),
                        constraints=[
                            constraints.AllowedValues([
                                'lettersdigits', 'letters', 'lowercase',
                                'uppercase', 'digits', 'hexdigits', 'octdigits'
                            ]),
                        ],
                        default='lettersdigits'),
                    CHARACTER_CLASSES_MIN:
                    properties.Schema(
                        properties.Schema.INTEGER,
                        _('The minimum number of characters from this '
                          'character class that will be in the generated '
                          'string.'),
                        default=1,
                        constraints=[
                            constraints.Range(1, 512),
                        ])
                }),
            # add defaults for backward compatibility
            default=[{
                CHARACTER_CLASSES_CLASS: 'lettersdigits',
                CHARACTER_CLASSES_MIN: 1
            }]),
        CHARACTER_SEQUENCES:
        properties.Schema(
            properties.Schema.LIST,
            _('A list of character sequences and their constraints to '
              'generate the random string from.'),
            schema=properties.Schema(
                properties.Schema.MAP,
                schema={
                    CHARACTER_SEQUENCES_SEQUENCE:
                    properties.Schema(
                        properties.Schema.STRING,
                        _('A character sequence and its corresponding %(min)s '
                          'constraint to generate the random string '
                          'from.') % {'min': CHARACTER_SEQUENCES_MIN},
                        required=True),
                    CHARACTER_SEQUENCES_MIN:
                    properties.Schema(
                        properties.Schema.INTEGER,
                        _('The minimum number of characters from this '
                          'sequence that will be in the generated '
                          'string.'),
                        default=1,
                        constraints=[
                            constraints.Range(1, 512),
                        ])
                })),
        SALT:
        properties.Schema(
            properties.Schema.STRING,
            _('Value which can be set or changed on stack update to trigger '
              'the resource for replacement with a new random string. The '
              'salt value itself is ignored by the random generator.')),
    }

    attributes_schema = {
        VALUE:
        attributes.Schema(_(
            'The random string generated by this resource. This value is '
            'also available by referencing the resource.'),
                          cache_mode=attributes.Schema.CACHE_NONE,
                          type=attributes.Schema.STRING),
    }

    _sequences = {
        'lettersdigits': string.ascii_letters + string.digits,
        'letters': string.ascii_letters,
        'lowercase': string.ascii_lowercase,
        'uppercase': string.ascii_uppercase,
        'digits': string.digits,
        'hexdigits': string.digits + 'ABCDEF',
        'octdigits': string.octdigits
    }

    def translation_rules(self, props):
        if props.get(self.SEQUENCE):
            return [
                translation.TranslationRule(
                    props, translation.TranslationRule.ADD,
                    [self.CHARACTER_CLASSES],
                    [{
                        self.CHARACTER_CLASSES_CLASS: props.get(self.SEQUENCE),
                        self.CHARACTER_CLASSES_MIN: 1
                    }]),
                translation.TranslationRule(props,
                                            translation.TranslationRule.DELETE,
                                            [self.SEQUENCE])
            ]

    def _generate_random_string(self, char_sequences, char_classes, length):
        random_string = ""

        # Add the minimum number of chars from each char sequence & char class
        if char_sequences:
            for char_seq in char_sequences:
                seq = char_seq[self.CHARACTER_SEQUENCES_SEQUENCE]
                seq_min = char_seq[self.CHARACTER_SEQUENCES_MIN]
                for i in six.moves.xrange(seq_min):
                    random_string += random.choice(seq)

        if char_classes:
            for char_class in char_classes:
                cclass_class = char_class[self.CHARACTER_CLASSES_CLASS]
                cclass_seq = self._sequences[cclass_class]
                cclass_min = char_class[self.CHARACTER_CLASSES_MIN]
                for i in six.moves.xrange(cclass_min):
                    random_string += random.choice(cclass_seq)

        def random_class_char():
            cclass_dict = random.choice(char_classes)
            cclass_class = cclass_dict[self.CHARACTER_CLASSES_CLASS]
            cclass_seq = self._sequences[cclass_class]
            return random.choice(cclass_seq)

        def random_seq_char():
            seq_dict = random.choice(char_sequences)
            seq = seq_dict[self.CHARACTER_SEQUENCES_SEQUENCE]
            return random.choice(seq)

        # Fill up rest with random chars from provided sequences & classes
        if char_sequences and char_classes:
            weighted_choices = ([True] * len(char_classes) +
                                [False] * len(char_sequences))
            while len(random_string) < length:
                if random.choice(weighted_choices):
                    random_string += random_class_char()
                else:
                    random_string += random_seq_char()

        elif char_sequences:
            while len(random_string) < length:
                random_string += random_seq_char()

        else:
            while len(random_string) < length:
                random_string += random_class_char()

        # Randomize string
        random_string = ''.join(
            random.sample(random_string, len(random_string)))
        return random_string

    def validate(self):
        super(RandomString, self).validate()
        char_sequences = self.properties[self.CHARACTER_SEQUENCES]
        char_classes = self.properties[self.CHARACTER_CLASSES]

        def char_min(char_dicts, min_prop):
            if char_dicts:
                return sum(char_dict[min_prop] for char_dict in char_dicts)
            return 0

        length = self.properties[self.LENGTH]
        min_length = (char_min(char_sequences, self.CHARACTER_SEQUENCES_MIN) +
                      char_min(char_classes, self.CHARACTER_CLASSES_MIN))
        if min_length > length:
            msg = _("Length property cannot be smaller than combined "
                    "character class and character sequence minimums")
            raise exception.StackValidationFailed(message=msg)

    def handle_create(self):
        char_sequences = self.properties[self.CHARACTER_SEQUENCES]
        char_classes = self.properties[self.CHARACTER_CLASSES]
        length = self.properties[self.LENGTH]

        random_string = self._generate_random_string(char_sequences,
                                                     char_classes, length)
        self.data_set('value', random_string, redact=True)
        self.resource_id_set(self.physical_resource_name())

    def _resolve_attribute(self, name):
        if name == self.VALUE:
            return self.data().get(self.VALUE)

    def get_reference_id(self):
        if self.resource_id is not None:
            return self.data().get('value')
        else:
            return six.text_type(self.name)
示例#27
0
class RBACPolicy(neutron.NeutronResource):
    """A Resource for managing RBAC policy in Neutron.

    This resource creates and manages Neutron RBAC policy,
    which allows to share Neutron networks to subsets of tenants.
    """

    support_status = support.SupportStatus(version='6.0.0')

    required_service_extension = 'rbac-policies'

    PROPERTIES = (OBJECT_TYPE, TARGET_TENANT, ACTION, OBJECT_ID,
                  TENANT_ID) = ('object_type', 'target_tenant', 'action',
                                'object_id', 'tenant_id')

    # Change it when neutron supports more function in the future.
    SUPPORTED_TYPES_ACTIONS = {'network': ['access_as_shared']}

    properties_schema = {
        OBJECT_TYPE:
        properties.Schema(
            properties.Schema.STRING,
            _('Type of the object that RBAC policy affects.'),
            required=True,
        ),
        TARGET_TENANT:
        properties.Schema(
            properties.Schema.STRING,
            _('ID of the tenant to which the RBAC policy will be enforced.'),
            required=True,
            update_allowed=True),
        ACTION:
        properties.Schema(
            properties.Schema.STRING,
            _('Action for the RBAC policy.'),
            required=True,
        ),
        OBJECT_ID:
        properties.Schema(properties.Schema.STRING,
                          _('ID or name of the RBAC object.'),
                          required=True),
        TENANT_ID:
        properties.Schema(
            properties.Schema.STRING,
            _('The owner tenant ID. Only required if the caller has an '
              'administrative role and wants to create a RBAC for another '
              'tenant.'))
    }

    def prepare_properties(self, properties, name):
        props = super(RBACPolicy, self).prepare_properties(properties, name)

        obj_type = props.get(self.OBJECT_TYPE)
        obj_id_or_name = props.get(self.OBJECT_ID)
        obj_id = neutronV20.find_resourceid_by_name_or_id(
            self.client(), obj_type, obj_id_or_name)
        props['object_id'] = obj_id
        return props

    def handle_create(self):
        props = self.prepare_properties(self.properties,
                                        self.physical_resource_name())
        rbac = self.client().create_rbac_policy({'rbac_policy':
                                                 props})['rbac_policy']
        self.resource_id_set(rbac['id'])

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            self.client().update_rbac_policy(self.resource_id,
                                             {'rbac_policy': prop_diff})

    def handle_delete(self):
        if self.resource_id is not None:
            with self.client_plugin().ignore_not_found:
                self.client().delete_rbac_policy(self.resource_id)

    def _show_resource(self):
        return self.client().show_rbac_policy(self.resource_id)['rbac_policy']

    def validate(self):
        """Validate the provided params."""
        super(RBACPolicy, self).validate()

        action = self.properties[self.ACTION]
        obj_type = self.properties[self.OBJECT_TYPE]
        obj_id_or_name = self.properties[self.OBJECT_ID]

        # Validate obj_type and action per SUPPORTED_TYPES_ACTIONS.
        if obj_type not in self.SUPPORTED_TYPES_ACTIONS:
            msg = (_("Invalid object_type: %(obj_type)s. "
                     "Valid object_type :%(value)s") % {
                         'obj_type': obj_type,
                         'value': self.SUPPORTED_TYPES_ACTIONS.keys()
                     })
            raise exception.StackValidationFailed(message=msg)
        if action not in self.SUPPORTED_TYPES_ACTIONS[obj_type]:
            msg = (_("Invalid action %(action)s for object type "
                     "%(obj_type)s. Valid actions :%(value)s") % {
                         'action': action,
                         'obj_type': obj_type,
                         'value': self.SUPPORTED_TYPES_ACTIONS[obj_type]
                     })
            raise exception.StackValidationFailed(message=msg)

        # Make sure the value of object_id is correct.
        neutronV20.find_resourceid_by_name_or_id(self.client(), obj_type,
                                                 obj_id_or_name)
示例#28
0
class Router(neutron.NeutronResource):
    """A resource that implements Neutron router.

    Router is a physical or virtual network device that passes network traffic
    between different networks.
    """

    required_service_extension = 'router'

    PROPERTIES = (
        NAME,
        EXTERNAL_GATEWAY,
        VALUE_SPECS,
        ADMIN_STATE_UP,
        L3_AGENT_ID,
        L3_AGENT_IDS,
        DISTRIBUTED,
        HA,
    ) = ('name', 'external_gateway_info', 'value_specs', 'admin_state_up',
         'l3_agent_id', 'l3_agent_ids', 'distributed', 'ha')

    _EXTERNAL_GATEWAY_KEYS = (
        EXTERNAL_GATEWAY_NETWORK,
        EXTERNAL_GATEWAY_ENABLE_SNAT,
        EXTERNAL_GATEWAY_FIXED_IPS,
    ) = (
        'network',
        'enable_snat',
        'external_fixed_ips',
    )

    _EXTERNAL_GATEWAY_FIXED_IPS_KEYS = (IP_ADDRESS, SUBNET) = ('ip_address',
                                                               'subnet')

    ATTRIBUTES = (
        STATUS,
        EXTERNAL_GATEWAY_INFO_ATTR,
        NAME_ATTR,
        ADMIN_STATE_UP_ATTR,
        TENANT_ID,
    ) = (
        'status',
        'external_gateway_info',
        'name',
        'admin_state_up',
        'tenant_id',
    )

    properties_schema = {
        NAME:
        properties.Schema(properties.Schema.STRING,
                          _('The name of the router.'),
                          update_allowed=True),
        EXTERNAL_GATEWAY:
        properties.Schema(
            properties.Schema.MAP,
            _('External network gateway configuration for a router.'),
            schema={
                EXTERNAL_GATEWAY_NETWORK:
                properties.Schema(
                    properties.Schema.STRING,
                    _('ID or name of the external network for the gateway.'),
                    required=True,
                    update_allowed=True),
                EXTERNAL_GATEWAY_ENABLE_SNAT:
                properties.Schema(
                    properties.Schema.BOOLEAN,
                    _('Enables Source NAT on the router gateway. NOTE: The '
                      'default policy setting in Neutron restricts usage of '
                      'this property to administrative users only.'),
                    update_allowed=True),
                EXTERNAL_GATEWAY_FIXED_IPS:
                properties.Schema(
                    properties.Schema.LIST,
                    _('External fixed IP addresses for the gateway.'),
                    schema=properties.Schema(
                        properties.Schema.MAP,
                        schema={
                            IP_ADDRESS:
                            properties.Schema(
                                properties.Schema.STRING,
                                _('External fixed IP address.'),
                                constraints=[
                                    constraints.CustomConstraint('ip_addr'),
                                ]),
                            SUBNET:
                            properties.Schema(
                                properties.Schema.STRING,
                                _('Subnet of external fixed IP address.'),
                                constraints=[
                                    constraints.CustomConstraint(
                                        'neutron.subnet')
                                ]),
                        }),
                    update_allowed=True,
                    support_status=support.SupportStatus(version='6.0.0')),
            },
            update_allowed=True),
        VALUE_SPECS:
        properties.Schema(
            properties.Schema.MAP,
            _('Extra parameters to include in the creation request.'),
            default={},
            update_allowed=True),
        ADMIN_STATE_UP:
        properties.Schema(properties.Schema.BOOLEAN,
                          _('The administrative state of the router.'),
                          default=True,
                          update_allowed=True),
        L3_AGENT_ID:
        properties.Schema(
            properties.Schema.STRING,
            _('ID of the L3 agent. NOTE: The default policy setting in '
              'Neutron restricts usage of this property to administrative '
              'users only.'),
            update_allowed=True,
            support_status=support.SupportStatus(
                status=support.HIDDEN,
                version='6.0.0',
                previous_status=support.SupportStatus(
                    status=support.DEPRECATED,
                    version='2015.1',
                    message=_('Use property %s.') % L3_AGENT_IDS,
                    previous_status=support.SupportStatus(version='2014.1'))),
        ),
        L3_AGENT_IDS:
        properties.Schema(
            properties.Schema.LIST,
            _('ID list of the L3 agent. User can specify multi-agents '
              'for highly available router. NOTE: The default policy '
              'setting in Neutron restricts usage of this property to '
              'administrative users only.'),
            schema=properties.Schema(properties.Schema.STRING, ),
            update_allowed=True,
            support_status=support.SupportStatus(version='2015.1')),
        DISTRIBUTED:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('Indicates whether or not to create a distributed router. '
              'NOTE: The default policy setting in Neutron restricts usage '
              'of this property to administrative users only. This property '
              'can not be used in conjunction with the L3 agent ID.'),
            support_status=support.SupportStatus(version='2015.1')),
        HA:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('Indicates whether or not to create a highly available router. '
              'NOTE: The default policy setting in Neutron restricts usage '
              'of this property to administrative users only. And now neutron '
              'do not support distributed and ha at the same time.'),
            support_status=support.SupportStatus(version='2015.1')),
    }

    attributes_schema = {
        STATUS:
        attributes.Schema(_("The status of the router."),
                          type=attributes.Schema.STRING),
        EXTERNAL_GATEWAY_INFO_ATTR:
        attributes.Schema(_("Gateway network for the router."),
                          type=attributes.Schema.MAP),
        NAME_ATTR:
        attributes.Schema(_("Friendly name of the router."),
                          type=attributes.Schema.STRING),
        ADMIN_STATE_UP_ATTR:
        attributes.Schema(_("Administrative state of the router."),
                          type=attributes.Schema.STRING),
        TENANT_ID:
        attributes.Schema(_("Tenant owning the router."),
                          type=attributes.Schema.STRING),
    }

    def translation_rules(self, props):
        rules = [
            translation.TranslationRule(
                props,
                translation.TranslationRule.RESOLVE,
                [self.EXTERNAL_GATEWAY, self.EXTERNAL_GATEWAY_NETWORK],
                client_plugin=self.client_plugin(),
                finder='find_resourceid_by_name_or_id',
                entity='network'),
            translation.TranslationRule(
                props,
                translation.TranslationRule.RESOLVE, [
                    self.EXTERNAL_GATEWAY, self.EXTERNAL_GATEWAY_FIXED_IPS,
                    self.SUBNET
                ],
                client_plugin=self.client_plugin(),
                finder='find_resourceid_by_name_or_id',
                entity='subnet')
        ]
        if props.get(self.L3_AGENT_ID):
            rules.extend([
                translation.TranslationRule(props,
                                            translation.TranslationRule.ADD,
                                            [self.L3_AGENT_IDS],
                                            [props.get(self.L3_AGENT_ID)]),
                translation.TranslationRule(props,
                                            translation.TranslationRule.DELETE,
                                            [self.L3_AGENT_ID])
            ])
        return rules

    def validate(self):
        super(Router, self).validate()
        is_distributed = self.properties[self.DISTRIBUTED]
        l3_agent_id = self.properties[self.L3_AGENT_ID]
        l3_agent_ids = self.properties[self.L3_AGENT_IDS]
        is_ha = self.properties[self.HA]
        if l3_agent_id and l3_agent_ids:
            raise exception.ResourcePropertyConflict(self.L3_AGENT_ID,
                                                     self.L3_AGENT_IDS)
        # do not specific l3 agent when creating a distributed router
        if is_distributed and (l3_agent_id or l3_agent_ids):
            raise exception.ResourcePropertyConflict(
                self.DISTRIBUTED,
                "/".join([self.L3_AGENT_ID, self.L3_AGENT_IDS]))
        if is_ha and is_distributed:
            raise exception.ResourcePropertyConflict(self.DISTRIBUTED, self.HA)
        if not is_ha and l3_agent_ids and len(l3_agent_ids) > 1:
            msg = _('Non HA routers can only have one L3 agent.')
            raise exception.StackValidationFailed(message=msg)

    def add_dependencies(self, deps):
        super(Router, self).add_dependencies(deps)
        external_gw = self.properties[self.EXTERNAL_GATEWAY]
        if external_gw:
            external_gw_net = external_gw.get(self.EXTERNAL_GATEWAY_NETWORK)
            for res in six.itervalues(self.stack):
                if res.has_interface('OS::Neutron::Subnet'):
                    subnet_net = res.properties.get(subnet.Subnet.NETWORK)
                    if subnet_net == external_gw_net:
                        deps += (self, res)

    def _resolve_gateway(self, props):
        gateway = props.get(self.EXTERNAL_GATEWAY)
        if gateway:
            gateway['network_id'] = gateway.pop(self.EXTERNAL_GATEWAY_NETWORK)
            if gateway[self.EXTERNAL_GATEWAY_ENABLE_SNAT] is None:
                del gateway[self.EXTERNAL_GATEWAY_ENABLE_SNAT]
            if gateway[self.EXTERNAL_GATEWAY_FIXED_IPS] is None:
                del gateway[self.EXTERNAL_GATEWAY_FIXED_IPS]
            else:
                self._resolve_subnet(gateway)
        return props

    def _get_l3_agent_list(self, props):
        l3_agent_id = props.pop(self.L3_AGENT_ID, None)
        l3_agent_ids = props.pop(self.L3_AGENT_IDS, None)
        if not l3_agent_ids and l3_agent_id:
            l3_agent_ids = [l3_agent_id]

        return l3_agent_ids

    def _resolve_subnet(self, gateway):
        external_gw_fixed_ips = gateway[self.EXTERNAL_GATEWAY_FIXED_IPS]
        for fixed_ip in external_gw_fixed_ips:
            for key, value in six.iteritems(fixed_ip):
                if value is None:
                    fixed_ip.pop(key)
            if self.SUBNET in fixed_ip:
                fixed_ip['subnet_id'] = fixed_ip.pop(self.SUBNET)

    def handle_create(self):
        props = self.prepare_properties(self.properties,
                                        self.physical_resource_name())
        self._resolve_gateway(props)
        l3_agent_ids = self._get_l3_agent_list(props)

        router = self.client().create_router({'router': props})['router']
        self.resource_id_set(router['id'])

        if l3_agent_ids:
            self._replace_agent(l3_agent_ids)

    def _show_resource(self):
        return self.client().show_router(self.resource_id)['router']

    def check_create_complete(self, *args):
        attributes = self._show_resource()
        return self.is_built(attributes)

    def handle_delete(self):
        if not self.resource_id:
            return

        try:
            self.client().delete_router(self.resource_id)
        except Exception as ex:
            self.client_plugin().ignore_not_found(ex)
        else:
            return True

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if self.EXTERNAL_GATEWAY in prop_diff:
            self._resolve_gateway(prop_diff)

        if self.L3_AGENT_IDS in prop_diff or self.L3_AGENT_ID in prop_diff:
            l3_agent_ids = self._get_l3_agent_list(prop_diff)
            self._replace_agent(l3_agent_ids)

        if prop_diff:
            self.prepare_update_properties(prop_diff)
            self.client().update_router(self.resource_id,
                                        {'router': prop_diff})

    def _replace_agent(self, l3_agent_ids=None):
        ret = self.client().list_l3_agent_hosting_routers(self.resource_id)
        for agent in ret['agents']:
            self.client().remove_router_from_l3_agent(agent['id'],
                                                      self.resource_id)
        if l3_agent_ids:
            for l3_agent_id in l3_agent_ids:
                self.client().add_router_to_l3_agent(
                    l3_agent_id, {'router_id': self.resource_id})
示例#29
0
class SoftwareDeploymentGroup(resource_group.ResourceGroup):
    """This resource associates a group of servers with some configuration.

    The configuration is to be deployed to all servers in the group.

    The properties work in a similar way to OS::Heat::SoftwareDeployment,
    and in addition to the attributes documented, you may pass any
    attribute supported by OS::Heat::SoftwareDeployment, including those
    exposing arbitrary outputs, and return a map of deployment names to
    the specified attribute.
    """

    support_status = support.SupportStatus(version='5.0.0')

    PROPERTIES = (
        SERVERS,
        CONFIG,
        INPUT_VALUES,
        DEPLOY_ACTIONS,
        NAME,
        SIGNAL_TRANSPORT,
    ) = (
        'servers',
        SoftwareDeployment.CONFIG,
        SoftwareDeployment.INPUT_VALUES,
        SoftwareDeployment.DEPLOY_ACTIONS,
        SoftwareDeployment.NAME,
        SoftwareDeployment.SIGNAL_TRANSPORT,
    )

    ATTRIBUTES = (STDOUTS, STDERRS,
                  STATUS_CODES) = ('deploy_stdouts', 'deploy_stderrs',
                                   'deploy_status_codes')

    _sd_ps = SoftwareDeployment.properties_schema
    _rg_ps = resource_group.ResourceGroup.properties_schema

    properties_schema = {
        SERVERS:
        properties.Schema(
            properties.Schema.MAP,
            _('A map of Nova names and IDs to apply configuration to.'),
            update_allowed=True),
        CONFIG:
        _sd_ps[CONFIG],
        INPUT_VALUES:
        _sd_ps[INPUT_VALUES],
        DEPLOY_ACTIONS:
        _sd_ps[DEPLOY_ACTIONS],
        NAME:
        _sd_ps[NAME],
        SIGNAL_TRANSPORT:
        _sd_ps[SIGNAL_TRANSPORT]
    }

    attributes_schema = {
        STDOUTS:
        attributes.Schema(_(
            "A map of Nova names and captured stdouts from the "
            "configuration execution to each server."),
                          type=attributes.Schema.MAP),
        STDERRS:
        attributes.Schema(_(
            "A map of Nova names and captured stderrs from the "
            "configuration execution to each server."),
                          type=attributes.Schema.MAP),
        STATUS_CODES:
        attributes.Schema(_(
            "A map of Nova names and returned status code from the "
            "configuration execution."),
                          type=attributes.Schema.MAP),
    }

    update_policy_schema = {}

    def get_size(self):
        return len(self.properties.get(self.SERVERS, {}))

    def _resource_names(self):
        return six.iterkeys(self.properties.get(self.SERVERS, {}))

    def get_resource_def(self, include_all=False):
        return dict(self.properties)

    def build_resource_definition(self, res_name, res_defn):
        props = copy.deepcopy(res_defn)
        servers = props.pop(self.SERVERS)
        props[SoftwareDeployment.SERVER] = servers.get(res_name)
        return rsrc_defn.ResourceDefinition(res_name,
                                            'OS::Heat::SoftwareDeployment',
                                            props, None)

    def get_attribute(self, key, *path):
        rg = super(SoftwareDeploymentGroup, self)
        if key == self.STDOUTS:
            n_attr = SoftwareDeployment.STDOUT
        elif key == self.STDERRS:
            n_attr = SoftwareDeployment.STDERR
        elif key == self.STATUS_CODES:
            n_attr = SoftwareDeployment.STATUS_CODE
        else:
            # Allow any attribute valid for a single SoftwareDeployment
            # including arbitrary outputs, so we can't validate here
            n_attr = key

        rg_attr = rg.get_attribute(rg.ATTR_ATTRIBUTES, n_attr)
        return attributes.select_from_attribute(rg_attr, path)
示例#30
0
class HeatWaitCondition(resource.Resource):
    """Resource for handling signals received by WaitConditionHandle.

    Resource takes WaitConditionHandle and starts to create. Resource is in
    CREATE_IN_PROGRESS status until WaitConditionHandle doesn't receive
    sufficient number of successful signals (this number can be specified with
    count property) and successfully creates after that, or fails due to
    timeout.
    """

    support_status = support.SupportStatus(version='2014.2')

    PROPERTIES = (
        HANDLE,
        TIMEOUT,
        COUNT,
    ) = (
        'handle',
        'timeout',
        'count',
    )

    ATTRIBUTES = (DATA, ) = ('data', )

    properties_schema = {
        HANDLE:
        properties.Schema(
            properties.Schema.STRING,
            _('A reference to the wait condition handle used to signal this '
              'wait condition.'),
            required=True),
        TIMEOUT:
        properties.Schema(
            properties.Schema.NUMBER,
            _('The number of seconds to wait for the correct number of '
              'signals to arrive.'),
            required=True,
            constraints=[
                constraints.Range(1, 43200),
            ]),
        COUNT:
        properties.Schema(
            properties.Schema.INTEGER,
            _('The number of success signals that must be received before '
              'the stack creation process continues.'),
            constraints=[
                constraints.Range(min=1),
            ],
            default=1,
            update_allowed=True),
    }

    attributes_schema = {
        DATA:
        attributes.Schema(_('JSON string containing data associated with wait '
                            'condition signals sent to the handle.'),
                          cache_mode=attributes.Schema.CACHE_NONE,
                          type=attributes.Schema.STRING),
    }

    def __init__(self, name, definition, stack):
        super(HeatWaitCondition, self).__init__(name, definition, stack)

    def _get_handle_resource(self):
        return self.stack.resource_by_refid(self.properties[self.HANDLE])

    def _validate_handle_resource(self, handle):
        if not isinstance(handle, wc_base.BaseWaitConditionHandle):
            raise ValueError(
                _('%(name)s is not a valid wait condition '
                  'handle.') % {'name': handle.name})

    def _wait(self, handle, started_at, timeout_in):
        if timeutils.is_older_than(started_at, timeout_in):
            exc = wc_base.WaitConditionTimeout(self, handle)
            LOG.info(_LI('%(name)s Timed out (%(timeout)s)'), {
                'name': str(self),
                'timeout': str(exc)
            })
            raise exc

        handle_status = handle.get_status()

        if any(s != handle.STATUS_SUCCESS for s in handle_status):
            failure = wc_base.WaitConditionFailure(self, handle)
            LOG.info(_LI('%(name)s Failed (%(failure)s)'), {
                'name': str(self),
                'failure': str(failure)
            })
            raise failure

        if len(handle_status) >= self.properties[self.COUNT]:
            LOG.info(_LI("%s Succeeded"), str(self))
            return True
        return False

    def handle_create(self):
        handle = self._get_handle_resource()
        self._validate_handle_resource(handle)
        started_at = timeutils.utcnow()
        return handle, started_at, float(self.properties[self.TIMEOUT])

    def check_create_complete(self, data):
        return self._wait(*data)

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            self.properties = json_snippet.properties(self.properties_schema,
                                                      self.context)

        handle = self._get_handle_resource()
        started_at = timeutils.utcnow()
        return handle, started_at, float(self.properties[self.TIMEOUT])

    def check_update_complete(self, data):
        return self._wait(*data)

    def handle_delete(self):
        handle = self._get_handle_resource()
        if handle and handle.id and handle.action != handle.INIT:
            handle.metadata_set({})

    def _resolve_attribute(self, key):
        handle = self._get_handle_resource()
        if key == self.DATA:
            meta = handle.metadata_get(refresh=True)
            res = {k: meta[k][handle.DATA] for k in meta}
            LOG.debug('%(name)s.GetAtt(%(key)s) == %(res)s' % {
                'name': self.name,
                'key': key,
                'res': res
            })

            return six.text_type(jsonutils.dumps(res))