Exemple #1
0
class VolumeType(base.CinderPersistentObject, base.CinderObject,
                 base.CinderObjectDictCompat, base.CinderComparableObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    DEFAULT_EXPECTED_ATTR = ('extra_specs', 'projects')

    fields = {
        'id': fields.UUIDField(),
        'name': fields.StringField(nullable=True),
        'description': fields.StringField(nullable=True),
        'is_public': fields.BooleanField(default=True),
        'projects': fields.ListOfStringsField(nullable=True),
        'extra_specs': fields.DictOfStringsField(nullable=True),
    }

    @staticmethod
    def _from_db_object(context, type, db_type, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for name, field in type.fields.items():
            if name in OPTIONAL_FIELDS:
                continue
            value = db_type[name]
            if isinstance(field, fields.IntegerField):
                value = value or 0
            type[name] = value

        # Get data from db_type object that was queried by joined query
        # from DB
        if 'extra_specs' in expected_attrs:
            type.extra_specs = {}
            specs = db_type.get('extra_specs')
            if specs and isinstance(specs, list):
                type.extra_specs = {
                    item['key']: item['value']
                    for item in specs
                }
            elif specs and isinstance(specs, dict):
                type.extra_specs = specs
        if 'projects' in expected_attrs:
            type.projects = db_type.get('projects', [])

        type._context = context
        type.obj_reset_changes()
        return type

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        db_volume_type = volume_types.create(self._context, self.name,
                                             self.extra_specs, self.is_public,
                                             self.projects, self.description)
        self._from_db_object(self._context, self, db_volume_type)

    @base.remotable
    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            volume_types.update(self._context, self.id, self.name,
                                self.description)
            self.obj_reset_changes()

    @base.remotable
    def destroy(self):
        with self.obj_as_admin():
            volume_types.destroy(self._context, self.id)
Exemple #2
0
class Cluster(base.MagnumPersistentObject, base.MagnumObject,
              base.MagnumObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added 'bay_create_timeout' field
    # Version 1.2: Add 'registry_trust_id' field
    # Version 1.3: Added 'baymodel' field
    # Version 1.4: Added more types of status to bay's status field
    # Version 1.5: Rename 'registry_trust_id' to 'trust_id'
    #              Add 'trustee_user_name', 'trustee_password',
    #              'trustee_user_id' field
    # Version 1.6: Add rollback support for Bay
    # Version 1.7: Added 'coe_version'  and 'container_version' fields
    # Version 1.8: Rename 'baymodel' to 'cluster_template'
    # Version 1.9: Rename table name from 'bay' to 'cluster'
    #              Rename 'baymodel_id' to 'cluster_template_id'
    #              Rename 'bay_create_timeout' to 'create_timeout'
    # Version 1.10: Added 'keypair' field
    # Version 1.11: Added 'RESUME_FAILED' in status field
    # Version 1.12: Added 'get_stats' method
    # Version 1.13: Added get_count_all method
    # Version 1.14: Added 'docker_volume_size' field
    # Version 1.15: Added 'labels' field
    # Version 1.16: Added 'master_flavor_id' field
    # Version 1.17: Added 'flavor_id' field

    VERSION = '1.17'

    dbapi = dbapi.get_instance()

    fields = {
        'id': fields.IntegerField(),
        'uuid': fields.UUIDField(nullable=True),
        'name': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'cluster_template_id': fields.StringField(nullable=True),
        'keypair': fields.StringField(nullable=True),
        'docker_volume_size': fields.IntegerField(nullable=True),
        'labels': fields.DictOfStringsField(nullable=True),
        'master_flavor_id': fields.StringField(nullable=True),
        'flavor_id': fields.StringField(nullable=True),
        'stack_id': fields.StringField(nullable=True),
        'status': m_fields.ClusterStatusField(nullable=True),
        'status_reason': fields.StringField(nullable=True),
        'create_timeout': fields.IntegerField(nullable=True),
        'api_address': fields.StringField(nullable=True),
        'node_addresses': fields.ListOfStringsField(nullable=True),
        'node_count': fields.IntegerField(nullable=True),
        'master_count': fields.IntegerField(nullable=True),
        'discovery_url': fields.StringField(nullable=True),
        'master_addresses': fields.ListOfStringsField(nullable=True),
        'ca_cert_ref': fields.StringField(nullable=True),
        'magnum_cert_ref': fields.StringField(nullable=True),
        'cluster_template': fields.ObjectField('ClusterTemplate'),
        'trust_id': fields.StringField(nullable=True),
        'trustee_username': fields.StringField(nullable=True),
        'trustee_password': fields.StringField(nullable=True),
        'trustee_user_id': fields.StringField(nullable=True),
        'coe_version': fields.StringField(nullable=True),
        'container_version': fields.StringField(nullable=True)
    }

    @staticmethod
    def _from_db_object(cluster, db_cluster):
        """Converts a database entity to a formal object."""
        for field in cluster.fields:
            if field != 'cluster_template':
                cluster[field] = db_cluster[field]

        # Note(eliqiao): The following line needs to be placed outside the
        # loop because there is a dependency from cluster_template to
        # cluster_template_id. The cluster_template_id must be populated
        # first in the loop before it can be used to find the cluster_template.
        cluster['cluster_template'] = ClusterTemplate.get_by_uuid(
            cluster._context, cluster.cluster_template_id)

        cluster.obj_reset_changes()
        return cluster

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [
            Cluster._from_db_object(cls(context), obj) for obj in db_objects
        ]

    @base.remotable_classmethod
    def get(cls, context, cluster_id):
        """Find a cluster based on its id or uuid and return a Cluster object.

        :param cluster_id: the id *or* uuid of a cluster.
        :param context: Security context
        :returns: a :class:`Cluster` object.
        """
        if strutils.is_int_like(cluster_id):
            return cls.get_by_id(context, cluster_id)
        elif uuidutils.is_uuid_like(cluster_id):
            return cls.get_by_uuid(context, cluster_id)
        else:
            raise exception.InvalidIdentity(identity=cluster_id)

    @base.remotable_classmethod
    def get_by_id(cls, context, cluster_id):
        """Find a cluster based on its integer id and return a Cluster object.

        :param cluster_id: the id of a cluster.
        :param context: Security context
        :returns: a :class:`Cluster` object.
        """
        db_cluster = cls.dbapi.get_cluster_by_id(context, cluster_id)
        cluster = Cluster._from_db_object(cls(context), db_cluster)
        return cluster

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a cluster based on uuid and return a :class:`Cluster` object.

        :param uuid: the uuid of a cluster.
        :param context: Security context
        :returns: a :class:`Cluster` object.
        """
        db_cluster = cls.dbapi.get_cluster_by_uuid(context, uuid)
        cluster = Cluster._from_db_object(cls(context), db_cluster)
        return cluster

    @base.remotable_classmethod
    def get_count_all(cls, context, filters=None):
        """Get count of matching clusters.

        :param context: The security context
        :param filters: filter dict, can includes 'cluster_template_id',
                        'name', 'node_count', 'stack_id', 'api_address',
                        'node_addresses', 'project_id', 'user_id',
                        'status'(should be a status list), 'master_count'.
        :returns: Count of matching clusters.
        """
        return cls.dbapi.get_cluster_count_all(context, filters=filters)

    @base.remotable_classmethod
    def get_by_name(cls, context, name):
        """Find a cluster based on name and return a Cluster object.

        :param name: the logical name of a cluster.
        :param context: Security context
        :returns: a :class:`Cluster` object.
        """
        db_cluster = cls.dbapi.get_cluster_by_name(context, name)
        cluster = Cluster._from_db_object(cls(context), db_cluster)
        return cluster

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None,
             filters=None):
        """Return a list of Cluster objects.

        :param context: Security context.
        :param limit: maximum number of resources to return in a single result.
        :param marker: pagination marker for large data sets.
        :param sort_key: column to sort results by.
        :param sort_dir: direction to sort. "asc" or "desc".
        :param filters: filter dict, can includes 'cluster_template_id',
                        'name', 'node_count', 'stack_id', 'api_address',
                        'node_addresses', 'project_id', 'user_id',
                        'status'(should be a status list), 'master_count'.
        :returns: a list of :class:`Cluster` object.

        """
        db_clusters = cls.dbapi.get_cluster_list(context,
                                                 limit=limit,
                                                 marker=marker,
                                                 sort_key=sort_key,
                                                 sort_dir=sort_dir,
                                                 filters=filters)
        return Cluster._from_db_object_list(db_clusters, cls, context)

    @base.remotable_classmethod
    def get_stats(cls, context, project_id=None):
        """Return a list of Cluster objects.

        :param context: Security context.
        :param project_id: project id
        """
        return cls.dbapi.get_cluster_stats(project_id)

    @base.remotable
    def create(self, context=None):
        """Create a Cluster record in the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Cluster(context)

        """
        values = self.obj_get_changes()
        db_cluster = self.dbapi.create_cluster(values)
        self._from_db_object(self, db_cluster)

    @base.remotable
    def destroy(self, context=None):
        """Delete the Cluster from the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Cluster(context)
        """
        self.dbapi.destroy_cluster(self.uuid)
        self.obj_reset_changes()

    @base.remotable
    def save(self, context=None):
        """Save updates to this Cluster.

        Updates will be made column by column based on the result
        of self.what_changed().

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Cluster(context)
        """
        updates = self.obj_get_changes()
        self.dbapi.update_cluster(self.uuid, updates)

        self.obj_reset_changes()

    @base.remotable
    def refresh(self, context=None):
        """Loads updates for this Cluster.

        Loads a Cluster with the same uuid from the database and
        checks for updated attributes. Updates are applied from
        the loaded Cluster column by column, if there are any updates.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Cluster(context)
        """
        current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
        for field in self.fields:
            if self.obj_attr_is_set(field) and self[field] != current[field]:
                self[field] = current[field]
Exemple #3
0
class ContainerBase(base.ZunPersistentObject, base.ZunObject):

    fields = {
        'id': fields.IntegerField(),
        'container_id': fields.StringField(nullable=True),
        'uuid': fields.UUIDField(nullable=True),
        'name': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'image': fields.StringField(nullable=True),
        'cpu': fields.FloatField(nullable=True),
        'cpu_policy': fields.StringField(nullable=True),
        'cpuset': fields.ObjectField("Cpuset", nullable=True),
        'memory': fields.StringField(nullable=True),
        'command': fields.ListOfStringsField(nullable=True),
        'status': z_fields.ContainerStatusField(nullable=True),
        'status_reason': fields.StringField(nullable=True),
        'task_state': z_fields.TaskStateField(nullable=True),
        'environment': fields.DictOfStringsField(nullable=True),
        'workdir': fields.StringField(nullable=True),
        'auto_remove': fields.BooleanField(nullable=True),
        'ports': z_fields.ListOfIntegersField(nullable=True),
        'hostname': fields.StringField(nullable=True),
        'labels': fields.DictOfStringsField(nullable=True),
        'addresses': z_fields.JsonField(nullable=True),
        'image_pull_policy': fields.StringField(nullable=True),
        'host': fields.StringField(nullable=True),
        'restart_policy': fields.DictOfStringsField(nullable=True),
        'status_detail': fields.StringField(nullable=True),
        'interactive': fields.BooleanField(nullable=True),
        'tty': fields.BooleanField(nullable=True),
        'image_driver': fields.StringField(nullable=True),
        'websocket_url': fields.StringField(nullable=True),
        'websocket_token': fields.StringField(nullable=True),
        'security_groups': fields.ListOfStringsField(nullable=True),
        'runtime': fields.StringField(nullable=True),
        'pci_devices': fields.ListOfObjectsField('PciDevice', nullable=True),
        'disk': fields.IntegerField(nullable=True),
        'auto_heal': fields.BooleanField(nullable=True),
        'started_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
        'exposed_ports': z_fields.JsonField(nullable=True),
        'exec_instances': fields.ListOfObjectsField('ExecInstance',
                                                    nullable=True),
        'privileged': fields.BooleanField(nullable=True),
        'healthcheck': z_fields.JsonField(nullable=True),
        'registry_id': fields.IntegerField(nullable=True),
        'registry': fields.ObjectField("Registry", nullable=True),
        'annotations': z_fields.JsonField(nullable=True),
        'cni_metadata': z_fields.JsonField(nullable=True),
        'entrypoint': fields.ListOfStringsField(nullable=True),
    }

    # should be redefined in subclasses
    container_type = None

    @staticmethod
    def _from_db_object(container, db_container):
        """Converts a database entity to a formal object."""
        for field in container.fields:
            if field in [
                    'pci_devices', 'exec_instances', 'registry', 'containers',
                    'init_containers'
            ]:
                continue
            if field == 'cpuset':
                container.cpuset = Cpuset._from_dict(db_container['cpuset'])
                continue
            setattr(container, field, db_container[field])

        container.obj_reset_changes()
        return container

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [cls._from_db_object(cls(context), obj) for obj in db_objects]

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a container based on uuid and return a :class:`Container` object.

        :param uuid: the uuid of a container.
        :param context: Security context
        :returns: a :class:`Container` object.
        """
        db_container = dbapi.get_container_by_uuid(context, cls.container_type,
                                                   uuid)
        container = cls._from_db_object(cls(context), db_container)
        return container

    @base.remotable_classmethod
    def get_by_name(cls, context, name):
        """Find a container based on name and return a Container object.

        :param name: the logical name of a container.
        :param context: Security context
        :returns: a :class:`Container` object.
        """
        db_container = dbapi.get_container_by_name(context, cls.container_type,
                                                   name)
        container = cls._from_db_object(cls(context), db_container)
        return container

    @staticmethod
    def get_container_any_type(context, uuid):
        """Find a container of any type based on uuid.

        :param uuid: the uuid of a container.
        :param context: Security context
        :returns: a :class:`ContainerBase` object.
        """
        db_container = dbapi.get_container_by_uuid(context, consts.TYPE_ANY,
                                                   uuid)
        type = db_container['container_type']
        if type == consts.TYPE_CONTAINER:
            container_cls = Container
        elif type == consts.TYPE_CAPSULE:
            container_cls = Capsule
        elif type == consts.TYPE_CAPSULE_CONTAINER:
            container_cls = CapsuleContainer
        elif type == consts.TYPE_CAPSULE_INIT_CONTAINER:
            container_cls = CapsuleInitContainer
        else:
            raise exception.ZunException(_('Unknown container type: %s'), type)

        obj = container_cls(context)
        container = container_cls._from_db_object(obj, db_container)
        return container

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None,
             filters=None):
        """Return a list of Container objects.

        :param context: Security context.
        :param limit: maximum number of resources to return in a single result.
        :param marker: pagination marker for large data sets.
        :param sort_key: column to sort results by.
        :param sort_dir: direction to sort. "asc" or "desc".
        :param filters: filters when list containers, the filter name could be
                        'name', 'image', 'project_id', 'user_id', 'memory'.
                        For example, filters={'image': 'nginx'}
        :returns: a list of :class:`Container` object.

        """
        db_containers = dbapi.list_containers(context,
                                              cls.container_type,
                                              limit=limit,
                                              marker=marker,
                                              sort_key=sort_key,
                                              sort_dir=sort_dir,
                                              filters=filters)
        return cls._from_db_object_list(db_containers, cls, context)

    @base.remotable_classmethod
    def list_by_host(cls, context, host):
        """Return a list of Container objects by host.

        :param context: Security context.
        :param host: A compute host.
        :returns: a list of :class:`Container` object.

        """
        db_containers = dbapi.list_containers(context,
                                              cls.container_type,
                                              filters={'host': host})
        return cls._from_db_object_list(db_containers, cls, context)

    @base.remotable
    def create(self, context):
        """Create a Container record in the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)

        """
        values = self.obj_get_changes()
        cpuset_obj = values.pop('cpuset', None)
        if cpuset_obj is not None:
            values['cpuset'] = cpuset_obj._to_dict()
        annotations = values.pop('annotations', None)
        if annotations is not None:
            values['annotations'] = self.fields['annotations'].to_primitive(
                self, 'annotations', self.annotations)
        cni_metadata = values.pop('cni_metadata', None)
        if cni_metadata is not None:
            values['cni_metadata'] = self.fields['cni_metadata'].to_primitive(
                self, 'cni_metadata', self.cni_metadata)
        values['container_type'] = self.container_type
        db_container = dbapi.create_container(context, values)
        self._from_db_object(self, db_container)

    @base.remotable
    def destroy(self, context=None):
        """Delete the Container from the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)
        """
        dbapi.destroy_container(context, self.container_type, self.uuid)
        self.obj_reset_changes()

    @base.remotable
    def save(self, context=None):
        """Save updates to this Container.

        Updates will be made column by column based on the result
        of self.what_changed().

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)
        """
        updates = self.obj_get_changes()
        cpuset_obj = updates.pop('cpuset', None)
        if cpuset_obj is not None:
            updates['cpuset'] = cpuset_obj._to_dict()
        annotations = updates.pop('annotations', None)
        if annotations is not None:
            updates['annotations'] = self.fields['annotations'].to_primitive(
                self, 'annotations', self.annotations)
        cni_metadata = updates.pop('cni_metadata', None)
        if cni_metadata is not None:
            updates['cni_metadata'] = self.fields['cni_metadata'].to_primitive(
                self, 'cni_metadata', self.cni_metadata)
        dbapi.update_container(context, self.container_type, self.uuid,
                               updates)

        self.obj_reset_changes()

    @base.remotable
    def refresh(self, context=None):
        """Loads updates for this Container.

        Loads a container with the same uuid from the database and
        checks for updated attributes. Updates are applied from
        the loaded container column by column, if there are any updates.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)
        """
        current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
        for field in self.fields:
            if self.obj_attr_is_set(field) and \
               getattr(self, field) != getattr(current, field):
                setattr(self, field, getattr(current, field))

    def obj_load_attr(self, attrname):
        if attrname not in CONTAINER_OPTIONAL_ATTRS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)

        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading '%(attr)s' on %(name)s uuid %(uuid)s", {
            'attr': attrname,
            'name': self.obj_name(),
            'uuid': self.uuid,
        })

        # NOTE(danms): We handle some fields differently here so that we
        # can be more efficient
        if attrname == 'pci_devices':
            self._load_pci_devices()

        if attrname == 'exec_instances':
            self._load_exec_instances()

        if attrname == 'registry':
            self._load_registry()

        self.obj_reset_changes([attrname])

    def _load_pci_devices(self):
        self.pci_devices = pci_device.PciDevice.list_by_container_uuid(
            self._context, self.uuid)

    def _load_exec_instances(self):
        self.exec_instances = exec_inst.ExecInstance.list_by_container_id(
            self._context, self.id)

    def _load_registry(self):
        self.registry = None
        if self.registry_id:
            self.registry = registry.Registry.get_by_id(
                self._context, self.registry_id)

    @base.remotable_classmethod
    def get_count(cls, context, project_id, flag):
        """Get the counts of Container objects in the database.

        :param context: The request context for database access.
        :param project_id: The project_id to count across.
        :param flag: The name of resource, one of the following options:
                     - containers: Count the number of containers owned by the
                     project.
                     - memory: The sum of containers's memory.
                     - cpu: The sum of container's cpu.
                     - disk: The sum of container's disk size.
        """
        usage = dbapi.count_usage(context, cls.container_type, project_id,
                                  flag)[0] or 0.0
        return usage
Exemple #4
0
class Registry(base.ZunPersistentObject, base.ZunObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {
        'id': fields.IntegerField(),
        'uuid': fields.UUIDField(nullable=True),
        'name': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'domain': fields.StringField(nullable=False),
        'username': fields.StringField(nullable=True),
        'password': fields.StringField(nullable=True),
    }

    @staticmethod
    def _from_db_object(registry, db_registry):
        """Converts a database entity to a formal object."""
        for field in registry.fields:
            setattr(registry, field, db_registry[field])

        registry.obj_reset_changes()
        return registry

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [
            Registry._from_db_object(cls(context), obj) for obj in db_objects
        ]

    @base.remotable_classmethod
    def get_by_id(cls, context, id):
        """Find a registry based on id and return a :class:`Registry` object.

        :param id: the id of a registry.
        :param context: Security context
        :returns: a :class:`Registry` object.
        """
        db_registry = dbapi.get_registry_by_id(context, id)
        registry = Registry._from_db_object(cls(context), db_registry)
        return registry

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a registry based on uuid and return a :class:`Registry` object.

        :param uuid: the uuid of a registry.
        :param context: Security context
        :returns: a :class:`Registry` object.
        """
        db_registry = dbapi.get_registry_by_uuid(context, uuid)
        registry = Registry._from_db_object(cls(context), db_registry)
        return registry

    @base.remotable_classmethod
    def get_by_name(cls, context, name):
        """Find a registry based on name and return a Registry object.

        :param name: the logical name of a registry.
        :param context: Security context
        :returns: a :class:`Registry` object.
        """
        db_registry = dbapi.get_registry_by_name(context, name)
        registry = Registry._from_db_object(cls(context), db_registry)
        return registry

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None,
             filters=None):
        """Return a list of Registry objects.

        :param context: Security context.
        :param limit: maximum number of resources to return in a single result.
        :param marker: pagination marker for large data sets.
        :param sort_key: column to sort results by.
        :param sort_dir: direction to sort. "asc" or "desc".
        :param filters: filters when list registries.
        :returns: a list of :class:`Registry` object.

        """
        db_registries = dbapi.list_registries(context,
                                              limit=limit,
                                              marker=marker,
                                              sort_key=sort_key,
                                              sort_dir=sort_dir,
                                              filters=filters)
        return Registry._from_db_object_list(db_registries, cls, context)

    @base.remotable
    def create(self, context):
        """Create a Registry record in the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Registry(context)

        """
        values = self.obj_get_changes()
        db_registry = dbapi.create_registry(context, values)
        self._from_db_object(self, db_registry)

    @base.remotable
    def destroy(self, context=None):
        """Delete the Registry from the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Registry(context)
        """
        dbapi.destroy_registry(context, self.uuid)
        self.obj_reset_changes()

    @base.remotable
    def save(self, context=None):
        """Save updates to this Registry.

        Updates will be made column by column based on the result
        of self.what_changed().

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Registry(context)
        """
        updates = self.obj_get_changes()
        dbapi.update_registry(context, self.uuid, updates)

        self.obj_reset_changes()
class VolumeAttachment(base.CinderPersistentObject, base.CinderObject,
                       base.CinderObjectDictCompat,
                       base.CinderComparableObject):
    # Version 1.0: Initial version
    # Version 1.1: Added volume relationship
    # Version 1.2: Added connection_info attribute
    # Version 1.3: Added the connector attribute.
    VERSION = '1.3'

    OPTIONAL_FIELDS = ['volume']
    obj_extra_fields = ['project_id', 'volume_host']

    # NOTE: When adding a field obj_make_compatible needs to be updated
    fields = {
        'id': fields.UUIDField(),
        'volume_id': fields.UUIDField(),
        'instance_uuid': fields.UUIDField(nullable=True),
        'attached_host': fields.StringField(nullable=True),
        'mountpoint': fields.StringField(nullable=True),

        'attach_time': fields.DateTimeField(nullable=True),
        'detach_time': fields.DateTimeField(nullable=True),

        'attach_status': c_fields.VolumeAttachStatusField(nullable=True),
        'attach_mode': fields.StringField(nullable=True),

        'volume': fields.ObjectField('Volume', nullable=False),
        'connection_info': c_fields.DictOfNullableField(nullable=True),
        'connector': c_fields.DictOfNullableField(nullable=True)
    }

    @property
    def project_id(self):
        return self.volume.project_id

    @property
    def volume_host(self):
        return self.volume.host

    @classmethod
    def _get_expected_attrs(cls, context, *args, **kwargs):
        return ['volume']

    @classmethod
    def _from_db_object(cls, context, attachment, db_attachment,
                        expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = cls._get_expected_attrs(context)

        for name, field in attachment.fields.items():
            if name in cls.OPTIONAL_FIELDS:
                continue
            value = db_attachment.get(name)
            if isinstance(field, fields.IntegerField):
                value = value or 0
            if name in ('connection_info', 'connector'):
                # Both of these fields are nullable serialized json dicts.
                setattr(attachment, name,
                        jsonutils.loads(value) if value else None)
            else:
                attachment[name] = value
        # NOTE: Check against the ORM instance's dictionary instead of using
        # hasattr or get to avoid the lazy loading of the Volume on
        # VolumeList.get_all.
        # Getting a Volume loads its VolumeAttachmentList, which think they
        # have the volume loaded, but they don't.  More detail on
        # https://review.opendev.org/632549
        # and its related bug report.
        if 'volume' in expected_attrs and 'volume' in vars(db_attachment):
            db_volume = db_attachment.volume
            if db_volume:
                attachment.volume = objects.Volume._from_db_object(
                    context, objects.Volume(), db_volume)

        attachment._context = context
        attachment.obj_reset_changes()

        # This is an online data migration which we should remove when enough
        # time has passed and we have a blocker schema migration to check to
        # make sure that the attachment_specs table is empty. Operators should
        # run the "cinder-manage db online_data_migrations" CLI to force the
        # migration on-demand.
        connector = db.attachment_specs_get(context, attachment.id)
        if connector:
            # Update ourselves and delete the attachment_specs.
            attachment.connector = connector
            attachment.save()
            # TODO(mriedem): Really need a delete-all method for this.
            for spec_key in connector:
                db.attachment_specs_delete(
                    context, attachment.id, spec_key)

        return attachment

    def obj_load_attr(self, attrname):
        if attrname not in self.OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'volume':
            volume = objects.Volume.get_by_id(self._context, self.volume_id)
            self.volume = volume

        self.obj_reset_changes(fields=[attrname])

    @staticmethod
    def _convert_connection_info_to_db_format(updates):
        properties = updates.pop('connection_info', None)
        if properties is not None:
            updates['connection_info'] = jsonutils.dumps(properties)

    @staticmethod
    def _convert_connector_to_db_format(updates):
        connector = updates.pop('connector', None)
        if connector is not None:
            updates['connector'] = jsonutils.dumps(connector)

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'connection_info' in updates:
                self._convert_connection_info_to_db_format(updates)
            if 'connector' in updates:
                self._convert_connector_to_db_format(updates)
            if 'volume' in updates:
                raise exception.ObjectActionError(action='save',
                                                  reason=_('volume changed'))

            db.volume_attachment_update(self._context, self.id, updates)
            self.obj_reset_changes()

    def finish_attach(self, instance_uuid, host_name,
                      mount_point, attach_mode='rw'):
        with self.obj_as_admin():
            db_volume, updated_values = db.volume_attached(
                self._context, self.id,
                instance_uuid, host_name,
                mount_point, attach_mode)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())
        return objects.Volume._from_db_object(self._context,
                                              objects.Volume(),
                                              db_volume)

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        updates = self.cinder_obj_get_changes()
        if 'connector' in updates:
            self._convert_connector_to_db_format(updates)
        with self.obj_as_admin():
            db_attachment = db.volume_attach(self._context, updates)
        self._from_db_object(self._context, self, db_attachment)

    def destroy(self):
        updated_values = db.attachment_destroy(self._context, self.id)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())
Exemple #6
0
class TestSubclassedObject(MyObj):
    fields = {'new_field': fields.StringField()}
Exemple #7
0
class ConsistencyGroup(base.CinderPersistentObject, base.CinderObject,
                       base.CinderObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added cgsnapshots and volumes relationships
    # Version 1.2: Changed 'status' field to use ConsistencyGroupStatusField
    VERSION = '1.2'

    fields = {
        'id': fields.UUIDField(),
        'user_id': fields.UUIDField(),
        'project_id': fields.UUIDField(),
        'host': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'name': fields.StringField(nullable=True),
        'description': fields.StringField(nullable=True),
        'volume_type_id': fields.UUIDField(nullable=True),
        'status': c_fields.ConsistencyGroupStatusField(nullable=True),
        'cgsnapshot_id': fields.UUIDField(nullable=True),
        'source_cgid': fields.UUIDField(nullable=True),
        'cgsnapshots': fields.ObjectField('CGSnapshotList', nullable=True),
        'volumes': fields.ObjectField('VolumeList', nullable=True),
    }

    @staticmethod
    def _from_db_object(context, consistencygroup, db_consistencygroup,
                        expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for name, field in consistencygroup.fields.items():
            if name in OPTIONAL_FIELDS:
                continue
            value = db_consistencygroup.get(name)
            setattr(consistencygroup, name, value)

        if 'cgsnapshots' in expected_attrs:
            cgsnapshots = base.obj_make_list(
                context, objects.CGSnapshotsList(context),
                objects.CGSnapshot,
                db_consistencygroup['cgsnapshots'])
            consistencygroup.cgsnapshots = cgsnapshots

        if 'volumes' in expected_attrs:
            volumes = base.obj_make_list(
                context, objects.VolumeList(context),
                objects.Volume,
                db_consistencygroup['volumes'])
            consistencygroup.cgsnapshots = volumes

        consistencygroup._context = context
        consistencygroup.obj_reset_changes()
        return consistencygroup

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already_created'))
        updates = self.cinder_obj_get_changes()

        if 'cgsnapshots' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('cgsnapshots assigned'))

        if 'volumes' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('volumes assigned'))

        db_consistencygroups = db.consistencygroup_create(self._context,
                                                          updates)
        self._from_db_object(self._context, self, db_consistencygroups)

    def obj_load_attr(self, attrname):
        if attrname not in OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'cgsnapshots':
            self.cgsnapshots = objects.CGSnapshotList.get_all_by_group(
                self._context, self.id)

        if attrname == 'volumes':
            self.volumes = objects.VolumeList.get_all_by_group(self._context,
                                                               self.id)

        self.obj_reset_changes(fields=[attrname])

    @base.remotable
    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'cgsnapshots' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('cgsnapshots changed'))
            if 'volumes' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('volumes changed'))

            db.consistencygroup_update(self._context, self.id, updates)
            self.obj_reset_changes()

    @base.remotable
    def destroy(self):
        with self.obj_as_admin():
            db.consistencygroup_destroy(self._context, self.id)
Exemple #8
0
class Volume(cleanable.CinderCleanableObject, base.CinderObject,
             base.CinderObjectDictCompat, base.CinderComparableObject,
             base.ClusteredObject):
    # Version 1.0: Initial version
    # Version 1.1: Added metadata, admin_metadata, volume_attachment, and
    #              volume_type
    # Version 1.2: Added glance_metadata, consistencygroup and snapshots
    # Version 1.3: Added finish_volume_migration()
    # Version 1.4: Added cluster fields
    # Version 1.5: Added group
    # Version 1.6: This object is now cleanable (adds rows to workers table)
    # Version 1.7: Added service_uuid
    # Version 1.8: Added shared_targets
    VERSION = '1.8'

    OPTIONAL_FIELDS = ('metadata', 'admin_metadata', 'glance_metadata',
                       'volume_type', 'volume_attachment', 'consistencygroup',
                       'snapshots', 'cluster', 'group')

    fields = {
        'id':
        fields.UUIDField(),
        '_name_id':
        fields.UUIDField(nullable=True),
        'ec2_id':
        fields.UUIDField(nullable=True),
        'user_id':
        fields.StringField(nullable=True),
        'project_id':
        fields.StringField(nullable=True),
        'snapshot_id':
        fields.UUIDField(nullable=True),
        'cluster_name':
        fields.StringField(nullable=True),
        'cluster':
        fields.ObjectField('Cluster', nullable=True, read_only=True),
        'host':
        fields.StringField(nullable=True),
        'size':
        fields.IntegerField(nullable=True),
        'availability_zone':
        fields.StringField(nullable=True),
        'status':
        fields.StringField(nullable=True),
        'attach_status':
        c_fields.VolumeAttachStatusField(nullable=True),
        'migration_status':
        fields.StringField(nullable=True),
        'scheduled_at':
        fields.DateTimeField(nullable=True),
        'launched_at':
        fields.DateTimeField(nullable=True),
        'terminated_at':
        fields.DateTimeField(nullable=True),
        'display_name':
        fields.StringField(nullable=True),
        'display_description':
        fields.StringField(nullable=True),
        'provider_id':
        fields.StringField(nullable=True),
        'provider_location':
        fields.StringField(nullable=True),
        'provider_auth':
        fields.StringField(nullable=True),
        'provider_geometry':
        fields.StringField(nullable=True),
        'volume_type_id':
        fields.UUIDField(nullable=True),
        'source_volid':
        fields.UUIDField(nullable=True),
        'encryption_key_id':
        fields.UUIDField(nullable=True),
        'consistencygroup_id':
        fields.UUIDField(nullable=True),
        'group_id':
        fields.UUIDField(nullable=True),
        'deleted':
        fields.BooleanField(default=False, nullable=True),
        'bootable':
        fields.BooleanField(default=False, nullable=True),
        'multiattach':
        fields.BooleanField(default=False, nullable=True),
        'replication_status':
        fields.StringField(nullable=True),
        'replication_extended_status':
        fields.StringField(nullable=True),
        'replication_driver_data':
        fields.StringField(nullable=True),
        'previous_status':
        fields.StringField(nullable=True),
        'metadata':
        fields.DictOfStringsField(nullable=True),
        'admin_metadata':
        fields.DictOfStringsField(nullable=True),
        'glance_metadata':
        fields.DictOfStringsField(nullable=True),
        'volume_type':
        fields.ObjectField('VolumeType', nullable=True),
        'volume_attachment':
        fields.ObjectField('VolumeAttachmentList', nullable=True),
        'consistencygroup':
        fields.ObjectField('ConsistencyGroup', nullable=True),
        'snapshots':
        fields.ObjectField('SnapshotList', nullable=True),
        'group':
        fields.ObjectField('Group', nullable=True),
        'service_uuid':
        fields.StringField(nullable=True),
        'shared_targets':
        fields.BooleanField(default=True, nullable=True),
    }

    # NOTE(thangp): obj_extra_fields is used to hold properties that are not
    # usually part of the model
    obj_extra_fields = [
        'name', 'name_id', 'volume_metadata', 'volume_admin_metadata',
        'volume_glance_metadata'
    ]

    @classmethod
    def _get_expected_attrs(cls, context, *args, **kwargs):
        expected_attrs = ['metadata', 'volume_type', 'volume_type.extra_specs']
        if context.is_admin:
            expected_attrs.append('admin_metadata')

        return expected_attrs

    @property
    def name_id(self):
        return self.id if not self._name_id else self._name_id

    @name_id.setter
    def name_id(self, value):
        self._name_id = value

    @property
    def name(self):
        return CONF.volume_name_template % self.name_id

    # TODO(dulek): Three properties below are for compatibility with dict
    # representation of volume. The format there is different (list of
    # SQLAlchemy models) so we need a conversion. Anyway - these should be
    # removed when we stop this class from deriving from DictObjectCompat.
    @property
    def volume_metadata(self):
        md = [MetadataObject(k, v) for k, v in self.metadata.items()]
        return md

    @volume_metadata.setter
    def volume_metadata(self, value):
        md = {d['key']: d['value'] for d in value}
        self.metadata = md

    @property
    def volume_admin_metadata(self):
        md = [MetadataObject(k, v) for k, v in self.admin_metadata.items()]
        return md

    @volume_admin_metadata.setter
    def volume_admin_metadata(self, value):
        md = {d['key']: d['value'] for d in value}
        self.admin_metadata = md

    @property
    def volume_glance_metadata(self):
        md = [MetadataObject(k, v) for k, v in self.glance_metadata.items()]
        return md

    @volume_glance_metadata.setter
    def volume_glance_metadata(self, value):
        md = {d['key']: d['value'] for d in value}
        self.glance_metadata = md

    def __init__(self, *args, **kwargs):
        super(Volume, self).__init__(*args, **kwargs)
        self._orig_metadata = {}
        self._orig_admin_metadata = {}
        self._orig_glance_metadata = {}

        self._reset_metadata_tracking()

    def obj_reset_changes(self, fields=None):
        super(Volume, self).obj_reset_changes(fields)
        self._reset_metadata_tracking(fields=fields)

    @classmethod
    def _obj_from_primitive(cls, context, objver, primitive):
        obj = super(Volume,
                    Volume)._obj_from_primitive(context, objver, primitive)
        obj._reset_metadata_tracking()
        return obj

    def _reset_metadata_tracking(self, fields=None):
        if fields is None or 'metadata' in fields:
            self._orig_metadata = (dict(self.metadata)
                                   if 'metadata' in self else {})
        if fields is None or 'admin_metadata' in fields:
            self._orig_admin_metadata = (dict(self.admin_metadata)
                                         if 'admin_metadata' in self else {})
        if fields is None or 'glance_metadata' in fields:
            self._orig_glance_metadata = (dict(self.glance_metadata)
                                          if 'glance_metadata' in self else {})

    def obj_what_changed(self):
        changes = super(Volume, self).obj_what_changed()
        if 'metadata' in self and self.metadata != self._orig_metadata:
            changes.add('metadata')
        if ('admin_metadata' in self
                and self.admin_metadata != self._orig_admin_metadata):
            changes.add('admin_metadata')
        if ('glance_metadata' in self
                and self.glance_metadata != self._orig_glance_metadata):
            changes.add('glance_metadata')

        return changes

    def obj_make_compatible(self, primitive, target_version):
        """Make a Volume representation compatible with a target version."""
        added_fields = (((1, 4), ('cluster', 'cluster_name')),
                        ((1, 5), ('group', 'group_id')), ((1, 7),
                                                          ('service_uuid')))

        # Convert all related objects
        super(Volume, self).obj_make_compatible(primitive, target_version)

        target_version = versionutils.convert_version_to_tuple(target_version)
        for version, remove_fields in added_fields:
            if target_version < version:
                for obj_field in remove_fields:
                    primitive.pop(obj_field, None)

    @classmethod
    def _from_db_object(cls, context, volume, db_volume, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for name, field in volume.fields.items():
            if name in cls.OPTIONAL_FIELDS:
                continue
            value = db_volume.get(name)
            if isinstance(field, fields.IntegerField):
                value = value or 0
            volume[name] = value

        # Get data from db_volume object that was queried by joined query
        # from DB
        if 'metadata' in expected_attrs:
            metadata = db_volume.get('volume_metadata', [])
            volume.metadata = {item['key']: item['value'] for item in metadata}
        if 'admin_metadata' in expected_attrs:
            metadata = db_volume.get('volume_admin_metadata', [])
            volume.admin_metadata = {
                item['key']: item['value']
                for item in metadata
            }
        if 'glance_metadata' in expected_attrs:
            metadata = db_volume.get('volume_glance_metadata', [])
            volume.glance_metadata = {
                item['key']: item['value']
                for item in metadata
            }
        if 'volume_type' in expected_attrs:
            db_volume_type = db_volume.get('volume_type')
            if db_volume_type:
                vt_expected_attrs = []
                if 'volume_type.extra_specs' in expected_attrs:
                    vt_expected_attrs.append('extra_specs')
                volume.volume_type = objects.VolumeType._from_db_object(
                    context,
                    objects.VolumeType(),
                    db_volume_type,
                    expected_attrs=vt_expected_attrs)
        if 'volume_attachment' in expected_attrs:
            attachments = base.obj_make_list(
                context, objects.VolumeAttachmentList(context),
                objects.VolumeAttachment, db_volume.get('volume_attachment'))
            volume.volume_attachment = attachments
        if volume.consistencygroup_id and 'consistencygroup' in expected_attrs:
            consistencygroup = objects.ConsistencyGroup(context)
            consistencygroup._from_db_object(context, consistencygroup,
                                             db_volume['consistencygroup'])
            volume.consistencygroup = consistencygroup
        if 'snapshots' in expected_attrs:
            snapshots = base.obj_make_list(context,
                                           objects.SnapshotList(context),
                                           objects.Snapshot,
                                           db_volume['snapshots'])
            volume.snapshots = snapshots
        if 'cluster' in expected_attrs:
            db_cluster = db_volume.get('cluster')
            # If this volume doesn't belong to a cluster the cluster field in
            # the ORM instance will have value of None.
            if db_cluster:
                volume.cluster = objects.Cluster(context)
                objects.Cluster._from_db_object(context, volume.cluster,
                                                db_cluster)
            else:
                volume.cluster = None
        if volume.group_id and 'group' in expected_attrs:
            group = objects.Group(context)
            group._from_db_object(context, group, db_volume['group'])
            volume.group = group

        volume._context = context
        volume.obj_reset_changes()
        return volume

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        updates = self.cinder_obj_get_changes()

        if 'consistencygroup' in updates:
            raise exception.ObjectActionError(
                action='create', reason=_('consistencygroup assigned'))
        if 'snapshots' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('snapshots assigned'))
        if 'cluster' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('cluster assigned'))
        if 'group' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('group assigned'))

        db_volume = db.volume_create(self._context, updates)
        self._from_db_object(self._context, self, db_volume)

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            # NOTE(xyang): Allow this to pass if 'consistencygroup' is
            # set to None. This is to support backward compatibility.
            # Also remove 'consistencygroup' from updates because
            # consistencygroup is the name of a relationship in the ORM
            # Volume model, so SQLA tries to do some kind of update of
            # the foreign key based on the provided updates if
            # 'consistencygroup' is in updates.
            if updates.pop('consistencygroup', None):
                raise exception.ObjectActionError(
                    action='save', reason=_('consistencygroup changed'))
            if 'group' in updates:
                raise exception.ObjectActionError(action='save',
                                                  reason=_('group changed'))
            if 'glance_metadata' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('glance_metadata changed'))
            if 'snapshots' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('snapshots changed'))
            if 'cluster' in updates:
                raise exception.ObjectActionError(action='save',
                                                  reason=_('cluster changed'))
            if 'metadata' in updates:
                # Metadata items that are not specified in the
                # self.metadata will be deleted
                metadata = updates.pop('metadata', None)
                self.metadata = db.volume_metadata_update(
                    self._context, self.id, metadata, True)
            if self._context.is_admin and 'admin_metadata' in updates:
                metadata = updates.pop('admin_metadata', None)
                self.admin_metadata = db.volume_admin_metadata_update(
                    self._context, self.id, metadata, True)

            # When we are creating a volume and we change from 'creating'
            # status to 'downloading' status we have to change the worker entry
            # in the DB to reflect this change, otherwise the cleanup will
            # not be performed as it will be mistaken for a volume that has
            # been somehow changed (reset status, forced operation...)
            if updates.get('status') == 'downloading':
                self.set_worker()

            # updates are changed after popping out metadata.
            if updates:
                db.volume_update(self._context, self.id, updates)
            self.obj_reset_changes()

    def destroy(self):
        with self.obj_as_admin():
            updated_values = db.volume_destroy(self._context, self.id)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())

    def obj_load_attr(self, attrname):
        if attrname not in self.OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'metadata':
            self.metadata = db.volume_metadata_get(self._context, self.id)
        elif attrname == 'admin_metadata':
            self.admin_metadata = {}
            if self._context.is_admin:
                self.admin_metadata = db.volume_admin_metadata_get(
                    self._context, self.id)
        elif attrname == 'glance_metadata':
            try:
                # NOTE(dulek): We're using alias here to have conversion from
                # list to dict done there.
                self.volume_glance_metadata = db.volume_glance_metadata_get(
                    self._context, self.id)
            except exception.GlanceMetadataNotFound:
                # NOTE(dulek): DB API raises when volume has no
                # glance_metadata. Silencing this because at this level no
                # metadata is a completely valid result.
                self.glance_metadata = {}
        elif attrname == 'volume_type':
            # If the volume doesn't have volume_type, VolumeType.get_by_id
            # would trigger a db call which raise VolumeTypeNotFound exception.
            self.volume_type = (objects.VolumeType.get_by_id(
                self._context, self.volume_type_id)
                                if self.volume_type_id else None)
        elif attrname == 'volume_attachment':
            attachments = objects.VolumeAttachmentList.get_all_by_volume_id(
                self._context, self.id)
            self.volume_attachment = attachments
        elif attrname == 'consistencygroup':
            if self.consistencygroup_id is None:
                self.consistencygroup = None
            else:
                consistencygroup = objects.ConsistencyGroup.get_by_id(
                    self._context, self.consistencygroup_id)
                self.consistencygroup = consistencygroup
        elif attrname == 'snapshots':
            self.snapshots = objects.SnapshotList.get_all_for_volume(
                self._context, self.id)
        elif attrname == 'cluster':
            # If this volume doesn't belong to a cluster (cluster_name is
            # empty), then cluster field will be None.
            if self.cluster_name:
                self.cluster = objects.Cluster.get_by_id(
                    self._context, name=self.cluster_name)
            else:
                self.cluster = None
        elif attrname == 'group':
            if self.group_id is None:
                self.group = None
            else:
                group = objects.Group.get_by_id(self._context, self.group_id)
                self.group = group

        self.obj_reset_changes(fields=[attrname])

    def delete_metadata_key(self, key):
        db.volume_metadata_delete(self._context, self.id, key)
        md_was_changed = 'metadata' in self.obj_what_changed()

        del self.metadata[key]
        self._orig_metadata.pop(key, None)

        if not md_was_changed:
            self.obj_reset_changes(['metadata'])

    def finish_volume_migration(self, dest_volume):
        # We swap fields between source (i.e. self) and destination at the
        # end of migration because we want to keep the original volume id
        # in the DB but now pointing to the migrated volume.
        skip = ({'id', 'provider_location', 'glance_metadata', 'volume_type'}
                | set(self.obj_extra_fields))
        for key in set(dest_volume.fields.keys()) - skip:
            # Only swap attributes that are already set.  We do not want to
            # unexpectedly trigger a lazy-load.
            if not dest_volume.obj_attr_is_set(key):
                continue

            value = getattr(dest_volume, key)
            value_to_dst = getattr(self, key)

            # Destination must have a _name_id since the id no longer matches
            # the volume.  If it doesn't have a _name_id we set one.
            if key == '_name_id':
                if not dest_volume._name_id:
                    setattr(dest_volume, key, self.id)
                continue
            elif key == 'migration_status':
                value = None
                value_to_dst = 'deleting'
            elif key == 'display_description':
                value_to_dst = 'migration src for ' + self.id
            elif key == 'status':
                value_to_dst = 'deleting'
            # Because dest_volume will be deleted soon, we can
            # skip to copy volume_type_id and volume_type which
            # are not keys for volume deletion.
            elif key == 'volume_type_id':
                # Initialize volume_type of source volume using
                # new volume_type_id.
                self.update({'volume_type_id': value})
                continue

            setattr(self, key, value)
            setattr(dest_volume, key, value_to_dst)

        self.save()
        dest_volume.save()
        return dest_volume

    def get_latest_snapshot(self):
        """Get volume's latest snapshot"""
        snapshot_db = db.snapshot_get_latest_for_volume(self._context, self.id)
        snapshot = objects.Snapshot(self._context)
        return snapshot._from_db_object(self._context, snapshot, snapshot_db)

    @staticmethod
    def _is_cleanable(status, obj_version):
        # Before 1.6 we didn't have workers table, so cleanup wasn't supported.
        # cleaning.
        if obj_version and obj_version < 1.6:
            return False
        return status in ('creating', 'deleting', 'uploading', 'downloading')

    def begin_attach(self, attach_mode):
        attachment = objects.VolumeAttachment(
            context=self._context,
            attach_status=c_fields.VolumeAttachStatus.ATTACHING,
            volume_id=self.id)
        attachment.create()
        with self.obj_as_admin():
            self.admin_metadata['attached_mode'] = attach_mode
            self.save()
        return attachment

    def finish_detach(self, attachment_id):
        with self.obj_as_admin():
            volume_updates, attachment_updates = (db.volume_detached(
                self._context, self.id, attachment_id))
            db.volume_admin_metadata_delete(self._context, self.id,
                                            'attached_mode')
            self.admin_metadata.pop('attached_mode', None)
        # Remove attachment in volume only when this field is loaded.
        if attachment_updates and self.obj_attr_is_set('volume_attachment'):
            for i, attachment in enumerate(self.volume_attachment):
                if attachment.id == attachment_id:
                    del self.volume_attachment.objects[i]
                    break

        self.update(volume_updates)
        self.obj_reset_changes(
            list(volume_updates.keys()) +
            ['volume_attachment', 'admin_metadata'])

    def is_replicated(self):
        return self.volume_type and self.volume_type.is_replicated()
Exemple #9
0
class Provider(base.ZunPersistentObject, base.ZunObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {
        'id': fields.IntegerField(),
        'provider': fields.StringField(nullable=True),
        'uuid': fields.UUIDField(nullable=True),
        'displayname': fields.StringField(nullable=True)
    }

    @staticmethod
    def _from_db_object(provider, db_provider):
        """Converts a database entity to a formal object."""
        for field in provider.fields:
            setattr(provider, field, db_provider[field])

        provider.obj_reset_changes()
        return provider

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [
            Provider._from_db_object(cls(context), obj) for obj in db_objects
        ]

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a provider based on uuid and return a :class:`Provider` object.

        :param uuid: the uuid of a provider.
        :param context: Security context
        :returns: a :class:`Provider` object.
        """
        db_provider = dbapi.get_provider_by_uuid(context, uuid)
        provider = Provider._from_db_object(cls(context), db_provider)
        return provider

    @base.remotable_classmethod
    def get_by_name(cls, context, name):
        """Find a provider based on name and return a Provider object.

        :param name: the logical name of a provider.
        :param context: Security context
        :returns: a :class:`Provider` object.
        """
        db_provider = dbapi.get_provider_by_name(context, name)
        provider = Provider._from_db_object(cls(context), db_provider)
        return provider

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None,
             filters=None):
        """Return a list of Provider objects.

        :param context: Security context.
        :param limit: maximum number of resources to return in a single result.
        :param marker: pagination marker for large data sets.
        :param sort_key: column to sort results by.
        :param sort_dir: direction to sort. "asc" or "desc".
        :param filters: filters when list providers, the filter name could be
                        'name', 'image', 'project_id', 'user_id', 'memory'.
                        For example, filters={'image': 'nginx'}
        :returns: a list of :class:`Provider` object.

        """
        db_providers = dbapi.list_providers(context,
                                            limit=limit,
                                            marker=marker,
                                            sort_key=sort_key,
                                            sort_dir=sort_dir,
                                            filters=filters)
        LOG.debug('get_all Provider LISt Provider xxxxxx db_providers =%s.',
                  db_providers)
        return Provider._from_db_object_list(db_providers, cls, context)

    @base.remotable_classmethod
    def list_by_host(cls, context, host):
        """Return a list of Provider objects by host.

        :param context: Security context.
        :param host: A compute host.
        :returns: a list of :class:`Provider` object.

        """
        db_providers = dbapi.list_providers(context, filters={'host': host})
        return Provider._from_db_object_list(db_providers, cls, context)

    @base.remotable
    def create(self, context):
        """Create a Provider record in the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Provider(context)

        """
        values = self.obj_get_changes()
        db_provider = dbapi.create_provider(context, values)
        self._from_db_object(self, db_provider)

    @base.remotable
    def destroy(self, context=None):
        """Delete the Provider from the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Provider(context)
        """
        dbapi.destroy_provider(context, self.uuid)
        self.obj_reset_changes()

    @base.remotable
    def save(self, context=None):
        """Save updates to this Provider.

        Updates will be made column by column based on the result
        of self.what_changed().

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Provider(context)
        """
        updates = self.obj_get_changes()
        LOG.debug('Save Provider xxxxxx uuid =%s, updates=%s', self.uuid,
                  updates)
        dbapi.update_provider(context, self.uuid, updates)

        self.obj_reset_changes()

    @base.remotable
    def refresh(self, context=None):
        """Loads updates for this Provider.

        Loads a provider with the same uuid from the database and
        checks for updated attributes. Updates are applied from
        the loaded provider column by column, if there are any updates.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Provider(context)
        """
        current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
        for field in self.fields:
            if self.obj_attr_is_set(field) and \
               getattr(self, field) != getattr(current, field):
                setattr(self, field, getattr(current, field))

    def get_sandbox_id(self):
        if self.meta:
            return self.meta.get('sandbox_id', None)
        else:
            return None

    def set_sandbox_id(self, sandbox_id):
        if self.meta is None:
            self.meta = {'sandbox_id': sandbox_id}
        else:
            self.meta['sandbox_id'] = sandbox_id
Exemple #10
0
class WatchRule(
        heat_base.HeatObject,
        base.VersionedObjectDictCompat,
):

    fields = {
        'id': fields.IntegerField(),
        'name': fields.StringField(nullable=True),
        'rule': heat_fields.JsonField(nullable=True),
        'state': fields.StringField(nullable=True),
        'last_evaluated': fields.DateTimeField(nullable=True),
        'stack_id': fields.StringField(),
        'stack': fields.ObjectField(stack.Stack),
        'watch_data': fields.ListOfObjectsField(watch_data.WatchData),
        'created_at': fields.DateTimeField(read_only=True),
        'updated_at': fields.DateTimeField(nullable=True),
    }

    @staticmethod
    def _from_db_object(context, rule, db_rule):
        for field in rule.fields:
            if field == 'stack':
                rule[field] = stack.Stack._from_db_object(
                    context, stack.Stack(), db_rule[field])
            elif field == 'watch_data':
                rule[field] = watch_data.WatchData.get_all_by_watch_rule_id(
                    context, db_rule['id'])
            else:
                rule[field] = db_rule[field]
        rule._context = context
        rule.obj_reset_changes()
        return rule

    @classmethod
    def get_by_id(cls, context, rule_id):
        db_rule = db_api.watch_rule_get(context, rule_id)
        return cls._from_db_object(context, cls(), db_rule)

    @classmethod
    def get_by_name(cls, context, watch_rule_name):
        db_rule = db_api.watch_rule_get_by_name(context, watch_rule_name)
        return cls._from_db_object(context, cls(), db_rule)

    @classmethod
    def get_all(cls, context):
        return [
            cls._from_db_object(context, cls(), db_rule)
            for db_rule in db_api.watch_rule_get_all(context)
        ]

    @classmethod
    def get_all_by_stack(cls, context, stack_id):
        return [
            cls._from_db_object(context, cls(), db_rule)
            for db_rule in db_api.watch_rule_get_all_by_stack(
                context, stack_id)
        ]

    @classmethod
    def update_by_id(cls, context, watch_id, values):
        db_api.watch_rule_update(context, watch_id, values)

    @classmethod
    def create(cls, context, values):
        return cls._from_db_object(context, cls(),
                                   db_api.watch_rule_create(context, values))

    @classmethod
    def delete(cls, context, watch_id):
        db_api.watch_rule_delete(context, watch_id)
Exemple #11
0
 class TestableObject(base.base.VersionedObject):
     fields = {
         'uuid': fields.StringField(),
     }
Exemple #12
0
class Resource(
        heat_base.HeatObject,
        base.VersionedObjectDictCompat,
        base.ComparableVersionedObject,
):
    fields = {
        'id':
        fields.IntegerField(),
        'uuid':
        fields.StringField(),
        'stack_id':
        fields.StringField(),
        'created_at':
        fields.DateTimeField(read_only=True),
        'updated_at':
        fields.DateTimeField(nullable=True),
        'nova_instance':
        fields.StringField(nullable=True),
        'name':
        fields.StringField(nullable=True),
        'status':
        fields.StringField(nullable=True),
        'status_reason':
        fields.StringField(nullable=True),
        'action':
        fields.StringField(nullable=True),
        'rsrc_metadata':
        heat_fields.JsonField(nullable=True),
        'properties_data':
        heat_fields.JsonField(nullable=True),
        'properties_data_encrypted':
        fields.BooleanField(default=False),
        'data':
        fields.ListOfObjectsField(resource_data.ResourceData, nullable=True),
        'stack':
        fields.ObjectField(stack.Stack),
        'engine_id':
        fields.StringField(nullable=True),
        'atomic_key':
        fields.IntegerField(nullable=True),
        'current_template_id':
        fields.IntegerField(),
        'needed_by':
        heat_fields.ListField(nullable=True, default=None),
        'requires':
        heat_fields.ListField(nullable=True, default=None),
        'replaces':
        fields.IntegerField(nullable=True),
        'replaced_by':
        fields.IntegerField(nullable=True),
        'root_stack_id':
        fields.StringField(nullable=True),
    }

    @staticmethod
    def _from_db_object(resource, context, db_resource):
        if db_resource is None:
            return None
        for field in resource.fields:
            if field == 'data':
                resource['data'] = [
                    resource_data.ResourceData._from_db_object(
                        resource_data.ResourceData(context), resd)
                    for resd in db_resource.data
                ]
            else:
                resource[field] = db_resource[field]

        if resource.properties_data_encrypted and resource.properties_data:
            properties_data = {}
            for prop_name, prop_value in resource.properties_data.items():
                method, value = prop_value
                decrypted_value = crypt.decrypt(method, value)
                prop_string = jsonutils.loads(decrypted_value)
                properties_data[prop_name] = prop_string
            resource.properties_data = properties_data

        resource._context = context
        resource.obj_reset_changes()
        return resource

    @classmethod
    def get_obj(cls, context, resource_id):
        resource_db = db_api.resource_get(context, resource_id)
        return cls._from_db_object(cls(context), context, resource_db)

    @classmethod
    def get_all(cls, context):
        resources_db = db_api.resource_get_all(context)
        resources = [
            (resource_name,
             cls._from_db_object(cls(context), context, resource_db))
            for resource_name, resource_db in six.iteritems(resources_db)
        ]
        return dict(resources)

    @classmethod
    def create(cls, context, values):
        return cls._from_db_object(cls(context), context,
                                   db_api.resource_create(context, values))

    @classmethod
    def delete(cls, context, resource_id):
        resource_db = db_api.resource_get(context, resource_id)
        resource_db.delete()

    @classmethod
    def exchange_stacks(cls, context, resource_id1, resource_id2):
        return db_api.resource_exchange_stacks(context, resource_id1,
                                               resource_id2)

    @classmethod
    def get_all_by_stack(cls, context, stack_id, key_id=False, filters=None):
        resources_db = db_api.resource_get_all_by_stack(
            context, stack_id, key_id, filters)
        resources = [
            (resource_key,
             cls._from_db_object(cls(context), context, resource_db))
            for resource_key, resource_db in six.iteritems(resources_db)
        ]
        return dict(resources)

    @classmethod
    def get_by_name_and_stack(cls, context, resource_name, stack_id):
        resource_db = db_api.resource_get_by_name_and_stack(
            context, resource_name, stack_id)
        return cls._from_db_object(cls(context), context, resource_db)

    @classmethod
    def get_by_physical_resource_id(cls, context, physical_resource_id):
        resource_db = db_api.resource_get_by_physical_resource_id(
            context, physical_resource_id)
        return cls._from_db_object(cls(context), context, resource_db)

    @classmethod
    def update_by_id(cls, context, resource_id, values):
        resource_db = db_api.resource_get(context, resource_id)
        resource_db.update_and_save(values)

    def update_and_save(self, values):
        resource_db = db_api.resource_get(self._context, self.id)
        resource_db.update_and_save(values)

    def select_and_update(self, values, expected_engine_id=None, atomic_key=0):
        return db_api.resource_update(self._context,
                                      self.id,
                                      values,
                                      atomic_key=atomic_key,
                                      expected_engine_id=expected_engine_id)

    def refresh(self, attrs=None):
        resource_db = db_api.resource_get(self._context, self.id)
        resource_db.refresh(attrs=attrs)
        return self.__class__._from_db_object(self, self._context, resource_db)

    @staticmethod
    def encrypt_properties_data(data):
        if cfg.CONF.encrypt_parameters_and_properties and data:
            result = {}
            for prop_name, prop_value in data.items():
                prop_string = jsonutils.dumps(prop_value)
                encrypted_value = crypt.encrypt(prop_string)
                result[prop_name] = encrypted_value
            return (True, result)
        return (False, data)

    def update_metadata(self, metadata):
        if self.rsrc_metadata != metadata:
            rows_updated = self.select_and_update({'rsrc_metadata': metadata},
                                                  self.engine_id,
                                                  self.atomic_key)
            if not rows_updated:
                action = _('metadata setting for resource %s') % self.name
                raise exception.ConcurrentTransaction(action=action)
Exemple #13
0
class VolumeAttachment(base.CinderPersistentObject, base.CinderObject,
                       base.CinderObjectDictCompat,
                       base.CinderComparableObject):
    # Version 1.0: Initial version
    # Version 1.1: Added volume relationship
    # Version 1.2: Added connection_info attribute
    VERSION = '1.2'

    OPTIONAL_FIELDS = ['volume']
    obj_extra_fields = ['project_id', 'volume_host']

    fields = {
        'id': fields.UUIDField(),
        'volume_id': fields.UUIDField(),
        'instance_uuid': fields.UUIDField(nullable=True),
        'attached_host': fields.StringField(nullable=True),
        'mountpoint': fields.StringField(nullable=True),
        'attach_time': fields.DateTimeField(nullable=True),
        'detach_time': fields.DateTimeField(nullable=True),
        'attach_status': c_fields.VolumeAttachStatusField(nullable=True),
        'attach_mode': fields.StringField(nullable=True),
        'volume': fields.ObjectField('Volume', nullable=False),
        'connection_info': c_fields.DictOfNullableField(nullable=True)
    }

    @property
    def project_id(self):
        return self.volume.project_id

    @property
    def volume_host(self):
        return self.volume.host

    @classmethod
    def _get_expected_attrs(cls, context, *args, **kwargs):
        return ['volume']

    def obj_make_compatible(self, primitive, target_version):
        """Make a object representation compatible with target version."""
        super(VolumeAttachment,
              self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 2):
            primitive.pop('connection_info', None)

    @classmethod
    def _from_db_object(cls,
                        context,
                        attachment,
                        db_attachment,
                        expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = cls._get_expected_attrs(context)

        for name, field in attachment.fields.items():
            if name in cls.OPTIONAL_FIELDS:
                continue
            value = db_attachment.get(name)
            if isinstance(field, fields.IntegerField):
                value = value or 0
            if name == 'connection_info':
                attachment.connection_info = jsonutils.loads(
                    value) if value else None
            else:
                attachment[name] = value
        if 'volume' in expected_attrs:
            db_volume = db_attachment.get('volume')
            if db_volume:
                attachment.volume = objects.Volume._from_db_object(
                    context, objects.Volume(), db_volume)

        attachment._context = context
        attachment.obj_reset_changes()
        return attachment

    def obj_load_attr(self, attrname):
        if attrname not in self.OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'volume':
            volume = objects.Volume.get_by_id(self._context, self.id)
            self.volume = volume

        self.obj_reset_changes(fields=[attrname])

    @staticmethod
    def _convert_connection_info_to_db_format(updates):
        properties = updates.pop('connection_info', None)
        if properties is not None:
            updates['connection_info'] = jsonutils.dumps(properties)

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'connection_info' in updates:
                self._convert_connection_info_to_db_format(updates)
            if 'volume' in updates:
                raise exception.ObjectActionError(action='save',
                                                  reason=_('volume changed'))

            db.volume_attachment_update(self._context, self.id, updates)
            self.obj_reset_changes()

    def finish_attach(self,
                      instance_uuid,
                      host_name,
                      mount_point,
                      attach_mode='rw'):
        with self.obj_as_admin():
            db_volume, updated_values = db.volume_attached(
                self._context, self.id, instance_uuid, host_name, mount_point,
                attach_mode)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())
        return objects.Volume._from_db_object(self._context, objects.Volume(),
                                              db_volume)

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        updates = self.cinder_obj_get_changes()
        with self.obj_as_admin():
            db_attachment = db.volume_attach(self._context, updates)
        self._from_db_object(self._context, self, db_attachment)

    def destroy(self):
        updated_values = db.attachment_destroy(self._context, self.id)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())
Exemple #14
0
class SyncPoint(
        heat_base.HeatObject,
        base.VersionedObjectDictCompat,
        base.ComparableVersionedObject,
):

    fields = {
        'entity_id': fields.StringField(),
        'traversal_id': fields.StringField(),
        'is_update': fields.BooleanField(),
        'created_at': fields.DateTimeField(read_only=True),
        'updated_at': fields.DateTimeField(nullable=True),
        'atomic_key': fields.IntegerField(),
        'stack_id': fields.StringField(),
        'input_data': heat_fields.JsonField(nullable=True),
    }

    @staticmethod
    def _from_db_object(context, sdata, db_sdata):
        if db_sdata is None:
            return None
        for field in sdata.fields:
            sdata[field] = db_sdata[field]
        sdata._context = context
        sdata.obj_reset_changes()
        return sdata

    @classmethod
    def get_by_key(cls,
                   context,
                   entity_id,
                   traversal_id,
                   is_update):
        sync_point_db = db_api.sync_point_get(context,
                                              entity_id,
                                              traversal_id,
                                              is_update)
        return cls._from_db_object(context, cls(), sync_point_db)

    @classmethod
    def create(cls, context, values):
        sync_point_db = db_api.sync_point_create(context, values)
        return cls._from_db_object(context, cls(), sync_point_db)

    @classmethod
    def update_input_data(cls,
                          context,
                          entity_id,
                          traversal_id,
                          is_update,
                          atomic_key,
                          input_data):
        return db_api.sync_point_update_input_data(
            context,
            entity_id,
            traversal_id,
            is_update,
            atomic_key,
            input_data)

    @classmethod
    def delete_all_by_stack_and_traversal(cls,
                                          context,
                                          stack_id,
                                          traversal_id):
        return db_api.sync_point_delete_all_by_stack_and_traversal(
            context,
            stack_id,
            traversal_id)
Exemple #15
0
class Agent(base.NeutronDbObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    db_model = agent_model.Agent

    fields = {
        'id': common_types.UUIDField(),
        'agent_type': obj_fields.StringField(),
        'binary': obj_fields.StringField(),
        'topic': obj_fields.StringField(),
        'host': obj_fields.StringField(),
        'availability_zone': obj_fields.StringField(nullable=True),
        'admin_state_up': obj_fields.BooleanField(default=True),
        'started_at': obj_fields.DateTimeField(tzinfo_aware=False),
        'created_at': obj_fields.DateTimeField(tzinfo_aware=False),
        'heartbeat_timestamp': obj_fields.DateTimeField(tzinfo_aware=False),
        'description': obj_fields.StringField(nullable=True),
        'configurations': common_types.DictOfMiscValuesField(),
        'resource_versions': common_types.DictOfMiscValuesField(nullable=True),
        'load': obj_fields.IntegerField(default=0),
    }

    @classmethod
    def modify_fields_to_db(cls, fields):
        result = super(Agent, cls).modify_fields_to_db(fields)
        if ('configurations' in result and not isinstance(
                result['configurations'], obj_utils.StringMatchingFilterObj)):
            # dump configuration into string, set '' if empty '{}'
            result['configurations'] = (cls.filter_to_json_str(
                result['configurations'], default=''))
        if ('resource_versions' in result
                and not isinstance(result['resource_versions'],
                                   obj_utils.StringMatchingFilterObj)):
            # dump resource version into string, set None if empty '{}' or None
            result['resource_versions'] = (cls.filter_to_json_str(
                result['resource_versions']))
        return result

    @classmethod
    def modify_fields_from_db(cls, db_obj):
        fields = super(Agent, cls).modify_fields_from_db(db_obj)
        if 'configurations' in fields:
            # load string from DB, set {} if configuration is ''
            fields['configurations'] = (cls.load_json_from_str(
                fields['configurations'], default={}))
        if 'resource_versions' in fields:
            # load string from DB, set None if resource_version is None or ''
            fields['resource_versions'] = (cls.load_json_from_str(
                fields['resource_versions']))
        return fields

    @property
    def is_active(self):
        return not utils.is_agent_down(self.heartbeat_timestamp)

    # TODO(ihrachys) reuse query builder from
    # get_l3_agents_ordered_by_num_routers
    @classmethod
    def get_l3_agent_with_min_routers(cls, context, agent_ids):
        """Return l3 agent with the least number of routers."""
        with context.session.begin(subtransactions=True):
            query = context.session.query(
                agent_model.Agent,
                func.count(
                    rb_model.RouterL3AgentBinding.router_id).label('count')
            ).outerjoin(rb_model.RouterL3AgentBinding).group_by(
                agent_model.Agent.id,
                rb_model.RouterL3AgentBinding.l3_agent_id).order_by('count')
            res = query.filter(agent_model.Agent.id.in_(agent_ids)).first()
        agent_obj = cls._load_object(context, res[0])
        return agent_obj

    @classmethod
    def get_l3_agents_ordered_by_num_routers(cls, context, agent_ids):
        with context.session.begin(subtransactions=True):
            query = (context.session.query(
                agent_model.Agent,
                func.count(
                    rb_model.RouterL3AgentBinding.router_id).label('count')
            ).outerjoin(rb_model.RouterL3AgentBinding).group_by(
                agent_model.Agent.id).filter(
                    agent_model.Agent.id.in_(agent_ids)).order_by('count'))
        agents = [cls._load_object(context, record[0]) for record in query]

        return agents

    @classmethod
    def get_ha_agents(cls, context, network_id=None, router_id=None):
        if not (network_id or router_id):
            return []
        query = context.session.query(agent_model.Agent.host)
        query = query.join(
            l3ha_model.L3HARouterAgentPortBinding,
            l3ha_model.L3HARouterAgentPortBinding.l3_agent_id ==
            agent_model.Agent.id)
        if router_id:
            query = query.filter(l3ha_model.L3HARouterAgentPortBinding.
                                 router_id == router_id).all()
        elif network_id:
            query = query.join(
                models_v2.Port, models_v2.Port.device_id ==
                l3ha_model.L3HARouterAgentPortBinding.router_id)
            query = query.filter(
                models_v2.Port.network_id == network_id,
                models_v2.Port.status == const.PORT_STATUS_ACTIVE,
                models_v2.Port.device_owner.in_(
                    (const.DEVICE_OWNER_HA_REPLICATED_INT,
                     const.DEVICE_OWNER_ROUTER_SNAT))).all()
        # L3HARouterAgentPortBinding will have l3 agent ids of hosting agents.
        # But we need l2 agent(for tunneling ip) while creating FDB entries.
        hosts = [host[0] for host in query]
        agents = cls.get_objects(context, host=hosts)
        return agents

    @classmethod
    def _get_agents_by_availability_zones_and_agent_type(
            cls, context, agent_type, availability_zones):
        query = context.session.query(
            agent_model.Agent).filter_by(agent_type=agent_type).group_by(
                agent_model.Agent.availability_zone)
        query = query.filter(
            agent_model.Agent.availability_zone.in_(availability_zones)).all()
        agents = [cls._load_object(context, record) for record in query]
        return agents

    @classmethod
    def get_objects_by_agent_mode(cls, context, agent_mode=None, **kwargs):
        mode_filter = obj_utils.StringContains(agent_mode)
        return cls.get_objects(context, configurations=mode_filter, **kwargs)
Exemple #16
0
class BaremetalNode(drydock_provisioner.objects.hostprofile.HostProfile):

    VERSION = '1.0'

    fields = {
        'addressing': ovo_fields.ObjectField('IpAddressAssignmentList'),
        'boot_mac': ovo_fields.StringField(nullable=True),
    }

    # A BaremetalNode is really nothing more than a physical
    # instantiation of a HostProfile, so they both represent
    # the same set of CIs
    def __init__(self, **kwargs):
        super(BaremetalNode, self).__init__(**kwargs)
        self.logicalnames = {}
        self.logger = logging.getLogger(
            config.config_mgr.conf.logging.global_logger_name)

    # Compile the applied version of this model sourcing referenced
    # data from the passed site design
    def compile_applied_model(self, site_design, state_manager):
        self.logger.debug("Applying host profile to node %s" % self.name)
        self.apply_host_profile(site_design)
        self.logger.debug("Applying hardware profile to node %s" % self.name)
        self.apply_hardware_profile(site_design)
        self.source = hd_fields.ModelSource.Compiled
        self.logger.debug("Resolving kernel parameters on node %s" % self.name)
        self.resolve_kernel_params(site_design)
        self.logger.debug("Resolving device aliases on node %s" % self.name)
        self.apply_logicalnames(site_design, state_manager)
        return

    def apply_host_profile(self, site_design):
        self.apply_inheritance(site_design)
        return

    # Translate device aliases to physical selectors and copy
    # other hardware attributes into this object
    def apply_hardware_profile(self, site_design):
        if self.hardware_profile is None:
            raise ValueError("Hardware profile not set")

        hw_profile = site_design.get_hardware_profile(self.hardware_profile)

        for i in getattr(self, 'interfaces', []):
            for s in i.get_hw_slaves():
                selector = hw_profile.resolve_alias("pci", s)
                if selector is None:
                    selector = objects.HardwareDeviceSelector()
                    selector.selector_type = 'name'
                    selector.address = s

                i.add_selector(selector)

        for p in getattr(self, 'partitions', []):
            selector = hw_profile.resolve_alias("scsi", p.get_device())
            if selector is None:
                selector = objects.HardwareDeviceSelector()
                selector.selector_type = 'name'
                selector.address = p.get_device()
            p.set_selector(selector)

        return

    def resolve_kernel_params(self, site_design):
        """Check if any kernel parameter values are supported references."""
        if not self.hardware_profile:
            raise ValueError("Hardware profile not set.")

        hwprof = site_design.get_hardware_profile(self.hardware_profile)

        if not hwprof:
            raise ValueError("Hardware profile not found.")

        resolved_params = dict()
        for p, v in self.kernel_params.items():
            try:
                rv = self.get_kernel_param_value(v, hwprof)
                resolved_params[p] = rv
            except (errors.InvalidParameterReference, errors.CpuSetNotFound,
                    errors.HugepageConfNotFound) as ex:
                resolved_params[p] = v
                msg = ("Error resolving parameter reference on node %s: %s" %
                       (self.name, str(ex)))
                self.logger.warning(msg)

        self.kernel_params = resolved_params

    def get_kernel_param_value(self, value, hwprof):
        """If ``value`` is a reference, resolve it otherwise return ``value``

        Support some referential values to extract data from the HardwareProfile

        hardwareprofile:cpuset.<setname>
        hardwareprofile:hugepages.<confname>.size
        hardwareprofile:hugepages.<confname>.count

        If ``value`` matches none of the above forms, just return the value as passed.

        :param value: the value string as specified in the node definition
        :param hwprof: the assigned HardwareProfile for this node
        """
        if value.startswith('hardwareprofile:'):
            (_, ref) = value.split(':', 1)
            if ref:
                (ref_type, ref_val) = ref.split('.', 1)
                if ref_type == 'cpuset':
                    return hwprof.get_cpu_set(ref_val)
                elif ref_type == 'hugepages':
                    (conf, field) = ref_val.split('.', 1)
                    hp_conf = hwprof.get_hugepage_conf(conf)
                    if field in ['size', 'count']:
                        return getattr(hp_conf, field)
                    else:
                        raise errors.InvalidParameterReference(
                            "Invalid field %s specified." % field)
                else:
                    raise errors.InvalidParameterReference(
                        "Invalid configuration %s specified." % ref_type)
            else:
                return value

        else:
            return value

    def get_applied_interface(self, iface_name):
        for i in getattr(self, 'interfaces', []):
            if i.get_name() == iface_name:
                return i

        return None

    def get_network_address(self, network_name):
        for a in getattr(self, 'addressing', []):
            if a.network == network_name:
                return a.address

        return None

    def find_fs_block_device(self, fs_mount=None):
        if not fs_mount:
            return (None, None)

        if self.volume_groups is not None:
            for vg in self.volume_groups:
                if vg.logical_volumes is not None:
                    for lv in vg.logical_volumes:
                        if lv.mountpoint is not None and lv.mountpoint == fs_mount:
                            return (vg, lv)
        if self.storage_devices is not None:
            for sd in self.storage_devices:
                if sd.partitions is not None:
                    for p in sd.partitions:
                        if p.mountpoint is not None and p.mountpoint == fs_mount:
                            return (sd, p)
        return (None, None)

    def _apply_logicalname(self, xml_root, alias_name, bus_type, address):
        """Given xml_data, checks for a matching businfo and returns the logicalname

        :param xml_root: Parsed ElementTree, it is searched for the logicalname.
        :param alias_name: String value of the current device alias, it is returned
                           if a logicalname is not found.
        :param bus_type: String value that is used to find the logicalname.
        :param address: String value that is used to find the logicalname.
        :return: String value of the logicalname or the alias_name if logicalname is not found.
        """
        nodes = xml_root.findall(".//node[businfo='" + bus_type + "@" +
                                 address + "'].logicalname")
        if len(nodes) >= 1 and nodes[0].text:
            if (len(nodes) > 1):
                self.logger.info("Multiple nodes found for businfo=%s@%s" %
                                 (bus_type, address))
            for logicalname in reversed(nodes[0].text.split("/")):
                self.logger.debug(
                    "Logicalname build dict: alias_name = %s, bus_type = %s, address = %s, "
                    "to logicalname = %s" %
                    (alias_name, bus_type, address, logicalname))
                return logicalname
        self.logger.debug(
            "Logicalname build dict: alias_name = %s, bus_type = %s, address = %s, not found"
            % (alias_name, bus_type, address))
        return alias_name

    def apply_logicalnames(self, site_design, state_manager):
        """Gets the logicalnames for devices from lshw.

        :param site_design: SiteDesign object.
        :param state_manager: DrydockState object.
        :return: Returns sets a dictionary of aliases that map to logicalnames in self.logicalnames.
        """
        logicalnames = {}

        results = state_manager.get_build_data(node_name=self.get_name(),
                                               latest=True)
        xml_data = None
        for result in results:
            if result.generator == "lshw":
                xml_data = result.data_element
                break

        if xml_data:
            xml_root = fromstring(xml_data)
            for hardware_profile in site_design.hardware_profiles:
                for device in hardware_profile.devices:
                    logicalname = self._apply_logicalname(
                        xml_root, device.alias, device.bus_type,
                        device.address)
                    logicalnames[device.alias] = logicalname
        else:
            self.logger.info("No Build Data found for node_name %s" %
                             (self.get_name()))

        self.logicalnames = logicalnames

    def get_logicalname(self, alias):
        """Gets the logicalname from self.logicalnames for an alias or returns the alias if not in the dictionary.
        """
        if (self.logicalnames and self.logicalnames.get(alias)):
            self.logger.debug("Logicalname input = %s with output %s." %
                              (alias, self.logicalnames[alias]))
            return self.logicalnames[alias]
        else:
            self.logger.debug(
                "Logicalname input = %s not in logicalnames dictionary." %
                alias)
            return alias
Exemple #17
0
class Port(base.NeutronDbObject):
    # Version 1.0: Initial version
    # Version 1.1: Add data_plane_status field
    # Version 1.2: Added segment_id to binding_levels
    # Version 1.3: distributed_binding -> distributed_bindings
    # Version 1.4: Attribute binding becomes ListOfObjectsField
    # Version 1.5: Added qos_network_policy_id field
    # Version 1.6: Added numa_affinity_policy field
    # Version 1.7: Added port_device field
    VERSION = '1.7'

    db_model = models_v2.Port

    fields = {
        'id': common_types.UUIDField(),
        'project_id': obj_fields.StringField(nullable=True),
        'name': obj_fields.StringField(nullable=True),
        'network_id': common_types.UUIDField(),
        'mac_address': common_types.MACAddressField(),
        'admin_state_up': obj_fields.BooleanField(),
        'device_id': obj_fields.StringField(),
        'device_owner': obj_fields.StringField(),
        'status': obj_fields.StringField(),

        'allowed_address_pairs': obj_fields.ListOfObjectsField(
            'AllowedAddressPair', nullable=True
        ),
        'bindings': obj_fields.ListOfObjectsField(
            'PortBinding', nullable=True
        ),
        'data_plane_status': obj_fields.ObjectField(
            'PortDataPlaneStatus', nullable=True
        ),
        'dhcp_options': obj_fields.ListOfObjectsField(
            'ExtraDhcpOpt', nullable=True
        ),
        'distributed_bindings': obj_fields.ListOfObjectsField(
            'DistributedPortBinding', nullable=True
        ),
        'dns': obj_fields.ObjectField('PortDNS', nullable=True),
        'fixed_ips': obj_fields.ListOfObjectsField(
            'IPAllocation', nullable=True
        ),
        # TODO(ihrachys): consider converting to boolean
        'security': obj_fields.ObjectField(
            'PortSecurity', nullable=True
        ),
        'security_group_ids': common_types.SetOfUUIDsField(
            nullable=True,
            # TODO(ihrachys): how do we safely pass a mutable default?
            default=None,
        ),
        'qos_policy_id': common_types.UUIDField(nullable=True, default=None),
        'qos_network_policy_id': common_types.UUIDField(nullable=True,
                                                        default=None),

        'binding_levels': obj_fields.ListOfObjectsField(
            'PortBindingLevel', nullable=True
        ),
        'numa_affinity_policy': obj_fields.StringField(nullable=True),
        'device_profile': obj_fields.StringField(nullable=True),

        # TODO(ihrachys): consider adding a 'dns_assignment' fully synthetic
        # field in later object iterations
    }

    extra_filter_names = {'security_group_ids'}

    fields_no_update = ['project_id', 'network_id']

    synthetic_fields = [
        'allowed_address_pairs',
        'bindings',
        'binding_levels',
        'data_plane_status',
        'device_profile',
        'dhcp_options',
        'distributed_bindings',
        'dns',
        'fixed_ips',
        'numa_affinity_policy',
        'qos_policy_id',
        'qos_network_policy_id',
        'security',
        'security_group_ids',
    ]

    fields_need_translation = {
        'bindings': 'port_bindings',
        'dhcp_options': 'dhcp_opts',
        'distributed_bindings': 'distributed_port_binding',
        'security': 'port_security',
    }

    def create(self):
        fields = self.obj_get_changes()
        with self.db_context_writer(self.obj_context):
            sg_ids = self.security_group_ids
            if sg_ids is None:
                sg_ids = set()
            qos_policy_id = self.qos_policy_id
            super(Port, self).create()
            if 'security_group_ids' in fields:
                self._attach_security_groups(sg_ids)
            if 'qos_policy_id' in fields:
                self._attach_qos_policy(qos_policy_id)

    def update(self):
        fields = self.obj_get_changes()
        with self.db_context_writer(self.obj_context):
            super(Port, self).update()
            if 'security_group_ids' in fields:
                self._attach_security_groups(fields['security_group_ids'])
            if 'qos_policy_id' in fields:
                self._attach_qos_policy(fields['qos_policy_id'])

    def _attach_qos_policy(self, qos_policy_id):
        binding.QosPolicyPortBinding.delete_objects(
            self.obj_context, port_id=self.id)
        if qos_policy_id:
            port_binding_obj = binding.QosPolicyPortBinding(
                self.obj_context, policy_id=qos_policy_id, port_id=self.id)
            port_binding_obj.create()

        self.qos_policy_id = qos_policy_id
        self.obj_reset_changes(['qos_policy_id'])

    def _attach_security_groups(self, sg_ids):
        # TODO(ihrachys): consider introducing an (internal) object for the
        # binding to decouple database operations a bit more
        obj_db_api.delete_objects(
            SecurityGroupPortBinding, self.obj_context, port_id=self.id)
        if sg_ids:
            for sg_id in sg_ids:
                self._attach_security_group(sg_id)
        self.security_group_ids = sg_ids
        self.obj_reset_changes(['security_group_ids'])

    def _attach_security_group(self, sg_id):
        obj_db_api.create_object(
            SecurityGroupPortBinding, self.obj_context,
            {'port_id': self.id, 'security_group_id': sg_id}
        )

    @classmethod
    def get_objects(cls, context, _pager=None, validate_filters=True,
                    security_group_ids=None, **kwargs):
        if security_group_ids:
            ports_with_sg = cls.get_ports_ids_by_security_groups(
                context, security_group_ids)
            port_ids = kwargs.get("id", [])
            if port_ids:
                kwargs['id'] = list(set(port_ids) & set(ports_with_sg))
            else:
                kwargs['id'] = ports_with_sg
        port_array = super(Port, cls).get_objects(context, _pager,
                                                  validate_filters,
                                                  **kwargs)
        sg_count = len(security_group_ids) if security_group_ids else 0
        LOG.debug("Time-cost: Fetching %(port_count)s ports in %(sg_count)s "
                  "security groups",
                  {'port_count': len(port_array),
                   'sg_count': sg_count})
        return port_array

    @classmethod
    @db_api.CONTEXT_READER
    def get_auto_deletable_port_ids_and_proper_port_count_by_segment(
            cls, context, segment_id):

        query = context.session.query(models_v2.Port.id)
        query = query.join(
            ml2_models.PortBindingLevel,
            ml2_models.PortBindingLevel.port_id == models_v2.Port.id)
        query = query.filter(
            ml2_models.PortBindingLevel.segment_id == segment_id)

        q_delete = query.filter(
            models_v2.Port.device_owner.in_(
                _constants.AUTO_DELETE_PORT_OWNERS))

        q_proper = query.filter(
            ~models_v2.Port.device_owner.in_(
                _constants.AUTO_DELETE_PORT_OWNERS))

        return ([r.id for r in q_delete.all()], q_proper.count())

    @classmethod
    def modify_fields_to_db(cls, fields):
        result = super(Port, cls).modify_fields_to_db(fields)

        # TODO(rossella_s): get rid of it once we switch the db model to using
        # custom types.
        if 'mac_address' in result:
            result['mac_address'] = cls.filter_to_str(result['mac_address'])

        # convert None to []
        if 'distributed_port_binding' in result:
            result['distributed_port_binding'] = (
                result['distributed_port_binding'] or []
            )
        return result

    @classmethod
    def modify_fields_from_db(cls, db_obj):
        fields = super(Port, cls).modify_fields_from_db(db_obj)

        # TODO(rossella_s): get rid of it once we switch the db model to using
        # custom types.
        if 'mac_address' in fields:
            fields['mac_address'] = net_utils.AuthenticEUI(
                fields['mac_address'])

        distributed_port_binding = fields.get('distributed_bindings')
        if distributed_port_binding:
            # TODO(ihrachys) support multiple bindings
            fields['distributed_bindings'] = fields['distributed_bindings'][0]
        else:
            fields['distributed_bindings'] = []
        return fields

    def from_db_object(self, db_obj):
        super(Port, self).from_db_object(db_obj)
        # extract security group bindings
        if db_obj.get('security_groups', []):
            self.security_group_ids = {
                sg.security_group_id
                for sg in db_obj.security_groups
            }
        else:
            self.security_group_ids = set()
        fields_to_change = ['security_group_ids']

        # extract qos policy binding
        if db_obj.get('qos_policy_binding'):
            self.qos_policy_id = db_obj.qos_policy_binding.policy_id
            fields_to_change.append('qos_policy_id')
        if db_obj.get('qos_network_policy_binding'):
            self.qos_network_policy_id = (
                db_obj.qos_network_policy_binding.policy_id)
            fields_to_change.append('qos_network_policy_binding')

        if db_obj.get('numa_affinity_policy'):
            self.numa_affinity_policy = (
                db_obj.numa_affinity_policy.numa_affinity_policy)
            fields_to_change.append('numa_affinity_policy')

        if db_obj.get('device_profile'):
            self.device_profile = db_obj.device_profile.device_profile
            fields_to_change.append('device_profile')

        self.obj_reset_changes(fields_to_change)

    def obj_make_compatible(self, primitive, target_version):
        _target_version = versionutils.convert_version_to_tuple(target_version)
        if _target_version < (1, 2):
            binding_levels = primitive.get('binding_levels', [])
            for lvl in binding_levels:
                lvl['versioned_object.version'] = '1.0'
                lvl['versioned_object.data'].pop('segment_id', None)
        if _target_version < (1, 3):
            bindings = primitive.pop('distributed_bindings', [])
            primitive['distributed_binding'] = (bindings[0]
                                                if bindings else None)
        if _target_version < (1, 4):
            # In version 1.4 we add support for multiple port bindings.
            # Previous versions only support one port binding. The following
            # lines look for the active port binding, which is the only one
            # needed in previous versions
            if 'bindings' in primitive:
                original_bindings = primitive.pop('bindings')
                primitive['binding'] = None
                for a_binding in original_bindings:
                    if (a_binding['versioned_object.data']['status'] ==
                            constants.ACTIVE):
                        primitive['binding'] = a_binding
                        break
        if _target_version < (1, 5):
            primitive.pop('qos_network_policy_id', None)
        if _target_version < (1, 6):
            primitive.pop('numa_affinity_policy', None)
        if _target_version < (1, 7):
            primitive.pop('device_profile', None)

    @classmethod
    @db_api.CONTEXT_READER
    def get_ports_by_router_and_network(cls, context, router_id, owner,
                                        network_id):
        """Returns port objects filtering by router ID, owner and network ID"""
        rports_filter = (models_v2.Port.network_id == network_id, )
        router_filter = (models_v2.Port.network_id == network_id, )
        return cls._get_ports_by_router(context, router_id, owner,
                                        rports_filter, router_filter)

    @classmethod
    @db_api.CONTEXT_READER
    def get_ports_by_router_and_port(cls, context, router_id, owner, port_id):
        """Returns port objects filtering by router ID, owner and port ID"""
        rports_filter = (l3.RouterPort.port_id == port_id, )
        router_filter = (models_v2.Port.id == port_id, )
        return cls._get_ports_by_router(context, router_id, owner,
                                        rports_filter, router_filter)

    @classmethod
    def _get_ports_by_router(cls, context, router_id, owner, rports_filter,
                             router_filter):
        """Returns port objects filtering by router id and owner

        The method will receive extra filters depending of the caller (filter
        by network or filter by port).

        The ports are retrieved using:
        - The RouterPort registers. Each time a port is assigned to a router,
          a new RouterPort register is added to the DB.
        - The port owner and device_id information.

        Both searches should return the same result. If not, a warning message
        is logged and the port list to be returned is completed with the
        missing ones.
        """
        rports_filter += (l3.RouterPort.router_id == router_id,
                          l3.RouterPort.port_type == owner)
        router_filter += (models_v2.Port.device_id == router_id,
                          models_v2.Port.device_owner == owner)

        ports = context.session.query(models_v2.Port).join(
            l3.RouterPort).filter(*rports_filter)
        ports_rports = [cls._load_object(context, db_obj)
                        for db_obj in ports.all()]

        ports = context.session.query(models_v2.Port).filter(*router_filter)
        ports_router = [cls._load_object(context, db_obj)
                        for db_obj in ports.all()]

        ports_rports_ids = {p.id for p in ports_rports}
        ports_router_ids = {p.id for p in ports_router}
        missing_port_ids = ports_router_ids - ports_rports_ids
        if missing_port_ids:
            LOG.warning('The following ports, assigned to router '
                        '%(router_id)s, do not have a "routerport" register: '
                        '%(port_ids)s', {'router_id': router_id,
                                         'port_ids': missing_port_ids})
            port_objs = [p for p in ports_router if p.id in missing_port_ids]
            ports_rports += port_objs

        return ports_rports

    @classmethod
    @db_api.CONTEXT_READER
    def get_ports_ids_by_security_groups(cls, context, security_group_ids,
                                         excluded_device_owners=None):
        query = context.session.query(sg_models.SecurityGroupPortBinding)
        query = query.filter(
            sg_models.SecurityGroupPortBinding.security_group_id.in_(
                security_group_ids))
        if excluded_device_owners:
            query = query.join(models_v2.Port)
            query = query.filter(
                ~models_v2.Port.device_owner.in_(excluded_device_owners))
        return [port_binding['port_id'] for port_binding in query.all()]

    @classmethod
    @db_api.CONTEXT_READER
    def get_ports_by_host(cls, context, host):
        query = context.session.query(models_v2.Port.id).join(
            ml2_models.PortBinding)
        query = query.filter(
            ml2_models.PortBinding.host == host)
        return [port_id[0] for port_id in query.all()]

    @classmethod
    @db_api.CONTEXT_READER
    def get_ports_by_binding_type_and_host(cls, context,
                                           binding_type, host):
        query = context.session.query(models_v2.Port).join(
            ml2_models.PortBinding)
        query = query.filter(
            ml2_models.PortBinding.vif_type == binding_type,
            ml2_models.PortBinding.host == host)
        return [cls._load_object(context, db_obj) for db_obj in query.all()]

    @classmethod
    @db_api.CONTEXT_READER
    def get_ports_by_vnic_type_and_host(
            cls, context, vnic_type, host):
        query = context.session.query(models_v2.Port).join(
            ml2_models.PortBinding)
        query = query.filter(
            ml2_models.PortBinding.vnic_type == vnic_type,
            ml2_models.PortBinding.host == host)
        return [cls._load_object(context, db_obj) for db_obj in query.all()]

    @classmethod
    @db_api.CONTEXT_READER
    def check_network_ports_by_binding_types(
            cls, context, network_id, binding_types, negative_search=False):
        """This method is to check whether networks have ports with given
        binding_types.

        :param context:
        :param network_id: ID of network to check
        :param binding_types: list of binding types to look for
        :param negative_search: if set to true, ports with with binding_type
                                other than "binding_types" will be counted
        :return: True if any port is found, False otherwise
        """
        query = context.session.query(models_v2.Port).join(
            ml2_models.PortBinding)
        query = query.filter(models_v2.Port.network_id == network_id)
        if negative_search:
            query = query.filter(
                ml2_models.PortBinding.vif_type.notin_(binding_types))
        else:
            query = query.filter(
                ml2_models.PortBinding.vif_type.in_(binding_types))
        return bool(query.count())

    @classmethod
    @db_api.CONTEXT_READER
    def get_ports_allocated_by_subnet_id(cls, context, subnet_id):
        """Return ports with fixed IPs in a subnet"""
        return context.session.query(models_v2.Port).filter(
            models_v2.IPAllocation.port_id == models_v2.Port.id).filter(
            models_v2.IPAllocation.subnet_id == subnet_id).all()

    @classmethod
    def get_port_from_mac_and_pci_slot(cls, context, device_mac,
                                       pci_slot=None):
        with db_api.CONTEXT_READER.using(context):
            ports = cls.get_objects(context, mac_address=device_mac)

        if not ports:
            return
        elif not pci_slot:
            return ports.pop()
        else:
            for port in ports:
                for _binding in port.bindings:
                    if _binding.get('profile', {}).get('pci_slot') == pci_slot:
                        return port

    @classmethod
    @db_api.CONTEXT_READER
    def get_gateway_port_ids_by_network(cls, context, network_id):
        gw_ports = context.session.query(models_v2.Port.id).filter_by(
            device_owner=constants.DEVICE_OWNER_ROUTER_GW,
            network_id=network_id)
        return [gw_port[0] for gw_port in gw_ports]
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from oslo_versionedobjects import fields as obj_fields

STANDARD_ATTRIBUTES = {
    'description': obj_fields.StringField(),
    'created_at': obj_fields.DateTimeField(nullable=True, tzinfo_aware=False),
    'updated_at': obj_fields.DateTimeField(nullable=True, tzinfo_aware=False),
}


def add_standard_attributes(cls):
    # Don't use parent's fields in case child class doesn't create
    # its own instance of list
    cls.fields = cls.fields.copy()
    cls.fields.update(STANDARD_ATTRIBUTES)
Exemple #19
0
class QualityOfServiceSpecs(base.CinderPersistentObject, base.CinderObject,
                            base.CinderObjectDictCompat,
                            base.CinderComparableObject):
    # Version
    #   1.0: Initial version
    VERSION = "1.0"

    OPTIONAL_FIELDS = ['volume_types']

    # NOTE: When adding a field obj_make_compatible needs to be updated
    fields = {
        'id':
        fields.UUIDField(),
        'name':
        fields.StringField(),
        'consumer':
        c_fields.QoSConsumerField(default=c_fields.QoSConsumerValues.BACK_END),
        'specs':
        fields.DictOfNullableStringsField(nullable=True),
        'volume_types':
        fields.ObjectField('VolumeTypeList', nullable=True),
    }

    def __init__(self, *args, **kwargs):
        super(QualityOfServiceSpecs, self).__init__(*args, **kwargs)
        self._init_specs = {}

    def __setattr__(self, name, value):
        try:
            super(QualityOfServiceSpecs, self).__setattr__(name, value)
        except ValueError:
            if name == 'consumer':
                # Give more descriptive error message for invalid 'consumer'
                msg = (_("Valid consumer of QoS specs are: %s") %
                       c_fields.QoSConsumerField())
                raise exception.InvalidQoSSpecs(reason=msg)
            else:
                raise

    def obj_reset_changes(self, fields=None, recursive=False):
        super(QualityOfServiceSpecs, self).obj_reset_changes(fields, recursive)
        if fields is None or 'specs' in fields:
            self._init_specs = self.specs.copy() if self.specs else {}

    def obj_what_changed(self):
        changes = super(QualityOfServiceSpecs, self).obj_what_changed()

        # Do comparison of what's in the dict vs. reference to the specs object
        if self.obj_attr_is_set('id'):
            if self.specs != self._init_specs:
                changes.add('specs')
            else:
                # If both dicts are equal don't consider anything gets changed
                if 'specs' in changes:
                    changes.remove('specs')

        return changes

    def obj_get_changes(self):
        changes = super(QualityOfServiceSpecs, self).obj_get_changes()
        if 'specs' in changes:
            # For specs, we only want what has changed in the dictionary,
            # because otherwise we'll individually overwrite the DB value for
            # every key in 'specs' even if it hasn't changed
            specs_changes = {}
            for key, val in self.specs.items():
                if val != self._init_specs.get(key):
                    specs_changes[key] = val
            changes['specs'] = specs_changes

            specs_keys_removed = (set(self._init_specs.keys()) -
                                  set(self.specs.keys()))
            if specs_keys_removed:
                # Special key notifying which specs keys have been deleted
                changes['specs_keys_removed'] = specs_keys_removed

        return changes

    def obj_load_attr(self, attrname):
        if attrname not in self.OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'volume_types':
            self.volume_types = objects.VolumeTypeList.get_all_types_for_qos(
                self._context, self.id)

    @classmethod
    def _from_db_object(cls,
                        context,
                        qos_spec,
                        db_qos_spec,
                        expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []

        for name, field in qos_spec.fields.items():
            if name not in cls.OPTIONAL_FIELDS:
                value = db_qos_spec.get(name)
                # 'specs' could be null if only a consumer is given, so make
                # it an empty dict instead of None
                if not value and isinstance(field, fields.DictOfStringsField):
                    value = {}
                setattr(qos_spec, name, value)

        if 'volume_types' in expected_attrs:
            volume_types = objects.VolumeTypeList.get_all_types_for_qos(
                context, db_qos_spec['id'])
            qos_spec.volume_types = volume_types

        qos_spec._context = context
        qos_spec.obj_reset_changes()
        return qos_spec

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        updates = self.cinder_obj_get_changes()

        try:
            create_ret = db.qos_specs_create(self._context, updates)
        except db_exc.DBDataError:
            msg = _('Error writing field to database')
            LOG.exception(msg)
            raise exception.Invalid(msg)
        except db_exc.DBError:
            LOG.exception('DB error occurred when creating QoS specs.')
            raise exception.QoSSpecsCreateFailed(name=self.name,
                                                 qos_specs=self.specs)
        # Save ID with the object
        updates['id'] = create_ret['id']
        self._from_db_object(self._context, self, updates)

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'specs_keys_removed' in updates.keys():
                for specs_key_to_remove in updates['specs_keys_removed']:
                    db.qos_specs_item_delete(self._context, self.id,
                                             specs_key_to_remove)
                del updates['specs_keys_removed']
            db.qos_specs_update(self._context, self.id, updates)

        self.obj_reset_changes()

    def destroy(self, force=False):
        """Deletes the QoS spec.

        :param force: when force is True, all volume_type mappings for this QoS
                      are deleted.  When force is False and volume_type
                      mappings still exist, a QoSSpecsInUse exception is thrown
        """
        if self.volume_types:
            if not force:
                raise exception.QoSSpecsInUse(specs_id=self.id)
            # remove all association
            db.qos_specs_disassociate_all(self._context, self.id)
        updated_values = db.qos_specs_delete(self._context, self.id)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())
Exemple #20
0
class Service(base.MagnumPersistentObject, base.MagnumObject,
              base.MagnumObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'id': fields.IntegerField(),
        'uuid': fields.StringField(nullable=True),
        'name': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'bay_uuid': fields.StringField(nullable=True),
        'labels': fields.DictOfStringsField(nullable=True),
        'selector': fields.DictOfStringsField(nullable=True),
        'ip': fields.StringField(nullable=True),
        'ports': magnum_fields.ListOfDictsField(nullable=True),
        'manifest_url': fields.StringField(nullable=True),
        'manifest': fields.StringField(nullable=True),
    }

    @staticmethod
    def _from_db_object(service, db_service):
        """Converts a database entity to a formal object."""
        for field in service.fields:
            # ignore manifest_url as it was used for create service
            if field == 'manifest_url':
                continue
            if field == 'manifest':
                continue
            service[field] = db_service[field]

        service.obj_reset_changes()
        return service

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [
            Service._from_db_object(cls(context), obj) for obj in db_objects
        ]

    @base.remotable_classmethod
    def get_by_id(cls, context, service_id):
        """Find a service based on its integer id and return a Service object.

        :param service_id: the id of a service.
        :returns: a :class:`Service` object.
        """
        db_service = cls.dbapi.get_service_by_id(context, service_id)
        service = Service._from_db_object(cls(context), db_service)
        return service

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a service based on uuid and return a :class:`Service` object.

        :param uuid: the uuid of a service.
        :param context: Security context
        :returns: a :class:`Service` object.
        """
        db_service = cls.dbapi.get_service_by_uuid(context, uuid)
        service = Service._from_db_object(cls(context), db_service)
        return service

    @base.remotable_classmethod
    def get_by_name(cls, context, name):
        """Find a service based on service name and
        return a :class:`Service` object.

        :param name: the name of a service.
        :param context: Security context
        :returns: a :class:`Service` object.
        """
        db_service = cls.dbapi.get_service_by_name(context, name)
        service = Service._from_db_object(cls(context), db_service)
        return service

    @base.remotable_classmethod
    def list_by_bay_uuid(cls, context, bay_uuid):
        """Return a list of :class:`Service` objects associated with a given bay.

        :param bay_uuid: the uuid of a bay.
        :param context: Security context
        :returns: a list of class:`Service` object.
        """
        db_services = cls.dbapi.get_services_by_bay_uuid(context, bay_uuid)
        return Service._from_db_object_list(db_services, cls, context)

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None):
        """Return a list of Service objects.

        :param context: Security context.
        :param limit: maximum number of resources to return in a single result.
        :param marker: pagination marker for large data sets.
        :param sort_key: column to sort results by.
        :param sort_dir: direction to sort. "asc" or "desc".
        :returns: a list of :class:`Service` object.

        """
        db_services = cls.dbapi.get_service_list(context,
                                                 limit=limit,
                                                 marker=marker,
                                                 sort_key=sort_key,
                                                 sort_dir=sort_dir)
        return Service._from_db_object_list(db_services, cls, context)

    @base.remotable
    def create(self, context=None):
        """Create a Service record in the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Service(context)

        """
        values = self.obj_get_changes()
        db_service = self.dbapi.create_service(values)
        self._from_db_object(self, db_service)

    @base.remotable
    def destroy(self, context=None):
        """Delete the Service from the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Service(context)
        """
        self.dbapi.destroy_service(self.uuid)
        self.obj_reset_changes()

    @base.remotable
    def save(self, context=None):
        """Save updates to this Service.

        Updates will be made column by column based on the result
        of self.what_changed().

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Service(context)
        """
        updates = self.obj_get_changes()
        self.dbapi.update_service(self.uuid, updates)

        self.obj_reset_changes()

    @base.remotable
    def refresh(self, context=None):
        """Loads updates for this Service.

        Loads a service with the same uuid from the database and
        checks for updated attributes. Updates are applied from
        the loaded service column by column, if there are any updates.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Service(context)
        """
        current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
        for field in self.fields:
            if field == 'manifest_url':
                continue
            if field == 'manifest':
                continue
            if self.obj_attr_is_set(field) and self[field] != current[field]:
                self[field] = current[field]
Exemple #21
0
class Backup(base.CinderPersistentObject, base.CinderObject,
             base.CinderObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Add new field num_dependent_backups and extra fields
    #              is_incremental and has_dependent_backups.
    # Version 1.2: Add new field snapshot_id and data_timestamp.
    # Version 1.3: Changed 'status' field to use BackupStatusField
    # Version 1.4: Add restore_volume_id
    VERSION = '1.4'

    fields = {
        'id': fields.UUIDField(),
        'user_id': fields.StringField(),
        'project_id': fields.StringField(),
        'volume_id': fields.UUIDField(),
        'host': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'container': fields.StringField(nullable=True),
        'parent_id': fields.StringField(nullable=True),
        'status': c_fields.BackupStatusField(nullable=True),
        'fail_reason': fields.StringField(nullable=True),
        'size': fields.IntegerField(nullable=True),
        'display_name': fields.StringField(nullable=True),
        'display_description': fields.StringField(nullable=True),

        # NOTE(dulek): Metadata field is used to store any strings by backup
        # drivers, that's why it can't be DictOfStringsField.
        'service_metadata': fields.StringField(nullable=True),
        'service': fields.StringField(nullable=True),
        'object_count': fields.IntegerField(nullable=True),
        'temp_volume_id': fields.StringField(nullable=True),
        'temp_snapshot_id': fields.StringField(nullable=True),
        'num_dependent_backups': fields.IntegerField(nullable=True),
        'snapshot_id': fields.StringField(nullable=True),
        'data_timestamp': fields.DateTimeField(nullable=True),
        'restore_volume_id': fields.StringField(nullable=True),
    }

    obj_extra_fields = ['name', 'is_incremental', 'has_dependent_backups']

    @property
    def name(self):
        return CONF.backup_name_template % self.id

    @property
    def is_incremental(self):
        return bool(self.parent_id)

    @property
    def has_dependent_backups(self):
        return bool(self.num_dependent_backups)

    def obj_make_compatible(self, primitive, target_version):
        """Make an object representation compatible with a target version."""
        super(Backup, self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)

    @staticmethod
    def _from_db_object(context, backup, db_backup):
        for name, field in backup.fields.items():
            value = db_backup.get(name)
            if isinstance(field, fields.IntegerField):
                value = value if value is not None else 0
            backup[name] = value

        backup._context = context
        backup.obj_reset_changes()
        return backup

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        updates = self.cinder_obj_get_changes()

        db_backup = db.backup_create(self._context, updates)
        self._from_db_object(self._context, self, db_backup)

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            db.backup_update(self._context, self.id, updates)

        self.obj_reset_changes()

    def destroy(self):
        with self.obj_as_admin():
            updated_values = db.backup_destroy(self._context, self.id)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())

    @staticmethod
    def decode_record(backup_url):
        """Deserialize backup metadata from string into a dictionary.

        :raises: InvalidInput
        """
        try:
            return jsonutils.loads(base64.decode_as_text(backup_url))
        except TypeError:
            msg = _("Can't decode backup record.")
        except ValueError:
            msg = _("Can't parse backup record.")
        raise exception.InvalidInput(reason=msg)

    def encode_record(self, **kwargs):
        """Serialize backup object, with optional extra info, into a string."""
        # We don't want to export extra fields and we want to force lazy
        # loading, so we can't use dict(self) or self.obj_to_primitive
        record = {
            name: field.to_primitive(self, name, getattr(self, name))
            for name, field in self.fields.items()
        }
        # We must update kwargs instead of record to ensure we don't overwrite
        # "real" data from the backup
        kwargs.update(record)
        retval = jsonutils.dump_as_bytes(kwargs)
        return base64.encode_as_text(retval)
Exemple #22
0
class Service(base.CinderPersistentObject, base.CinderObject,
              base.CinderObjectDictCompat,
              base.CinderComparableObject):
    # Version 1.0: Initial version
    # Version 1.1: Add rpc_current_version and object_current_version fields
    # Version 1.2: Add get_minimum_rpc_version() and get_minimum_obj_version()
    VERSION = '1.2'

    fields = {
        'id': fields.IntegerField(),
        'host': fields.StringField(nullable=True),
        'binary': fields.StringField(nullable=True),
        'topic': fields.StringField(nullable=True),
        'report_count': fields.IntegerField(default=0),
        'disabled': fields.BooleanField(default=False),
        'availability_zone': fields.StringField(nullable=True,
                                                default='cinder'),
        'disabled_reason': fields.StringField(nullable=True),

        'modified_at': fields.DateTimeField(nullable=True),
        'rpc_current_version': fields.StringField(nullable=True),
        'object_current_version': fields.StringField(nullable=True),
    }

    def obj_make_compatible(self, primitive, target_version):
        """Make an object representation compatible with a target version."""
        target_version = utils.convert_version_to_tuple(target_version)

    @staticmethod
    def _from_db_object(context, service, db_service):
        for name, field in service.fields.items():
            value = db_service.get(name)
            if isinstance(field, fields.IntegerField):
                value = value or 0
            elif isinstance(field, fields.DateTimeField):
                value = value or None
            service[name] = value

        service._context = context
        service.obj_reset_changes()
        return service

    @base.remotable_classmethod
    def get_by_host_and_topic(cls, context, host, topic):
        db_service = db.service_get_by_host_and_topic(context, host, topic)
        return cls._from_db_object(context, cls(context), db_service)

    @base.remotable_classmethod
    def get_by_args(cls, context, host, binary_key):
        db_service = db.service_get_by_args(context, host, binary_key)
        return cls._from_db_object(context, cls(context), db_service)

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        updates = self.cinder_obj_get_changes()
        db_service = db.service_create(self._context, updates)
        self._from_db_object(self._context, self, db_service)

    @base.remotable
    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            db.service_update(self._context, self.id, updates)
            self.obj_reset_changes()

    @base.remotable
    def destroy(self):
        with self.obj_as_admin():
            db.service_destroy(self._context, self.id)

    @classmethod
    def _get_minimum_version(cls, attribute, context, binary):
        services = ServiceList.get_all_by_binary(context, binary)
        min_ver = None
        min_ver_str = None
        for s in services:
            ver_str = getattr(s, attribute)
            if ver_str is None:
                # FIXME(dulek) None in *_current_version means that this
                # service is in Liberty version, so we must assume this is the
                # lowest one. We use handy and easy to remember token to
                # indicate that. This may go away as soon as we drop
                # compatibility with Liberty, possibly in early N.
                return 'liberty'
            ver = versionutils.convert_version_to_int(ver_str)
            if min_ver is None or ver < min_ver:
                min_ver = ver
                min_ver_str = ver_str

        return min_ver_str

    @base.remotable_classmethod
    def get_minimum_rpc_version(cls, context, binary):
        return cls._get_minimum_version('rpc_current_version', context, binary)

    @base.remotable_classmethod
    def get_minimum_obj_version(cls, context, binary):
        return cls._get_minimum_version('object_current_version', context,
                                        binary)
Exemple #23
0
class Volume(base.CinderPersistentObject, base.CinderObject,
             base.CinderObjectDictCompat, base.CinderComparableObject):
    # Version 1.0: Initial version
    # Version 1.1: Added metadata, admin_metadata, volume_attachment, and
    #              volume_type
    # Version 1.2: Added glance_metadata, consistencygroup and snapshots
    VERSION = '1.2'

    OPTIONAL_FIELDS = ('metadata', 'admin_metadata', 'glance_metadata',
                       'volume_type', 'volume_attachment', 'consistencygroup',
                       'snapshots')

    DEFAULT_EXPECTED_ATTR = ('admin_metadata', 'metadata')

    fields = {
        'id':
        fields.UUIDField(),
        '_name_id':
        fields.UUIDField(nullable=True),
        'ec2_id':
        fields.UUIDField(nullable=True),
        'user_id':
        fields.UUIDField(nullable=True),
        'project_id':
        fields.UUIDField(nullable=True),
        'snapshot_id':
        fields.UUIDField(nullable=True),
        'host':
        fields.StringField(nullable=True),
        'size':
        fields.IntegerField(),
        'availability_zone':
        fields.StringField(),
        'status':
        fields.StringField(),
        'attach_status':
        fields.StringField(),
        'migration_status':
        fields.StringField(nullable=True),
        'scheduled_at':
        fields.DateTimeField(nullable=True),
        'launched_at':
        fields.DateTimeField(nullable=True),
        'terminated_at':
        fields.DateTimeField(nullable=True),
        'display_name':
        fields.StringField(nullable=True),
        'display_description':
        fields.StringField(nullable=True),
        'provider_id':
        fields.UUIDField(nullable=True),
        'provider_location':
        fields.StringField(nullable=True),
        'provider_auth':
        fields.StringField(nullable=True),
        'provider_geometry':
        fields.StringField(nullable=True),
        'volume_type_id':
        fields.UUIDField(nullable=True),
        'source_volid':
        fields.UUIDField(nullable=True),
        'encryption_key_id':
        fields.UUIDField(nullable=True),
        'consistencygroup_id':
        fields.UUIDField(nullable=True),
        'deleted':
        fields.BooleanField(default=False),
        'bootable':
        fields.BooleanField(default=False),
        'multiattach':
        fields.BooleanField(default=False),
        'replication_status':
        fields.StringField(nullable=True),
        'replication_extended_status':
        fields.StringField(nullable=True),
        'replication_driver_data':
        fields.StringField(nullable=True),
        'previous_status':
        fields.StringField(nullable=True),
        'metadata':
        fields.DictOfStringsField(nullable=True),
        'admin_metadata':
        fields.DictOfStringsField(nullable=True),
        'glance_metadata':
        fields.DictOfStringsField(nullable=True),
        'volume_type':
        fields.ObjectField('VolumeType', nullable=True),
        'volume_attachment':
        fields.ObjectField('VolumeAttachmentList', nullable=True),
        'consistencygroup':
        fields.ObjectField('ConsistencyGroup', nullable=True),
        'snapshots':
        fields.ObjectField('SnapshotList', nullable=True),
    }

    # NOTE(thangp): obj_extra_fields is used to hold properties that are not
    # usually part of the model
    obj_extra_fields = ['name', 'name_id']

    @property
    def name_id(self):
        return self.id if not self._name_id else self._name_id

    @name_id.setter
    def name_id(self, value):
        self._name_id = value

    @property
    def name(self):
        return CONF.volume_name_template % self.name_id

    def __init__(self, *args, **kwargs):
        super(Volume, self).__init__(*args, **kwargs)
        self._orig_metadata = {}
        self._orig_admin_metadata = {}
        self._orig_glance_metadata = {}

        self._reset_metadata_tracking()

    def obj_reset_changes(self, fields=None):
        super(Volume, self).obj_reset_changes(fields)
        self._reset_metadata_tracking(fields=fields)

    def _reset_metadata_tracking(self, fields=None):
        if fields is None or 'metadata' in fields:
            self._orig_metadata = (dict(self.metadata)
                                   if 'metadata' in self else {})
        if fields is None or 'admin_metadata' in fields:
            self._orig_admin_metadata = (dict(self.admin_metadata)
                                         if 'admin_metadata' in self else {})
        if fields is None or 'glance_metadata' in fields:
            self._orig_glance_metadata = (dict(self.glance_metadata)
                                          if 'glance_metadata' in self else {})

    def obj_what_changed(self):
        changes = super(Volume, self).obj_what_changed()
        if 'metadata' in self and self.metadata != self._orig_metadata:
            changes.add('metadata')
        if ('admin_metadata' in self
                and self.admin_metadata != self._orig_admin_metadata):
            changes.add('admin_metadata')
        if ('glance_metadata' in self
                and self.glance_metadata != self._orig_glance_metadata):
            changes.add('glance_metadata')

        return changes

    def obj_make_compatible(self, primitive, target_version):
        """Make an object representation compatible with a target version."""
        super(Volume, self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)

    @staticmethod
    def _from_db_object(context, volume, db_volume, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for name, field in volume.fields.items():
            if name in Volume.OPTIONAL_FIELDS:
                continue
            value = db_volume.get(name)
            if isinstance(field, fields.IntegerField):
                value = value or 0
            volume[name] = value

        # Get data from db_volume object that was queried by joined query
        # from DB
        if 'metadata' in expected_attrs:
            volume.metadata = {}
            metadata = db_volume.get('volume_metadata', [])
            if metadata:
                volume.metadata = {
                    item['key']: item['value']
                    for item in metadata
                }
        if 'admin_metadata' in expected_attrs:
            volume.admin_metadata = {}
            metadata = db_volume.get('volume_admin_metadata', [])
            if metadata:
                volume.admin_metadata = {
                    item['key']: item['value']
                    for item in metadata
                }
        if 'glance_metadata' in expected_attrs:
            volume.glance_metadata = {}
            metadata = db_volume.get('volume_glance_metadata', [])
            if metadata:
                volume.glance_metadata = {
                    item['key']: item['value']
                    for item in metadata
                }
        if 'volume_type' in expected_attrs:
            db_volume_type = db_volume.get('volume_type')
            if db_volume_type:
                volume.volume_type = objects.VolumeType._from_db_object(
                    context,
                    objects.VolumeType(),
                    db_volume_type,
                    expected_attrs='extra_specs')
        if 'volume_attachment' in expected_attrs:
            attachments = base.obj_make_list(
                context, objects.VolumeAttachmentList(context),
                objects.VolumeAttachment, db_volume.get('volume_attachment'))
            volume.volume_attachment = attachments
        if 'consistencygroup' in expected_attrs:
            consistencygroup = objects.ConsistencyGroup(context)
            consistencygroup._from_db_object(context, consistencygroup,
                                             db_volume['consistencygroup'])
            volume.consistencygroup = consistencygroup
        if 'snapshots' in expected_attrs:
            snapshots = base.obj_make_list(context,
                                           objects.SnapshotList(context),
                                           objects.Snapshot,
                                           db_volume['snapshots'])
            volume.snapshots = snapshots

        volume._context = context
        volume.obj_reset_changes()
        return volume

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        updates = self.cinder_obj_get_changes()

        if 'consistencygroup' in updates:
            raise exception.ObjectActionError(
                action='create', reason=_('consistencygroup assigned'))
        if 'glance_metadata' in updates:
            raise exception.ObjectActionError(
                action='create', reason=_('glance_metadata assigned'))
        if 'snapshots' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('snapshots assigned'))

        db_volume = db.volume_create(self._context, updates)
        self._from_db_object(self._context, self, db_volume)

    @base.remotable
    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'consistencygroup' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('consistencygroup changed'))
            if 'glance_metadata' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('glance_metadata changed'))
            if 'snapshots' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('snapshots changed'))
            if 'metadata' in updates:
                # Metadata items that are not specified in the
                # self.metadata will be deleted
                metadata = updates.pop('metadata', None)
                self.metadata = db.volume_metadata_update(
                    self._context, self.id, metadata, True)
            if self._context.is_admin and 'admin_metadata' in updates:
                metadata = updates.pop('admin_metadata', None)
                self.admin_metadata = db.volume_admin_metadata_update(
                    self._context, self.id, metadata, True)

            db.volume_update(self._context, self.id, updates)
            self.obj_reset_changes()

    @base.remotable
    def destroy(self):
        with self.obj_as_admin():
            db.volume_destroy(self._context, self.id)

    def obj_load_attr(self, attrname):
        if attrname not in self.OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'metadata':
            self.metadata = db.volume_metadata_get(self._context, self.id)
        elif attrname == 'admin_metadata':
            self.admin_metadata = {}
            if self._context.is_admin:
                self.admin_metadata = db.volume_admin_metadata_get(
                    self._context, self.id)
        elif attrname == 'glance_metadata':
            self.glance_metadata = db.volume_glance_metadata_get(
                self._context, self.id)
        elif attrname == 'volume_type':
            self.volume_type = objects.VolumeType.get_by_id(
                self._context, self.volume_type_id)
        elif attrname == 'volume_attachment':
            attachments = objects.VolumeAttachmentList.get_all_by_volume_id(
                self._context, self.id)
            self.volume_attachment = attachments
        elif attrname == 'consistencygroup':
            consistencygroup = objects.ConsistencyGroup.get_by_id(
                self._context, self.consistencygroup_id)
            self.consistencygroup = consistencygroup
        elif attrname == 'snapshots':
            self.snapshots = objects.SnapshotList.get_all_for_volume(
                self._context, self.id)

        self.obj_reset_changes(fields=[attrname])

    def delete_metadata_key(self, key):
        db.volume_metadata_delete(self._context, self.id, key)
        md_was_changed = 'metadata' in self.obj_what_changed()

        del self.metadata[key]
        self._orig_metadata.pop(key, None)

        if not md_was_changed:
            self.obj_reset_changes(['metadata'])
Exemple #24
0
class Container(base.ZunPersistentObject, base.ZunObject):
    # Version 1.0: Initial version
    # Version 1.1: Add container_id column
    # Version 1.2: Add memory column
    # Version 1.3: Add task_state column
    # Version 1.4: Add cpu, workdir, ports, hostname and labels columns
    # Version 1.5: Add meta column
    # Version 1.6: Add addresses column
    # Version 1.7: Add host column
    # Version 1.8: Add restart_policy
    # Version 1.9: Add status_detail column
    # Version 1.10: Add tty, stdin_open
    # Version 1.11: Add image_driver
    # Version 1.12: Add 'Created' to ContainerStatus
    # Version 1.13: Add more task states for container
    # Version 1.14: Add method 'list_by_host'
    # Version 1.15: Combine tty and stdin_open
    # Version 1.16: Add websocket_url and token
    # Version 1.17: Add security_groups
    # Version 1.18: Add auto_remove
    # Version 1.19: Add runtime column
    # Version 1.20: Change runtime to String type
    # Version 1.21: Add pci_device attribute
    # Version 1.22: Add 'Deleting' to ContainerStatus
    # Version 1.23: Add the missing 'pci_devices' attribute
    # Version 1.24: Add the storage_opt attribute
    # Version 1.25: Change TaskStateField definition
    # Version 1.26:  Add auto_heal
    # Version 1.27: Make auto_heal field nullable
    # Version 1.28: Add 'Dead' to ContainerStatus
    # Version 1.29: Add 'Restarting' to ContainerStatus
    # Version 1.30: Add capsule_id attribute
    # Version 1.31: Add 'started_at' attribute
    # Version 1.32: Add 'exec_instances' attribute
    VERSION = '1.33'

    fields = {
        'id': fields.IntegerField(),
        'container_id': fields.StringField(nullable=True),
        'uuid': fields.UUIDField(nullable=True),
        'name': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'image': fields.StringField(nullable=True),
        'cpu': fields.FloatField(nullable=True),
        'memory': fields.StringField(nullable=True),
        'command': fields.ListOfStringsField(nullable=True),
        'status': z_fields.ContainerStatusField(nullable=True),
        'status_reason': fields.StringField(nullable=True),
        'task_state': z_fields.TaskStateField(nullable=True),
        'environment': fields.DictOfStringsField(nullable=True),
        'workdir': fields.StringField(nullable=True),
        'auto_remove': fields.BooleanField(nullable=True),
        'ports': z_fields.ListOfIntegersField(nullable=True),
        'hostname': fields.StringField(nullable=True),
        'labels': fields.DictOfStringsField(nullable=True),
        'meta': fields.DictOfStringsField(nullable=True),
        'addresses': z_fields.JsonField(nullable=True),
        'image_pull_policy': fields.StringField(nullable=True),
        'host': fields.StringField(nullable=True),
        'restart_policy': fields.DictOfStringsField(nullable=True),
        'status_detail': fields.StringField(nullable=True),
        'interactive': fields.BooleanField(nullable=True),
        'image_driver': fields.StringField(nullable=True),
        'websocket_url': fields.StringField(nullable=True),
        'websocket_token': fields.StringField(nullable=True),
        'security_groups': fields.ListOfStringsField(nullable=True),
        'runtime': fields.StringField(nullable=True),
        'pci_devices': fields.ListOfObjectsField('PciDevice', nullable=True),
        'disk': fields.IntegerField(nullable=True),
        'auto_heal': fields.BooleanField(nullable=True),
        'capsule_id': fields.IntegerField(nullable=True),
        'started_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
        'exec_instances': fields.ListOfObjectsField('ExecInstance',
                                                    nullable=True),
    }

    @staticmethod
    def _from_db_object(container, db_container):
        """Converts a database entity to a formal object."""
        for field in container.fields:
            if field in ['pci_devices', 'exec_instances']:
                continue
            setattr(container, field, db_container[field])

        container.obj_reset_changes()
        return container

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [
            Container._from_db_object(cls(context), obj) for obj in db_objects
        ]

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a container based on uuid and return a :class:`Container` object.

        :param uuid: the uuid of a container.
        :param context: Security context
        :returns: a :class:`Container` object.
        """
        db_container = dbapi.get_container_by_uuid(context, uuid)
        container = Container._from_db_object(cls(context), db_container)
        return container

    @base.remotable_classmethod
    def get_by_name(cls, context, name):
        """Find a container based on name and return a Container object.

        :param name: the logical name of a container.
        :param context: Security context
        :returns: a :class:`Container` object.
        """
        db_container = dbapi.get_container_by_name(context, name)
        container = Container._from_db_object(cls(context), db_container)
        return container

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None,
             filters=None):
        """Return a list of Container objects.

        :param context: Security context.
        :param limit: maximum number of resources to return in a single result.
        :param marker: pagination marker for large data sets.
        :param sort_key: column to sort results by.
        :param sort_dir: direction to sort. "asc" or "desc".
        :param filters: filters when list containers, the filter name could be
                        'name', 'image', 'project_id', 'user_id', 'memory'.
                        For example, filters={'image': 'nginx'}
        :returns: a list of :class:`Container` object.

        """
        db_containers = dbapi.list_containers(context,
                                              limit=limit,
                                              marker=marker,
                                              sort_key=sort_key,
                                              sort_dir=sort_dir,
                                              filters=filters)
        return Container._from_db_object_list(db_containers, cls, context)

    @base.remotable_classmethod
    def list_by_host(cls, context, host):
        """Return a list of Container objects by host.

        :param context: Security context.
        :param host: A compute host.
        :returns: a list of :class:`Container` object.

        """
        db_containers = dbapi.list_containers(context, filters={'host': host})
        return Container._from_db_object_list(db_containers, cls, context)

    @base.remotable_classmethod
    def list_by_capsule_id(cls, context, capsule_id):
        """Return a list of Container objects by capsule_id.

        :param context: Security context.
        :param host: A capsule id.
        :returns: a list of :class:`Container` object.

        """
        db_containers = dbapi.list_containers(
            context, filters={'capsule_id': capsule_id})
        return Container._from_db_object_list(db_containers, cls, context)

    @base.remotable
    def create(self, context):
        """Create a Container record in the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)

        """
        values = self.obj_get_changes()
        db_container = dbapi.create_container(context, values)
        self._from_db_object(self, db_container)

    @base.remotable
    def destroy(self, context=None):
        """Delete the Container from the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)
        """
        dbapi.destroy_container(context, self.uuid)
        self.obj_reset_changes()

    @base.remotable
    def save(self, context=None):
        """Save updates to this Container.

        Updates will be made column by column based on the result
        of self.what_changed().

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)
        """
        updates = self.obj_get_changes()
        dbapi.update_container(context, self.uuid, updates)

        self.obj_reset_changes()

    @base.remotable
    def refresh(self, context=None):
        """Loads updates for this Container.

        Loads a container with the same uuid from the database and
        checks for updated attributes. Updates are applied from
        the loaded container column by column, if there are any updates.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: Container(context)
        """
        current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
        for field in self.fields:
            if self.obj_attr_is_set(field) and \
               getattr(self, field) != getattr(current, field):
                setattr(self, field, getattr(current, field))

    def get_sandbox_id(self):
        if self.meta:
            return self.meta.get('sandbox_id', None)
        else:
            return None

    def set_sandbox_id(self, sandbox_id):
        if self.meta is None:
            self.meta = {'sandbox_id': sandbox_id}
        else:
            self.meta['sandbox_id'] = sandbox_id
            self._changed_fields.add('meta')

    def obj_load_attr(self, attrname):
        if attrname not in CONTAINER_OPTIONAL_ATTRS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)

        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading '%(attr)s' on %(name)s uuid %(uuid)s", {
            'attr': attrname,
            'name': self.obj_name(),
            'uuid': self.uuid,
        })

        # NOTE(danms): We handle some fields differently here so that we
        # can be more efficient
        if attrname == 'pci_devices':
            self._load_pci_devices()

        if attrname == 'exec_instances':
            self._load_exec_instances()

        self.obj_reset_changes([attrname])

    def _load_pci_devices(self):
        self.pci_devices = pci_device.PciDevice.list_by_container_uuid(
            self._context, self.uuid)

    def _load_exec_instances(self):
        self.exec_instances = exec_inst.ExecInstance.list_by_container_id(
            self._context, self.id)
Exemple #25
0
class NetworkSegmentRange(base.NeutronDbObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    db_model = range_model.NetworkSegmentRange

    primary_keys = ['id']

    fields = {
        'id':
        common_types.UUIDField(),
        'name':
        obj_fields.StringField(nullable=True),
        'default':
        obj_fields.BooleanField(nullable=False),
        'shared':
        obj_fields.BooleanField(nullable=False),
        'project_id':
        obj_fields.StringField(nullable=True),
        'network_type':
        common_types.NetworkSegmentRangeNetworkTypeEnumField(nullable=False),
        'physical_network':
        obj_fields.StringField(nullable=True),
        'minimum':
        obj_fields.IntegerField(nullable=True),
        'maximum':
        obj_fields.IntegerField(nullable=True)
    }

    def to_dict(self, fields=None):
        _dict = super(NetworkSegmentRange, self).to_dict()
        # extend the network segment range dict with `available` and `used`
        # fields
        _dict.update({'available': self._get_available_allocation()})
        _dict.update({'used': self._get_used_allocation_mapping()})
        # TODO(kailun): For tag mechanism. This will be removed in bug/1704137
        try:
            _dict['tags'] = [t.tag for t in self.db_obj.standard_attr.tags]
        except AttributeError:
            # AttrtibuteError can be raised when accessing self.db_obj
            # or self.db_obj.standard_attr
            pass
        # NOTE(ralonsoh): this workaround should be removed once the migration
        # from "tenant_id" to "project_id" is finished.
        _dict = db_utils.resource_fields(_dict, fields)
        _dict.pop('tenant_id', None)
        return _dict

    def _check_shared_project_id(self, action):
        if self.shared is False and not self.project_id:
            raise n_exc.ObjectActionError(
                action=action,
                reason='if NetworkSegmentRange is not shared, it must have a '
                'project_id')

    def create(self):
        self._check_shared_project_id('create')
        super(NetworkSegmentRange, self).create()

    def update(self):
        self._check_shared_project_id('update')
        super(NetworkSegmentRange, self).update()

    def _get_allocation_model_details(self):
        model = models_map.get(self.network_type)
        if model is not None:
            alloc_segmentation_id = model.get_segmentation_id()
        else:
            msg = (_("network_type '%s' unknown for getting allocation "
                     "information") % self.network_type)
            raise n_exc.InvalidInput(error_message=msg)
        allocated = model.allocated

        return model, alloc_segmentation_id, allocated

    def _get_available_allocation(self):
        with self.db_context_reader(self.obj_context):
            model, alloc_segmentation_id, allocated = (
                self._get_allocation_model_details())

            query = self.obj_context.session.query(alloc_segmentation_id)
            query = query.filter(
                and_(alloc_segmentation_id >= self.minimum,
                     alloc_segmentation_id <= self.maximum), not_(allocated))
            if self.network_type == constants.TYPE_VLAN:
                alloc_available = query.filter(
                    model.physical_network == self.physical_network).all()
            else:
                alloc_available = query.all()

            return [segmentation_id for (segmentation_id, ) in alloc_available]

    def _get_used_allocation_mapping(self):
        with self.db_context_reader(self.obj_context):
            query = self.obj_context.session.query(
                segments_model.NetworkSegment.segmentation_id,
                models_v2.Network.project_id)
            alloc_used = (query.filter(
                and_(
                    segments_model.NetworkSegment.network_type ==
                    self.network_type,
                    segments_model.NetworkSegment.physical_network ==
                    self.physical_network,
                    segments_model.NetworkSegment.segmentation_id >=
                    self.minimum, segments_model.NetworkSegment.segmentation_id
                    <= self.maximum)).filter(
                        segments_model.NetworkSegment.network_id ==
                        models_v2.Network.id)).all()
        return {
            segmentation_id: project_id
            for segmentation_id, project_id in alloc_used
        }
Exemple #26
0
class Event(
        heat_base.HeatObject,
        base.VersionedObjectDictCompat,
):
    fields = {
        'id': fields.IntegerField(),
        'stack_id': fields.StringField(),
        'uuid': fields.StringField(),
        'resource_action': fields.StringField(nullable=True),
        'resource_status': fields.StringField(nullable=True),
        'resource_name': fields.StringField(nullable=True),
        'physical_resource_id': fields.StringField(nullable=True),
        'resource_status_reason': fields.StringField(nullable=True),
        'resource_type': fields.StringField(nullable=True),
        'rsrc_prop_data_id': fields.ObjectField(
            fields.IntegerField(nullable=True)),
        'created_at': fields.DateTimeField(read_only=True),
        'updated_at': fields.DateTimeField(nullable=True),
    }

    @staticmethod
    def _from_db_object(context, event, db_event):
        event._resource_properties = None
        for field in event.fields:
            if field == 'resource_status_reason':
                # this works whether db_event is a dict or db ref
                event[field] = db_event['_resource_status_reason']
            else:
                event[field] = db_event[field]
        if db_event['rsrc_prop_data_id'] is None:
            event._resource_properties = db_event['resource_properties'] or {}
        else:
            if hasattr(db_event, '__dict__'):
                rpd_obj = db_event.__dict__.get('rsrc_prop_data')
            elif hasattr(db_event, 'rsrc_prop_data'):
                rpd_obj = db_event['rsrc_prop_data']
            else:
                rpd_obj = None
            if rpd_obj is not None:
                # Object is already eager loaded
                rpd_obj = (
                    rpd.ResourcePropertiesData._from_db_object(
                        rpd.ResourcePropertiesData(),
                        context,
                        rpd_obj))
                event._resource_properties = rpd_obj.data
        event._context = context
        event.obj_reset_changes()
        return event

    @property
    def resource_properties(self):
        if self._resource_properties is None:
            LOG.info('rsrp_prop_data lazy load')
            rpd_obj = rpd.ResourcePropertiesData.get_by_id(
                self._context, self.rsrc_prop_data_id)
            self._resource_properties = rpd_obj.data or {}
        return self._resource_properties

    @classmethod
    def get_all_by_tenant(cls, context, **kwargs):
        return [cls._from_db_object(context, cls(), db_event)
                for db_event in db_api.event_get_all_by_tenant(context,
                                                               **kwargs)]

    @classmethod
    def get_all_by_stack(cls, context, stack_id, **kwargs):
        return [cls._from_db_object(context, cls(), db_event)
                for db_event in db_api.event_get_all_by_stack(context,
                                                              stack_id,
                                                              **kwargs)]

    @classmethod
    def count_all_by_stack(cls, context, stack_id):
        return db_api.event_count_all_by_stack(context, stack_id)

    @classmethod
    def create(cls, context, values):
        # Using dict() allows us to be done with the sqlalchemy/model
        # layer in one call, rather than hitting that layer for every
        # field in _from_db_object().
        return cls._from_db_object(context, cls(context=context),
                                   dict(db_api.event_create(context, values)))

    def identifier(self, stack_identifier):
        """Return a unique identifier for the event."""

        res_id = identifier.ResourceIdentifier(
            resource_name=self.resource_name, **stack_identifier)

        return identifier.EventIdentifier(event_id=str(self.uuid), **res_id)
Exemple #27
0
class VolumeType(base.CinderPersistentObject, base.CinderObject,
                 base.CinderObjectDictCompat, base.CinderComparableObject):
    # Version 1.0: Initial version
    # Version 1.1: Changed extra_specs to DictOfNullableStringsField
    # Version 1.2: Added qos_specs
    # Version 1.3: Add qos_specs_id
    VERSION = '1.3'

    OPTIONAL_FIELDS = ('extra_specs', 'projects', 'qos_specs')

    # NOTE: When adding a field obj_make_compatible needs to be updated
    fields = {
        'id': fields.UUIDField(),
        'name': fields.StringField(nullable=True),
        'description': fields.StringField(nullable=True),
        'is_public': fields.BooleanField(default=True, nullable=True),
        'projects': fields.ListOfStringsField(nullable=True),
        'extra_specs': fields.DictOfNullableStringsField(nullable=True),
        'qos_specs_id': fields.UUIDField(nullable=True),
        'qos_specs': fields.ObjectField('QualityOfServiceSpecs',
                                        nullable=True),
    }

    @classmethod
    def _get_expected_attrs(cls, context, *args, **kwargs):
        return 'extra_specs', 'projects'

    @classmethod
    def _from_db_object(cls, context, type, db_type, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = ['extra_specs', 'projects']
        for name, field in type.fields.items():
            if name in cls.OPTIONAL_FIELDS:
                continue
            value = db_type[name]
            if isinstance(field, fields.IntegerField):
                value = value or 0
            type[name] = value

        # Get data from db_type object that was queried by joined query
        # from DB
        if 'extra_specs' in expected_attrs:
            type.extra_specs = {}
            specs = db_type.get('extra_specs')
            if specs and isinstance(specs, list):
                type.extra_specs = {
                    item['key']: item['value']
                    for item in specs
                }
            elif specs and isinstance(specs, dict):
                type.extra_specs = specs
        if 'projects' in expected_attrs:
            # NOTE(geguileo): Until projects stops being a polymorphic value we
            # have to do a conversion here for VolumeTypeProjects ORM instance
            # lists.
            projects = db_type.get('projects', [])
            if projects and not isinstance(projects[0], str):
                projects = [p.project_id for p in projects]
            type.projects = projects
        if 'qos_specs' in expected_attrs:
            qos_specs = objects.QualityOfServiceSpecs(context)
            qos_specs._from_db_object(context, qos_specs, db_type['qos_specs'])
            type.qos_specs = qos_specs
        type._context = context
        type.obj_reset_changes()
        return type

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        db_volume_type = volume_types.create(self._context, self.name,
                                             self.extra_specs, self.is_public,
                                             self.projects, self.description)
        self._from_db_object(self._context, self, db_volume_type)

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            volume_types.update(self._context, self.id, self.name,
                                self.description)
            self.obj_reset_changes()

    def destroy(self):
        with self.obj_as_admin():
            updated_values = volume_types.destroy(self._context, self.id)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())

    def obj_load_attr(self, attrname):
        if attrname not in self.OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'extra_specs':
            self.extra_specs = db.volume_type_extra_specs_get(
                self._context, self.id)

        elif attrname == 'qos_specs':
            if self.qos_specs_id:
                self.qos_specs = objects.QualityOfServiceSpecs.get_by_id(
                    self._context, self.qos_specs_id)
            else:
                self.qos_specs = None

        elif attrname == 'projects':
            volume_type_projects = db.volume_type_access_get_all(
                self._context, self.id)
            self.projects = [x.project_id for x in volume_type_projects]

        self.obj_reset_changes(fields=[attrname])

    @classmethod
    def get_by_name_or_id(cls, context, identity):
        orm_obj = volume_types.get_by_name_or_id(context, identity)
        expected_attrs = cls._get_expected_attrs(context)
        return cls._from_db_object(context,
                                   cls(context),
                                   orm_obj,
                                   expected_attrs=expected_attrs)

    def is_replicated(self):
        return volume_utils.is_replicated_spec(self.extra_specs)

    def is_multiattach(self):
        return volume_utils.is_multiattach_spec(self.extra_specs)
Exemple #28
0
class Snapshot(cleanable.CinderCleanableObject, base.CinderObject,
               base.CinderObjectDictCompat, base.CinderComparableObject):
    # Version 1.0: Initial version
    # Version 1.1: Changed 'status' field to use SnapshotStatusField
    # Version 1.2: This object is now cleanable (adds rows to workers table)
    VERSION = '1.2'

    # NOTE(thangp): OPTIONAL_FIELDS are fields that would be lazy-loaded. They
    # are typically the relationship in the sqlalchemy object.
    OPTIONAL_FIELDS = ('volume', 'metadata', 'cgsnapshot', 'group_snapshot')

    fields = {
        'id': fields.UUIDField(),

        'user_id': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),

        'volume_id': fields.UUIDField(nullable=True),
        'cgsnapshot_id': fields.UUIDField(nullable=True),
        'group_snapshot_id': fields.UUIDField(nullable=True),
        'status': c_fields.SnapshotStatusField(nullable=True),
        'progress': fields.StringField(nullable=True),
        'volume_size': fields.IntegerField(nullable=True),

        'display_name': fields.StringField(nullable=True),
        'display_description': fields.StringField(nullable=True),

        'encryption_key_id': fields.UUIDField(nullable=True),
        'volume_type_id': fields.UUIDField(nullable=True),

        'provider_location': fields.StringField(nullable=True),
        'provider_id': fields.StringField(nullable=True),
        'metadata': fields.DictOfStringsField(),
        'provider_auth': fields.StringField(nullable=True),

        'volume': fields.ObjectField('Volume', nullable=True),
        'cgsnapshot': fields.ObjectField('CGSnapshot', nullable=True),
        'group_snapshot': fields.ObjectField('GroupSnapshot', nullable=True),
    }

    @property
    def service_topic_queue(self):
        return self.volume.service_topic_queue

    @classmethod
    def _get_expected_attrs(cls, context, *args, **kwargs):
        return 'metadata',

    # NOTE(thangp): obj_extra_fields is used to hold properties that are not
    # usually part of the model
    obj_extra_fields = ['name', 'volume_name']

    @property
    def name(self):
        return CONF.snapshot_name_template % self.id

    @property
    def volume_name(self):
        return self.volume.name

    def __init__(self, *args, **kwargs):
        super(Snapshot, self).__init__(*args, **kwargs)
        self._orig_metadata = {}

        self._reset_metadata_tracking()

    def obj_reset_changes(self, fields=None):
        super(Snapshot, self).obj_reset_changes(fields)
        self._reset_metadata_tracking(fields=fields)

    def _reset_metadata_tracking(self, fields=None):
        if fields is None or 'metadata' in fields:
            self._orig_metadata = (dict(self.metadata)
                                   if self.obj_attr_is_set('metadata') else {})

    def obj_what_changed(self):
        changes = super(Snapshot, self).obj_what_changed()
        if hasattr(self, 'metadata') and self.metadata != self._orig_metadata:
            changes.add('metadata')

        return changes

    def obj_make_compatible(self, primitive, target_version):
        """Make an object representation compatible with a target version."""
        super(Snapshot, self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)

    @classmethod
    def _from_db_object(cls, context, snapshot, db_snapshot,
                        expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for name, field in snapshot.fields.items():
            if name in cls.OPTIONAL_FIELDS:
                continue
            value = db_snapshot.get(name)
            if isinstance(field, fields.IntegerField):
                value = value if value is not None else 0
            setattr(snapshot, name, value)

        if 'volume' in expected_attrs:
            volume = objects.Volume(context)
            volume._from_db_object(context, volume, db_snapshot['volume'])
            snapshot.volume = volume
        if 'cgsnapshot' in expected_attrs:
            cgsnapshot = objects.CGSnapshot(context)
            cgsnapshot._from_db_object(context, cgsnapshot,
                                       db_snapshot['cgsnapshot'])
            snapshot.cgsnapshot = cgsnapshot
        if 'group_snapshot' in expected_attrs:
            group_snapshot = objects.GroupSnapshot(context)
            group_snapshot._from_db_object(context, group_snapshot,
                                           db_snapshot['group_snapshot'])
            snapshot.group_snapshot = group_snapshot

        if 'metadata' in expected_attrs:
            metadata = db_snapshot.get('snapshot_metadata')
            if metadata is None:
                raise exception.MetadataAbsent()
            snapshot.metadata = {item['key']: item['value']
                                 for item in metadata}
        snapshot._context = context
        snapshot.obj_reset_changes()
        return snapshot

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already created'))
        updates = self.cinder_obj_get_changes()

        if 'volume' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('volume assigned'))
        if 'cgsnapshot' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason=_('cgsnapshot assigned'))
        if 'cluster' in updates:
            raise exception.ObjectActionError(
                action='create', reason=_('cluster assigned'))
        if 'group_snapshot' in updates:
            raise exception.ObjectActionError(
                action='create',
                reason=_('group_snapshot assigned'))

        db_snapshot = db.snapshot_create(self._context, updates)
        self._from_db_object(self._context, self, db_snapshot)

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'volume' in updates:
                raise exception.ObjectActionError(action='save',
                                                  reason=_('volume changed'))
            if 'cgsnapshot' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('cgsnapshot changed'))
            if 'group_snapshot' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('group_snapshot changed'))

            if 'cluster' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('cluster changed'))

            if 'metadata' in updates:
                # Metadata items that are not specified in the
                # self.metadata will be deleted
                metadata = updates.pop('metadata', None)
                self.metadata = db.snapshot_metadata_update(self._context,
                                                            self.id, metadata,
                                                            True)

            db.snapshot_update(self._context, self.id, updates)

        self.obj_reset_changes()

    def destroy(self):
        updated_values = db.snapshot_destroy(self._context, self.id)
        self.update(updated_values)
        self.obj_reset_changes(updated_values.keys())

    def obj_load_attr(self, attrname):
        if attrname not in self.OPTIONAL_FIELDS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute %s not lazy-loadable') % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        if attrname == 'volume':
            self.volume = objects.Volume.get_by_id(self._context,
                                                   self.volume_id)

        if attrname == 'cgsnapshot':
            self.cgsnapshot = objects.CGSnapshot.get_by_id(self._context,
                                                           self.cgsnapshot_id)

        if attrname == 'group_snapshot':
            self.group_snapshot = objects.GroupSnapshot.get_by_id(
                self._context,
                self.group_snapshot_id)

        self.obj_reset_changes(fields=[attrname])

    def delete_metadata_key(self, context, key):
        db.snapshot_metadata_delete(context, self.id, key)
        md_was_changed = 'metadata' in self.obj_what_changed()

        del self.metadata[key]
        self._orig_metadata.pop(key, None)

        if not md_was_changed:
            self.obj_reset_changes(['metadata'])

    @classmethod
    def snapshot_data_get_for_project(cls, context, project_id,
                                      volume_type_id=None):
        return db.snapshot_data_get_for_project(context, project_id,
                                                volume_type_id)

    @staticmethod
    def _is_cleanable(status, obj_version):
        # Before 1.2 we didn't have workers table, so cleanup wasn't supported.
        if obj_version and obj_version < 1.2:
            return False
        return status == 'creating'
Exemple #29
0
class Subnet(base.NeutronDbObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    db_model = models_v2.Subnet

    fields = {
        'id':
        common_types.UUIDField(),
        'project_id':
        obj_fields.StringField(nullable=True),
        'name':
        obj_fields.StringField(nullable=True),
        'network_id':
        common_types.UUIDField(),
        'segment_id':
        common_types.UUIDField(nullable=True),
        # NOTE: subnetpool_id can be 'prefix_delegation' string
        # when the IPv6 Prefix Delegation is enabled
        'subnetpool_id':
        obj_fields.StringField(nullable=True),
        'ip_version':
        common_types.IPVersionEnumField(),
        'cidr':
        common_types.IPNetworkField(),
        'gateway_ip':
        obj_fields.IPAddressField(nullable=True),
        'allocation_pools':
        obj_fields.ListOfObjectsField('IPAllocationPool', nullable=True),
        'enable_dhcp':
        obj_fields.BooleanField(nullable=True),
        'shared':
        obj_fields.BooleanField(nullable=True),
        'dns_nameservers':
        obj_fields.ListOfObjectsField('DNSNameServer', nullable=True),
        'host_routes':
        obj_fields.ListOfObjectsField('Route', nullable=True),
        'ipv6_ra_mode':
        common_types.IPV6ModeEnumField(nullable=True),
        'ipv6_address_mode':
        common_types.IPV6ModeEnumField(nullable=True),
        'service_types':
        obj_fields.ListOfStringsField(nullable=True)
    }

    synthetic_fields = [
        'allocation_pools', 'dns_nameservers', 'host_routes', 'service_types',
        'shared'
    ]

    foreign_keys = {'Network': {'network_id': 'id'}}

    fields_no_update = ['project_id', 'network_id', 'segment_id']

    fields_need_translation = {'host_routes': 'routes'}

    def __init__(self, context=None, **kwargs):
        super(Subnet, self).__init__(context, **kwargs)
        self.add_extra_filter_name('shared')

    def obj_load_attr(self, attrname):
        if attrname == 'shared':
            return self._load_shared()
        if attrname == 'service_types':
            return self._load_service_types()
        super(Subnet, self).obj_load_attr(attrname)

    def _load_shared(self, db_obj=None):
        if db_obj:
            # NOTE(korzen) db_obj is passed when Subnet object is loaded
            # from DB
            rbac_entries = db_obj.get('rbac_entries') or {}
            shared = (rbac_db.RbacNeutronDbObjectMixin.is_network_shared(
                self.obj_context, rbac_entries))
        else:
            # NOTE(korzen) this case is used when Subnet object was
            # instantiated and without DB interaction (get_object(s), update,
            # create), it should be rare case to load 'shared' by that method
            shared = (rbac_db.RbacNeutronDbObjectMixin.get_shared_with_tenant(
                self.obj_context.elevated(), rbac_db_models.NetworkRBAC,
                self.network_id, self.project_id))
        setattr(self, 'shared', shared)
        self.obj_reset_changes(['shared'])

    def _load_service_types(self, db_obj=None):
        if db_obj:
            service_types = db_obj.get('service_types', [])
        else:
            service_types = SubnetServiceType.get_objects(self.obj_context,
                                                          subnet_id=self.id)

        self.service_types = [
            service_type['service_type'] for service_type in service_types
        ]
        self.obj_reset_changes(['service_types'])

    def from_db_object(self, db_obj):
        super(Subnet, self).from_db_object(db_obj)
        self._load_shared(db_obj)
        self._load_service_types(db_obj)

    @classmethod
    def modify_fields_from_db(cls, db_obj):
        # TODO(korzen) remove this method when IP and CIDR decorator ready
        result = super(Subnet, cls).modify_fields_from_db(db_obj)
        if 'cidr' in result:
            result['cidr'] = utils.AuthenticIPNetwork(result['cidr'])
        if 'gateway_ip' in result and result['gateway_ip'] is not None:
            result['gateway_ip'] = netaddr.IPAddress(result['gateway_ip'])
        return result

    @classmethod
    def modify_fields_to_db(cls, fields):
        # TODO(korzen) remove this method when IP and CIDR decorator ready
        result = super(Subnet, cls).modify_fields_to_db(fields)
        if 'cidr' in result:
            result['cidr'] = cls.filter_to_str(result['cidr'])
        if 'gateway_ip' in result and result['gateway_ip'] is not None:
            result['gateway_ip'] = cls.filter_to_str(result['gateway_ip'])
        return result
Exemple #30
0
class X509KeyPair(base.MagnumPersistentObject, base.MagnumObject,
                  base.MagnumObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added new method get_x509keypair_by_bay_uuid
    # Version 1.2: Remove bay_uuid, name, ca_cert and add intermediates
    #              and private_key_passphrase
    VERSION = '1.2'

    dbapi = dbapi.get_instance()

    fields = {
        'id': fields.IntegerField(),
        'uuid': fields.UUIDField(nullable=True),
        'certificate': fields.StringField(nullable=True),
        'private_key': fields.StringField(nullable=True),
        'intermediates': fields.StringField(nullable=True),
        'private_key_passphrase': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
    }

    @staticmethod
    def _from_db_object(x509keypair, db_x509keypair):
        """Converts a database entity to a formal object."""
        for field in x509keypair.fields:
            x509keypair[field] = db_x509keypair[field]

        x509keypair.obj_reset_changes()
        return x509keypair

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [
            X509KeyPair._from_db_object(cls(context), obj)
            for obj in db_objects
        ]

    @base.remotable_classmethod
    def get(cls, context, x509keypair_id):
        """Find a X509KeyPair based on its id or uuid.

        Find X509KeyPair by id or uuid and return a X509KeyPair object.

        :param x509keypair_id: the id *or* uuid of a x509keypair.
        :param context: Security context
        :returns: a :class:`X509KeyPair` object.
        """
        if strutils.is_int_like(x509keypair_id):
            return cls.get_by_id(context, x509keypair_id)
        elif uuidutils.is_uuid_like(x509keypair_id):
            return cls.get_by_uuid(context, x509keypair_id)
        else:
            raise exception.InvalidIdentity(identity=x509keypair_id)

    @base.remotable_classmethod
    def get_by_id(cls, context, x509keypair_id):
        """Find a X509KeyPair based on its integer id.

        Find X509KeyPair by id and return a X509KeyPair object.

        :param x509keypair_id: the id of a x509keypair.
        :param context: Security context
        :returns: a :class:`X509KeyPair` object.
        """
        db_x509keypair = cls.dbapi.get_x509keypair_by_id(
            context, x509keypair_id)
        x509keypair = X509KeyPair._from_db_object(cls(context), db_x509keypair)
        return x509keypair

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a x509keypair based on uuid and return a :class:`X509KeyPair` object.

        :param uuid: the uuid of a x509keypair.
        :param context: Security context
        :returns: a :class:`X509KeyPair` object.
        """
        db_x509keypair = cls.dbapi.get_x509keypair_by_uuid(context, uuid)
        x509keypair = X509KeyPair._from_db_object(cls(context), db_x509keypair)
        return x509keypair

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None,
             filters=None):
        """Return a list of X509KeyPair objects.

        :param context: Security context.
        :param limit: maximum number of resources to return in a single result.
        :param marker: pagination marker for large data sets.
        :param sort_key: column to sort results by.
        :param sort_dir: direction to sort. "asc" or "desc".
        :param filters: filter dict, can include 'x509keypairmodel_id',
                        'project_id', 'user_id'.
        :returns: a list of :class:`X509KeyPair` object.

        """
        db_x509keypairs = cls.dbapi.get_x509keypair_list(context,
                                                         limit=limit,
                                                         marker=marker,
                                                         sort_key=sort_key,
                                                         sort_dir=sort_dir,
                                                         filters=filters)
        return X509KeyPair._from_db_object_list(db_x509keypairs, cls, context)

    @base.remotable
    def create(self, context=None):
        """Create a X509KeyPair record in the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: X509KeyPair(context)

        """
        values = self.obj_get_changes()
        db_x509keypair = self.dbapi.create_x509keypair(values)
        self._from_db_object(self, db_x509keypair)

    @base.remotable
    def destroy(self, context=None):
        """Delete the X509KeyPair from the DB.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: X509KeyPair(context)
        """
        self.dbapi.destroy_x509keypair(self.uuid)
        self.obj_reset_changes()

    @base.remotable
    def save(self, context=None):
        """Save updates to this X509KeyPair.

        Updates will be made column by column based on the result
        of self.what_changed().

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: X509KeyPair(context)
        """
        updates = self.obj_get_changes()
        self.dbapi.update_x509keypair(self.uuid, updates)

        self.obj_reset_changes()

    @base.remotable
    def refresh(self, context=None):
        """Loads updates for this X509KeyPair.

        Loads a x509keypair with the same uuid from the database and
        checks for updated attributes. Updates are applied from
        the loaded x509keypair column by column, if there are any updates.

        :param context: Security context. NOTE: This should only
                        be used internally by the indirection_api.
                        Unfortunately, RPC requires context as the first
                        argument, even though we don't use it.
                        A context should be set when instantiating the
                        object, e.g.: X509KeyPair(context)
        """
        current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
        for field in self.fields:
            if self.obj_attr_is_set(field) and self[field] != current[field]:
                self[field] = current[field]