Exemple #1
0
class NodeFilter(base.DrydockObject):

    VERSION = '1.0'

    fields = {
        'filter_type': ovo_fields.StringField(nullable=False),
        'node_names': ovo_fields.ListOfStringsField(nullable=True),
        'node_tags': ovo_fields.ListOfStringsField(nullable=True),
        'node_labels': ovo_fields.DictOfStringsField(nullable=True),
        'rack_names': ovo_fields.ListOfStringsField(nullable=True),
        'rack_labels': ovo_fields.DictOfStringsField(nullable=True),
    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
Exemple #2
0
class Site(base.DrydockPersistentObject, base.DrydockObject):

    VERSION = '1.0'

    fields = {
        'name':
        ovo_fields.StringField(),
        'status':
        hd_fields.SiteStatusField(default=hd_fields.SiteStatus.Unknown),
        'source':
        hd_fields.ModelSourceField(),
        'tag_definitions':
        ovo_fields.ObjectField('NodeTagDefinitionList', nullable=True),
        'repositories':
        ovo_fields.ObjectField('RepositoryList', nullable=True),
        'authorized_keys':
        ovo_fields.ListOfStringsField(nullable=True),
    }

    def __init__(self, **kwargs):
        super(Site, self).__init__(**kwargs)

    def get_id(self):
        return self.name

    def get_name(self):
        return self.name

    def add_tag_definition(self, tag_definition):
        self.tag_definitions.append(tag_definition)

    def add_key(self, key_string):
        self.authorized_keys.append(key_string)
Exemple #3
0
class VolumeProperties(base.CinderObject, base.CinderObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    # TODO(dulek): We should add this to initially move volume_properites to
    # ovo, but this should be removed as soon as possible. Most of the data
    # here is already in request_spec and volume there. Outstanding ones would
    # be reservation, and qos_specs. First one may be moved to request_spec and
    # second added as relationship in volume_type field and whole
    # volume_properties (and resource_properties) in request_spec won't be
    # needed.

    fields = {
        'attach_status': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'cgsnapshot_id': fields.UUIDField(nullable=True),
        'consistencygroup_id': fields.UUIDField(nullable=True),
        'display_description': fields.StringField(nullable=True),
        'display_name': fields.StringField(nullable=True),
        'encryption_key_id': fields.UUIDField(nullable=True),
        'metadata': fields.DictOfStringsField(nullable=True),
        'multiattach': fields.BooleanField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'qos_specs': fields.DictOfStringsField(nullable=True),
        'replication_status': fields.StringField(nullable=True),
        'reservations': fields.ListOfStringsField(nullable=True),
        'size': fields.IntegerField(nullable=True),
        'snapshot_id': fields.UUIDField(nullable=True),
        'source_replicaid': fields.UUIDField(nullable=True),
        'source_volid': fields.UUIDField(nullable=True),
        'status': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'volume_type_id': fields.UUIDField(nullable=True),
    }
Exemple #4
0
class RouterExtraAttributes(base.NeutronDbObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    db_model = l3_attrs.RouterExtraAttributes

    fields = {
        'router_id': common_types.UUIDField(),
        'distributed': obj_fields.BooleanField(default=False),
        'service_router': obj_fields.BooleanField(default=False),
        'ha': obj_fields.BooleanField(default=False),
        'ha_vr_id': obj_fields.IntegerField(nullable=True),
        'availability_zone_hints': obj_fields.ListOfStringsField(nullable=True)
    }

    primary_keys = ['router_id']

    foreign_keys = {'Router': {'router_id': 'id'}}

    @classmethod
    def modify_fields_from_db(cls, db_obj):
        result = super(RouterExtraAttributes,
                       cls).modify_fields_from_db(db_obj)
        if az_ext.AZ_HINTS in result:
            result[az_ext.AZ_HINTS] = (az_ext.convert_az_string_to_list(
                result[az_ext.AZ_HINTS]))
        return result

    @classmethod
    def modify_fields_to_db(cls, fields):
        result = super(RouterExtraAttributes, cls).modify_fields_to_db(fields)
        if az_ext.AZ_HINTS in result:
            result[az_ext.AZ_HINTS] = (az_ext.convert_az_list_to_string(
                result[az_ext.AZ_HINTS]))
        return result
 def setUp(self):
     super(TestListOfStrings, self).setUp()
     self.field = fields.ListOfStringsField()
     self.coerce_good_values = [(['foo', 'bar'], ['foo', 'bar'])]
     self.coerce_bad_values = ['foo']
     self.to_primitive_values = [(['foo'], ['foo'])]
     self.from_primitive_values = [(['foo'], ['foo'])]
Exemple #6
0
class NetworkLink(base.DrydockPersistentObject, base.DrydockObject):

    VERSION = '1.0'

    fields = {
        'name':
        ovo_fields.StringField(),
        'site':
        ovo_fields.StringField(),
        'metalabels':
        ovo_fields.ListOfStringsField(nullable=True),
        'bonding_mode':
        hd_fields.NetworkLinkBondingModeField(
            default=hd_fields.NetworkLinkBondingMode.Disabled),
        'bonding_xmit_hash':
        ovo_fields.StringField(nullable=True, default='layer3+4'),
        'bonding_peer_rate':
        ovo_fields.StringField(nullable=True, default='slow'),
        'bonding_mon_rate':
        ovo_fields.IntegerField(nullable=True, default=100),
        'bonding_up_delay':
        ovo_fields.IntegerField(nullable=True, default=200),
        'bonding_down_delay':
        ovo_fields.IntegerField(nullable=True, default=200),
        'mtu':
        ovo_fields.IntegerField(default=1500),
        'linkspeed':
        ovo_fields.StringField(default='auto'),
        'trunk_mode':
        hd_fields.NetworkLinkTrunkingModeField(
            default=hd_fields.NetworkLinkTrunkingMode.Disabled),
        'native_network':
        ovo_fields.StringField(nullable=True),
        'allowed_networks':
        ovo_fields.ListOfStringsField(),
    }

    def __init__(self, **kwargs):
        super(NetworkLink, self).__init__(**kwargs)

    # NetworkLink keyed by name
    def get_id(self):
        return self.get_name()

    def get_name(self):
        return self.name
Exemple #7
0
class RouterExtraAttributes(base.NeutronDbObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    db_model = l3_attrs.RouterExtraAttributes

    fields = {
        'router_id': common_types.UUIDField(),
        'distributed': obj_fields.BooleanField(default=False),
        'service_router': obj_fields.BooleanField(default=False),
        'ha': obj_fields.BooleanField(default=False),
        'ha_vr_id': obj_fields.IntegerField(nullable=True),
        'availability_zone_hints': obj_fields.ListOfStringsField(nullable=True)
    }

    primary_keys = ['router_id']

    foreign_keys = {'Router': {'router_id': 'id'}}

    @classmethod
    def modify_fields_from_db(cls, db_obj):
        result = super(RouterExtraAttributes, cls).modify_fields_from_db(
            db_obj)
        if az_def.AZ_HINTS in result:
            result[az_def.AZ_HINTS] = (
                az_validator.convert_az_string_to_list(
                    result[az_def.AZ_HINTS]))
        return result

    @classmethod
    def modify_fields_to_db(cls, fields):
        result = super(RouterExtraAttributes, cls).modify_fields_to_db(fields)
        if az_def.AZ_HINTS in result:
            result[az_def.AZ_HINTS] = (
                az_validator.convert_az_list_to_string(
                    result[az_def.AZ_HINTS]))
        return result

    @classmethod
    def get_router_agents_count(cls, context):
        # TODO(sshank): This is pulled out from l3_agentschedulers_db.py
        # until a way to handle joins is figured out.
        binding_model = rb_model.RouterL3AgentBinding
        sub_query = (context.session.query(
            binding_model.router_id,
            func.count(binding_model.router_id).label('count')).
                     join(l3_attrs.RouterExtraAttributes,
                          binding_model.router_id ==
                          l3_attrs.RouterExtraAttributes.router_id).
                     join(l3.Router).
                     group_by(binding_model.router_id).subquery())

        query = (context.session.query(l3.Router, sub_query.c.count).
                 outerjoin(sub_query))

        return [(router, agent_count) for router, agent_count in query]
Exemple #8
0
class Repository(base.DrydockObject):

    VERSION = '1.0'

    fields = {
        'name': ovo_fields.StringField(),
        'url': ovo_fields.StringField(),
        'repo_type': ovo_fields.StringField(),
        'gpgkey': ovo_fields.StringField(nullable=True),
        'distributions': ovo_fields.ListOfStringsField(nullable=True),
        'subrepos': ovo_fields.ListOfStringsField(nullable=True),
        'components': ovo_fields.ListOfStringsField(nullable=True),
        'arches': ovo_fields.ListOfStringsField(default=['amd64']),
        'options': ovo_fields.DictOfStringsField(nullable=True)
    }

    STANDARD_COMPONENTS = {
        'apt': {'main', 'restricted', 'universe', 'multiverse'},
    }

    STANDARD_SUBREPOS = {
        'apt': {'security', 'updates', 'backports'},
    }

    def __init__(self, **kwargs):
        super(Repository, self).__init__(**kwargs)

    # Repository keyed by tag
    def get_id(self):
        return self.name

    def get_disabled_components(self):
        enabled = set(self.components or [])
        std = self.STANDARD_COMPONENTS.get(self.repo_type, ())
        return std - enabled

    def get_disabled_subrepos(self):
        enabled = set(self.subrepos or [])
        std = self.STANDARD_SUBREPOS.get(self.repo_type, ())
        return std - enabled
Exemple #9
0
class BootAction(base.DrydockPersistentObject, base.DrydockObject):

    VERSION = '1.0'

    fields = {
        'name': ovo_fields.StringField(),
        'source': hd_fields.ModelSourceField(nullable=False),
        'asset_list': ovo_fields.ObjectField('BootActionAssetList',
                                             nullable=False),
        'node_filter': ovo_fields.ObjectField('NodeFilterSet', nullable=True),
        'target_nodes': ovo_fields.ListOfStringsField(nullable=True),
        'signaling': ovo_fields.BooleanField(default=True),
    }

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

    # NetworkLink keyed by name
    def get_id(self):
        return self.get_name()

    def get_name(self):
        return self.name

    def render_assets(self,
                      nodename,
                      site_design,
                      action_id,
                      design_ref,
                      type_filter=None):
        """Render all of the assets in this bootaction.

        Render the assets of this bootaction and return them in a list.
        The ``nodename`` and ``action_id`` will be
        used to build the context for any assets utilizing the ``template``
        pipeline segment.

        :param nodename: name of the node the assets are destined for
        :param site_design: a objects.SiteDesign instance holding the design sets
        :param action_id: a 128-bit ULID action_id of the boot action
                          the assets are part of
        :param design_ref: the design ref this boot action was initiated under
        :param type_filter: optional filter of the types of assets to render
        """
        assets = list()
        for a in self.asset_list:
            if type_filter is None or (type_filter is not None
                                       and a.type == type_filter):
                a.render(nodename, site_design, action_id, design_ref)
                assets.append(a)

        return assets
Exemple #10
0
class Subnet(base.VersionedObject):
    """Represents a subnet."""
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {
        'cidr': fields.StringField(nullable=True),
        'dns': fields.ListOfStringsField(),
        'gateway': fields.StringField(),
        'ips': fields.ListOfStringsField(),
        'routes': fields.ListOfStringsField(),
        'version': fields.IntegerField(nullable=True),
    }

    def __init__(self,
                 cidr=None,
                 dns=None,
                 gateway=None,
                 ips=None,
                 routes=None,
                 **kwargs):

        dns = dns or set()
        ips = ips or set()
        routes = routes or set()
        version = kwargs.pop('version', None)

        if cidr and not version:
            version = netaddr.IPNetwork(cidr).version
        super(Subnet, self).__init__(cidr=cidr,
                                     dns=dns,
                                     gateway=gateway,
                                     ips=ips,
                                     routes=routes,
                                     version=version)

    def as_netaddr(self):
        """Convenience function to get cidr as a netaddr object."""
        return netaddr.IPNetwork(self.cidr)
Exemple #11
0
class ClassificationType(base.NeutronObject):

    VERSION = '1.0'

    fields = {
        'type': obj_fields.StringField(),
        'supported_parameters': obj_fields.ListOfStringsField(),
    }

    @classmethod
    def get_object(cls, classification_type, **kwargs):

        parameters = list(constants.SUPPORTED_FIELDS[classification_type])

        return cls(type=classification_type, supported_parameters=parameters)

    @classmethod
    def get_objects(cls, classification_type, **kwargs):
        # Only get one object
        pass
Exemple #12
0
class ClusterLock(senlin_base.SenlinObject, base.VersionedObjectDictCompat):
    """Senlin cluster lock object."""

    fields = {
        'cluster_id': fields.UUIDField(),
        'action_ids': fields.ListOfStringsField(),
        'semaphore': fields.IntegerField(),
    }

    @classmethod
    def acquire(cls, cluster_id, action_id, scope):
        return db_api.cluster_lock_acquire(cluster_id, action_id, scope)

    @classmethod
    def release(cls, cluster_id, action_id, scope):
        return db_api.cluster_lock_release(cluster_id, action_id, scope)

    @classmethod
    def steal(cls, cluster_id, action_id):
        return db_api.cluster_lock_steal(cluster_id, action_id)
Exemple #13
0
class Rack(base.DrydockPersistentObject, base.DrydockObject):

    VERSION = '1.0'

    fields = {
        'name': obj_fields.StringField(nullable=False),
        'site': obj_fields.StringField(nullable=False),
        'source': hd_fields.ModelSourceField(nullable=False),
        'tor_switches': obj_fields.ObjectField('TorSwitchList',
                                               nullable=False),
        'location': obj_fields.DictOfStringsField(nullable=False),
        'local_networks': obj_fields.ListOfStringsField(nullable=True),
    }

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

    def get_id(self):
        return self.get_name()

    def get_name(self):
        return self.name
Exemple #14
0
class Network(base.DrydockPersistentObject, base.DrydockObject):

    VERSION = '1.0'

    fields = {
        'name': ovo_fields.StringField(),
        'site': ovo_fields.StringField(),
        'metalabels': ovo_fields.ListOfStringsField(nullable=True),
        'cidr': ovo_fields.StringField(),
        'allocation_strategy': ovo_fields.StringField(),
        'vlan_id': ovo_fields.StringField(nullable=True),
        'mtu': ovo_fields.IntegerField(nullable=True),
        'dns_domain': ovo_fields.StringField(nullable=True),
        'dns_servers': ovo_fields.StringField(nullable=True),
        # Keys of ranges are 'type', 'start', 'end'
        'ranges': ovo_fields.ListOfDictOfNullableStringsField(),
        # Keys of routes are 'subnet', 'gateway', 'metric'
        'routes': ovo_fields.ListOfDictOfNullableStringsField(),
        'dhcp_relay_self_ip': ovo_fields.StringField(nullable=True),
        'dhcp_relay_upstream_target': ovo_fields.StringField(nullable=True),
    }

    def __init__(self, **kwargs):
        super(Network, self).__init__(**kwargs)

    # Network keyed on name
    def get_id(self):
        return self.get_name()

    def get_name(self):
        return self.name

    def get_default_gateway(self):
        for r in getattr(self, 'routes', []):
            if r.get('subnet', '') == '0.0.0.0/0':
                return r.get('gateway', None)

        return None
Exemple #15
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 #16
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.2'

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

    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': fields.ObjectField('QualityOfServiceSpecs',
                                        nullable=True),
    }

    def obj_make_compatible(self, primitive, target_version):
        super(VolumeType, self).obj_make_compatible(primitive, target_version)

        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 1):
            if primitive.get('extra_specs'):
                # Before 1.1 extra_specs field didn't allowed None values. To
                # make sure we won't explode on receiver side - change Nones to
                # empty string.
                for k, v in primitive['extra_specs'].items():
                    if v is None:
                        primitive['extra_specs'][k] = ''

    @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:
            type.projects = db_type.get('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())
Exemple #17
0
class RequestSpec(base.CinderObject, base.CinderObjectDictCompat,
                  base.CinderComparableObject):
    # Version 1.0: Initial version
    # Version 1.1: Added group_id and group_backend
    # Version 1.2 Added ``resource_backend``
    # Version 1.3: Added backup_id
    # Version 1.4: Add 'operation'
    # Version 1.5: Added 'availability_zones'
    VERSION = '1.5'

    # NOTE: When adding a field obj_make_compatible needs to be updated
    fields = {
        'consistencygroup_id': fields.UUIDField(nullable=True),
        'group_id': fields.UUIDField(nullable=True),
        'cgsnapshot_id': fields.UUIDField(nullable=True),
        'image_id': fields.UUIDField(nullable=True),
        'snapshot_id': fields.UUIDField(nullable=True),
        'source_replicaid': fields.UUIDField(nullable=True),
        'source_volid': fields.UUIDField(nullable=True),
        'volume_id': fields.UUIDField(nullable=True),
        'volume': fields.ObjectField('Volume', nullable=True),
        'volume_type': fields.ObjectField('VolumeType', nullable=True),
        'volume_properties': fields.ObjectField('VolumeProperties',
                                                nullable=True),
        'CG_backend': fields.StringField(nullable=True),
        'group_backend': fields.StringField(nullable=True),
        'resource_backend': fields.StringField(nullable=True),
        'backup_id': fields.UUIDField(nullable=True),
        'operation': fields.StringField(nullable=True),
        'availability_zones': fields.ListOfStringsField(nullable=True),
    }

    obj_extra_fields = ['resource_properties']

    @property
    def resource_properties(self):
        # TODO(dulek): This is to maintain compatibility with filters from
        # oslo-incubator. As we've moved them into our codebase we should adapt
        # them to use volume_properties and remove this shim.
        return self.volume_properties

    @classmethod
    def from_primitives(cls, spec):
        """Returns RequestSpec object creating it from legacy dictionary.

        FIXME(dulek): This should go away in early O as we stop supporting
        backward compatibility with M.
        """
        spec = spec.copy()
        spec_obj = cls()

        vol_props = spec.pop('volume_properties', {})
        if vol_props is not None:
            vol_props = VolumeProperties(**vol_props)
        spec_obj.volume_properties = vol_props

        if 'volume' in spec:
            vol = spec.pop('volume', {})
            vol.pop('name', None)
            if vol is not None:
                vol = objects.Volume(**vol)
            spec_obj.volume = vol

        if 'volume_type' in spec:
            vol_type = spec.pop('volume_type', {})
            if vol_type is not None:
                vol_type = objects.VolumeType(**vol_type)
            spec_obj.volume_type = vol_type

        spec.pop('resource_properties', None)

        for k, v in spec.items():
            setattr(spec_obj, k, v)

        return spec_obj
Exemple #18
0
class Network(rbac_db.NeutronRbacObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    rbac_db_model = rbac_db_models.NetworkRBAC
    db_model = models_v2.Network

    fields = {
        'id': common_types.UUIDField(),
        'project_id': obj_fields.StringField(nullable=True),
        'name': obj_fields.StringField(nullable=True),
        'status': obj_fields.StringField(nullable=True),
        'admin_state_up': obj_fields.BooleanField(nullable=True),
        'vlan_transparent': obj_fields.BooleanField(nullable=True),
        # TODO(ihrachys): consider converting to a field of stricter type
        'availability_zone_hints':
        obj_fields.ListOfStringsField(nullable=True),
        'shared': obj_fields.BooleanField(default=False),
        'mtu': obj_fields.IntegerField(nullable=True),

        # TODO(ihrachys): consider exposing availability zones

        # TODO(ihrachys): consider converting to boolean
        'security': obj_fields.ObjectField('NetworkPortSecurity',
                                           nullable=True),
        'segments': obj_fields.ListOfObjectsField('NetworkSegment',
                                                  nullable=True),
        'dns_domain': common_types.DomainNameField(nullable=True),
        'qos_policy_id': common_types.UUIDField(nullable=True, default=None),

        # TODO(ihrachys): add support for tags, probably through a base class
        # since it's a feature that will probably later be added for other
        # resources too

        # TODO(ihrachys): expose external network attributes
    }

    synthetic_fields = [
        'dns_domain',
        # MTU is not stored in the database any more, it's a synthetic field
        # that may be used by plugins to provide a canonical representation for
        # the resource
        'mtu',
        'qos_policy_id',
        'security',
        'segments',
    ]

    fields_need_translation = {
        'security': 'port_security',
    }

    def create(self):
        fields = self.obj_get_changes()
        with db_api.autonested_transaction(self.obj_context.session):
            dns_domain = self.dns_domain
            qos_policy_id = self.qos_policy_id
            super(Network, self).create()
            if 'dns_domain' in fields:
                self._set_dns_domain(dns_domain)
            if 'qos_policy_id' in fields:
                self._attach_qos_policy(qos_policy_id)

    def update(self):
        fields = self.obj_get_changes()
        with db_api.autonested_transaction(self.obj_context.session):
            super(Network, self).update()
            if 'dns_domain' in fields:
                self._set_dns_domain(fields['dns_domain'])
            if 'qos_policy_id' in fields:
                self._attach_qos_policy(fields['qos_policy_id'])

    def _attach_qos_policy(self, qos_policy_id):
        # TODO(ihrachys): introduce an object for the binding to isolate
        # database access in a single place, currently scattered between port
        # and policy objects
        obj_db_api.delete_objects(
            self.obj_context,
            qos_models.QosNetworkPolicyBinding,
            network_id=self.id,
        )
        if qos_policy_id:
            obj_db_api.create_object(self.obj_context,
                                     qos_models.QosNetworkPolicyBinding, {
                                         'network_id': self.id,
                                         'policy_id': qos_policy_id
                                     })
        self.qos_policy_id = qos_policy_id
        self.obj_reset_changes(['qos_policy_id'])

    def _set_dns_domain(self, dns_domain):
        objs = NetworkDNSDomain.get_objects(self.obj_context,
                                            network_id=self.id)
        for obj in objs:
            obj.delete()
        if dns_domain:
            NetworkDNSDomain(self.obj_context,
                             network_id=self.id,
                             dns_domain=dns_domain).create()
        self.dns_domain = dns_domain
        self.obj_reset_changes(['dns_domain'])

    @classmethod
    def modify_fields_from_db(cls, db_obj):
        result = super(Network, cls).modify_fields_from_db(db_obj)
        if az_ext.AZ_HINTS in result:
            result[az_ext.AZ_HINTS] = (az_ext.convert_az_string_to_list(
                result[az_ext.AZ_HINTS]))
        return result

    @classmethod
    def modify_fields_to_db(cls, fields):
        result = super(Network, cls).modify_fields_to_db(fields)
        if az_ext.AZ_HINTS in result:
            result[az_ext.AZ_HINTS] = (az_ext.convert_az_list_to_string(
                result[az_ext.AZ_HINTS]))
        return result

    def from_db_object(self, *objs):
        super(Network, self).from_db_object(*objs)
        for db_obj in objs:
            # extract domain name
            if db_obj.get('dns_domain'):
                self.dns_domain = (db_obj.dns_domain.dns_domain)
            else:
                self.dns_domain = None
            self.obj_reset_changes(['dns_domain'])

            # extract qos policy binding
            if db_obj.get('qos_policy_binding'):
                self.qos_policy_id = (db_obj.qos_policy_binding.policy_id)
            else:
                self.qos_policy_id = None
            self.obj_reset_changes(['qos_policy_id'])

    @classmethod
    def get_bound_tenant_ids(cls, context, policy_id):
        # TODO(ihrachys): provide actual implementation
        return set()
Exemple #19
0
class NetworkSegment(base.NeutronDbObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    db_model = segment_model.NetworkSegment

    fields = {
        'id': common_types.UUIDField(),
        'network_id': common_types.UUIDField(),
        'name': obj_fields.StringField(nullable=True),
        'network_type': obj_fields.StringField(),
        'physical_network': obj_fields.StringField(nullable=True),
        'segmentation_id': obj_fields.IntegerField(nullable=True),
        'is_dynamic': obj_fields.BooleanField(default=False),
        'segment_index': obj_fields.IntegerField(default=0),
        'hosts': obj_fields.ListOfStringsField(nullable=True)
    }

    synthetic_fields = ['hosts']

    fields_no_update = ['network_id']

    foreign_keys = {
        'Network': {
            'network_id': 'id'
        },
        'PortBindingLevel': {
            'id': 'segment_id'
        },
    }

    def create(self):
        fields = self.obj_get_changes()
        with db_api.autonested_transaction(self.obj_context.session):
            hosts = self.hosts
            if hosts is None:
                hosts = []
            super(NetworkSegment, self).create()
            if 'hosts' in fields:
                self._attach_hosts(hosts)

    def update(self):
        fields = self.obj_get_changes()
        with db_api.autonested_transaction(self.obj_context.session):
            super(NetworkSegment, self).update()
            if 'hosts' in fields:
                self._attach_hosts(fields['hosts'])

    def _attach_hosts(self, hosts):
        SegmentHostMapping.delete_objects(
            self.obj_context,
            segment_id=self.id,
        )
        if hosts:
            for host in hosts:
                SegmentHostMapping(self.obj_context,
                                   segment_id=self.id,
                                   host=host).create()
        self.hosts = hosts
        self.obj_reset_changes(['hosts'])

    def obj_load_attr(self, attrname):
        if attrname == 'hosts':
            return self._load_hosts()
        super(NetworkSegment, self).obj_load_attr(attrname)

    def _load_hosts(self, db_obj=None):
        if db_obj:
            hosts = db_obj.get('segment_host_mapping', [])
        else:
            hosts = SegmentHostMapping.get_objects(self.obj_context,
                                                   segment_id=self.id)

        self.hosts = [host['host'] for host in hosts]
        self.obj_reset_changes(['hosts'])

    def from_db_object(self, db_obj):
        super(NetworkSegment, self).from_db_object(db_obj)
        self._load_hosts(db_obj)

    @classmethod
    def get_objects(cls, context, _pager=None, **kwargs):
        if not _pager:
            _pager = base.Pager()
        if not _pager.sorts:
            # (NOTE) True means ASC, False is DESC
            _pager.sorts = [(field, True)
                            for field in ('network_id', 'segment_index')]
        return super(NetworkSegment, cls).get_objects(context, _pager,
                                                      **kwargs)
Exemple #20
0
class Pod(base.MagnumPersistentObject, base.MagnumObject,
          base.MagnumObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Remove unused Pod object API 'list_by_bay_uuid'
    VERSION = '1.1'

    dbapi = dbapi.get_instance()

    fields = {
        'id': fields.IntegerField(),
        'uuid': fields.StringField(nullable=True),
        'name': fields.StringField(nullable=True),
        'desc': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'bay_uuid': fields.StringField(nullable=True),
        'images': fields.ListOfStringsField(nullable=True),
        'labels': fields.DictOfStringsField(nullable=True),
        'status': fields.StringField(nullable=True),
        'manifest_url': fields.StringField(nullable=True),
        'manifest': fields.StringField(nullable=True),
        'host': fields.StringField(nullable=True),
    }

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid, bay_uuid, k8s_api):
        """Find a pod based on pod uuid and the uuid for a bay.

        :param context: Security context
        :param uuid: the uuid of a pod.
        :param bay_uuid: the UUID of the Bay
        :param k8s_api: k8s API object

        :returns: a :class:`Pod` object.
        """
        try:
            resp = k8s_api.list_namespaced_pod(namespace='default')
        except rest.ApiException as err:
            raise exception.KubernetesAPIFailed(err=err)

        if resp is None:
            raise exception.PodListNotFound(bay_uuid=bay_uuid)

        pod = {}
        for pod_entry in resp.items:
            if pod_entry.metadata.uid == uuid:
                pod['uuid'] = pod_entry.metadata.uid
                pod['name'] = pod_entry.metadata.name
                pod['project_id'] = context.project_id
                pod['user_id'] = context.user_id
                pod['bay_uuid'] = bay_uuid
                pod['images'] = [c.image for c in pod_entry.spec.containers]
                if not pod_entry.metadata.labels:
                    pod['labels'] = {}
                else:
                    pod['labels'] = ast.literal_eval(pod_entry.metadata.labels)
                pod['status'] = pod_entry.status.phase
                pod['host'] = pod_entry.spec.node_name

                pod_obj = Pod(context, **pod)
                return pod_obj
        raise exception.PodNotFound(pod=uuid)

    @base.remotable_classmethod
    def get_by_name(cls, context, name, bay_uuid, k8s_api):
        """Find a pod based on pod name and the uuid for a bay.

        :param context: Security context
        :param name: the name of a pod.
        :param bay_uuid: the UUID of the Bay
        :param k8s_api: k8s API object

        :returns: a :class:`Pod` object.
        """
        try:
            resp = k8s_api.read_namespaced_pod(name=name, namespace='default')
        except rest.ApiException as err:
            raise exception.KubernetesAPIFailed(err=err)

        if resp is None:
            raise exception.PodNotFound(pod=name)

        pod = {}
        pod['uuid'] = resp.metadata.uid
        pod['name'] = resp.metadata.name
        pod['project_id'] = context.project_id
        pod['user_id'] = context.user_id
        pod['bay_uuid'] = bay_uuid
        pod['images'] = [c.image for c in resp.spec.containers]
        if not resp.metadata.labels:
            pod['labels'] = {}
        else:
            pod['labels'] = ast.literal_eval(resp.metadata.labels)
        pod['status'] = resp.status.phase
        pod['host'] = resp.spec.node_name

        pod_obj = Pod(context, **pod)
        return pod_obj
Exemple #21
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 #22
0
class Capsule(base.ZunPersistentObject, base.ZunObject):
    # Version 1.0: Initial version
    # Version 1.1: Add host to capsule
    # Version 1.2: Change the properties of meta_labels
    # Version 1.3: Add 'Deleting' to ContainerStatus
    # Version 1.4: Add addresses and volumes_info
    # Version 1.5: Change the properties of restort_policy
    # Version 1.6: Change the type of status
    VERSION = '1.6'

    fields = {
        'capsule_version': fields.StringField(nullable=True),
        'kind': fields.StringField(nullable=True),
        'restart_policy': fields.StringField(nullable=True),
        'host_selector': fields.StringField(nullable=True),
        'id': fields.IntegerField(),
        'uuid': fields.UUIDField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'user_id': fields.StringField(nullable=True),
        'status': z_fields.CapsuleStatusField(nullable=True),
        'status_reason': fields.StringField(nullable=True),
        'cpu': fields.FloatField(nullable=True),
        'memory': fields.StringField(nullable=True),
        'addresses': z_fields.JsonField(nullable=True),

        # conclude the readable message
        # 'key': 'value'--> 'time':'message'
        # wait until zun notify is finished
        # 'message': fields.DictOfStringsField(nullable=True),
        'spec': z_fields.JsonField(nullable=True),
        'meta_name': fields.StringField(nullable=True),
        'meta_labels': fields.DictOfStringsField(nullable=True),
        'containers': fields.ListOfObjectsField('Container', nullable=True),
        # The list of containers uuids inside the capsule
        'containers_uuids': fields.ListOfStringsField(nullable=True),
        'host': fields.StringField(nullable=True),

        # volumes_info records the volume and container attached
        # relationship:
        # {'<volume-uuid1>': ['<container-uuid1>', '<container-uuid2>'],
        # '<volume-uuid2>': ['<container-uuid2>', '<container-uuid3>']},
        # one container can attach at least one volume, also will support
        # one volume multiple in the future.
        'volumes_info': z_fields.JsonField(nullable=True),
    }

    @staticmethod
    def _from_db_object(capsule, db_capsule):
        """Converts a database entity to a formal object."""
        for field in capsule.fields:
            if field in CAPSULE_OPTIONAL_ATTRS:
                continue
            setattr(capsule, field, db_capsule[field])
        capsule.obj_reset_changes()
        return capsule

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

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

        :param uuid: the uuid of a capsule.
        :param context: Security context
        :returns: a :class:`Capsule` object.
        """
        db_capsule = dbapi.get_capsule_by_uuid(context, uuid)
        capsule = Capsule._from_db_object(cls(context), db_capsule)
        return capsule

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

        :param name: the meta_name of a capsule.
        :param context: Security context
        :returns: a :class:`Capsule` object.
        """
        db_capsule = dbapi.get_capsule_by_meta_name(context, name)
        capsule = Capsule._from_db_object(cls(context), db_capsule)
        return capsule

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None,
             filters=None):
        """Return a list of Capsule 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 capsules, the filter name could be
                        'name', 'image', 'project_id', 'user_id', 'memory'.
                        For example, filters={'image': 'nginx'}
        :returns: a list of :class:`Capsule` object.

        """
        db_capsules = dbapi.list_capsules(context,
                                          limit=limit,
                                          marker=marker,
                                          sort_key=sort_key,
                                          sort_dir=sort_dir,
                                          filters=filters)
        return Capsule._from_db_object_list(db_capsules, 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.: Capsule(context)

        """
        values = self.obj_get_changes()
        if 'containers' in values:
            raise exception.ObjectActionError(action='create',
                                              reason='containers assigned')

        db_capsule = dbapi.create_capsule(context, values)
        self._from_db_object(self, db_capsule)

    @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.: Capsule(context)
        """
        dbapi.destroy_capsule(context, self.uuid)
        self.obj_reset_changes()

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

        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.: Capsule(context)
        """
        updates = self.obj_get_changes()
        if 'containers' in updates:
            raise exception.ObjectActionError(action='save',
                                              reason='containers changed')
        dbapi.update_capsule(context, self.uuid, updates)

        self.obj_reset_changes()

    def as_dict(self):
        capsule_dict = super(Capsule, self).as_dict()
        capsule_dict['containers'] = [c.as_dict() for c in self.containers]
        return capsule_dict

    def obj_load_attr(self, attrname):
        if attrname not in CAPSULE_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,
        })

        if attrname == 'containers':
            self.containers = container.Container.list_by_capsule_id(
                self._context, self.id)

        self.obj_reset_changes(fields=[attrname])
Exemple #23
0
class Group(base.CinderPersistentObject, base.CinderObject,
            base.CinderObjectDictCompat, base.ClusteredObject):
    # Version 1.0: Initial version
    # Version 1.1: Added group_snapshots, group_snapshot_id, and
    #              source_group_id
    # Version 1.2: Added replication_status
    VERSION = '1.2'

    OPTIONAL_FIELDS = ['volumes', 'volume_types', 'group_snapshots']

    fields = {
        'id': fields.UUIDField(),
        'user_id': fields.StringField(),
        'project_id': fields.StringField(),
        'cluster_name': fields.StringField(nullable=True),
        'host': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'name': fields.StringField(nullable=True),
        'description': fields.StringField(nullable=True),
        'group_type_id': fields.StringField(),
        'volume_type_ids': fields.ListOfStringsField(nullable=True),
        'status': c_fields.GroupStatusField(nullable=True),
        'group_snapshot_id': fields.UUIDField(nullable=True),
        'source_group_id': fields.UUIDField(nullable=True),
        'replication_status': c_fields.ReplicationStatusField(nullable=True),
        'volumes': fields.ObjectField('VolumeList', nullable=True),
        'volume_types': fields.ObjectField('VolumeTypeList', nullable=True),
        'group_snapshots': fields.ObjectField('GroupSnapshotList',
                                              nullable=True),
    }

    def obj_make_compatible(self, primitive, target_version):
        """Make an object representation compatible with target version."""
        super(Group, self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 1):
            for key in ('group_snapshot_id', 'source_group_id',
                        'group_snapshots'):
                primitive.pop(key, None)
        if target_version < (1, 2):
            primitive.pop('replication_status', None)

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

        if 'volumes' in expected_attrs:
            volumes = base.obj_make_list(context, objects.VolumeList(context),
                                         objects.Volume, db_group['volumes'])
            group.volumes = volumes

        if 'volume_types' in expected_attrs:
            volume_types = base.obj_make_list(context,
                                              objects.VolumeTypeList(context),
                                              objects.VolumeType,
                                              db_group['volume_types'])
            group.volume_types = volume_types

        if 'group_snapshots' in expected_attrs:
            group_snapshots = base.obj_make_list(
                context, objects.GroupSnapshotList(context),
                objects.GroupSnapshot, db_group['group_snapshots'])
            group.group_snapshots = group_snapshots

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

    def create(self, group_snapshot_id=None, source_group_id=None):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason=_('already_created'))
        updates = self.cinder_obj_get_changes()

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

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

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

        db_groups = db.group_create(self._context, updates, group_snapshot_id,
                                    source_group_id)
        self._from_db_object(self._context, self, db_groups)

    def obj_load_attr(self, attrname):
        if attrname not in Group.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_by_group(
                self._context, self.id)

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

        if attrname == 'group_snapshots':
            self.group_snapshots = objects.GroupSnapshotList.get_all_by_group(
                self._context, self.id)

        self.obj_reset_changes(fields=[attrname])

    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'volume_types' in updates:
                msg = _('Cannot save volume_types changes in group object '
                        'update.')
                raise exception.ObjectActionError(action='save', reason=msg)
            if 'volumes' in updates:
                msg = _('Cannot save volumes changes in group object update.')
                raise exception.ObjectActionError(action='save', reason=msg)
            if 'group_snapshots' in updates:
                msg = _('Cannot save group_snapshots changes in group object '
                        'update.')
                raise exception.ObjectActionError(action='save', reason=msg)

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

    def destroy(self):
        with self.obj_as_admin():
            db.group_destroy(self._context, self.id)
Exemple #24
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 #25
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 #26
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)
class ReplicationController(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),
        'images': fields.ListOfStringsField(nullable=True),
        'bay_uuid': fields.StringField(nullable=True),
        'labels': fields.DictOfStringsField(nullable=True),
        'replicas': fields.IntegerField(nullable=True),
        'manifest_url': fields.StringField(nullable=True),
        'manifest': fields.StringField(nullable=True),
    }

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

        rc.obj_reset_changes()
        return rc

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

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

        Find ReplicationController based on id and return a
        ReplicationController object.

        :param rc_id: the id of a ReplicationController.
        :returns: a :class:`ReplicationController` object.
        """
        db_rc = cls.dbapi.get_rc_by_id(context, rc_id)
        rc = ReplicationController._from_db_object(cls(context), db_rc)
        return rc

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a ReplicationController based on uuid.

        Find ReplicationController by uuid and return a
        :class:`ReplicationController` object.

        :param uuid: the uuid of a ReplicationController.
        :param context: Security context
        :returns: a :class:`ReplicationController` object.
        """
        db_rc = cls.dbapi.get_rc_by_uuid(context, uuid)
        rc = ReplicationController._from_db_object(cls(context), db_rc)
        return rc

    @base.remotable_classmethod
    def get_by_name(cls, context, name):
        """Find a ReplicationController based on name.

        Find ReplicationController by name and return a
        :class:`ReplicationController` object.

        :param name: the name of a ReplicationController.
        :param context: Security context
        :returns: a :class:`ReplicationController` object.
        """
        db_rc = cls.dbapi.get_rc_by_name(context, name)
        rc = ReplicationController._from_db_object(cls(context), db_rc)
        return rc

    @base.remotable_classmethod
    def list(cls,
             context,
             limit=None,
             marker=None,
             sort_key=None,
             sort_dir=None):
        """Return a list of ReplicationController 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:`ReplicationController` object.

        """
        db_rcs = cls.dbapi.get_rc_list(context,
                                       limit=limit,
                                       marker=marker,
                                       sort_key=sort_key,
                                       sort_dir=sort_dir)
        return ReplicationController._from_db_object_list(db_rcs, cls, context)

    @base.remotable
    def create(self, context=None):
        """Create a ReplicationController 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.: ReplicationController(context)

        """
        values = self.obj_get_changes()
        db_rc = self.dbapi.create_rc(values)
        self._from_db_object(self, db_rc)

    @base.remotable
    def destroy(self, context=None):
        """Delete the ReplicationController 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.: ReplicationController(context)
        """
        self.dbapi.destroy_rc(self.uuid)
        self.obj_reset_changes()

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

        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.: ReplicationController(context)
        """
        updates = self.obj_get_changes()
        self.dbapi.update_rc(self.uuid, updates)

        self.obj_reset_changes()

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

        Loads a rc with the same uuid from the database and
        checks for updated attributes. Updates are applied from
        the loaded rc 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.: ReplicationController(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 #28
0
class ComputeNode(base.ZunPersistentObject, base.ZunObject):
    # Version 1.0: Initial version
    # Version 1.1: Add mem_total, mem_free, mem_available columns
    # Version 1.2: Add total, running, pasued, stopped containers columns
    # Version 1.3: Add cpus, cpu_used
    # Version 1.4: Add host operating system info
    # Version 1.5: Add host labels info
    # Version 1.6: Add mem_used to compute node
    # Version 1.7: Change get_by_hostname to get_by_name
    # Version 1.8: Add pci_device_pools to compute node
    # Version 1.9: Change PciDevicePoolList to ObjectField
    # Version 1.10: Add disk_total, disk_used columns
    # Version 1.11: Add disk_quota_supported field
    # Version 1.12: Add runtimes field
    VERSION = '1.12'

    fields = {
        'uuid': fields.UUIDField(read_only=True, nullable=False),
        'numa_topology': fields.ObjectField('NUMATopology', nullable=True),
        'hostname': fields.StringField(nullable=False),
        'mem_total': fields.IntegerField(nullable=False),
        'mem_free': fields.IntegerField(nullable=False),
        'mem_available': fields.IntegerField(nullable=False),
        'mem_used': fields.IntegerField(nullable=False),
        'total_containers': fields.IntegerField(nullable=False),
        'running_containers': fields.IntegerField(nullable=False),
        'paused_containers': fields.IntegerField(nullable=False),
        'stopped_containers': fields.IntegerField(nullable=False),
        'cpus': fields.IntegerField(nullable=False),
        'cpu_used': fields.FloatField(nullable=False),
        'architecture': fields.StringField(nullable=True),
        'os_type': fields.StringField(nullable=True),
        'os': fields.StringField(nullable=True),
        'kernel_version': fields.StringField(nullable=True),
        'labels': fields.DictOfStringsField(nullable=True),
        # NOTE(pmurray): the pci_device_pools field maps to the
        # pci_stats field in the database
        'pci_device_pools': fields.ObjectField('PciDevicePoolList',
                                               nullable=True),
        'disk_total': fields.IntegerField(nullable=False),
        'disk_used': fields.IntegerField(nullable=False),
        'disk_quota_supported': fields.BooleanField(nullable=False),
        'runtimes': fields.ListOfStringsField(nullable=True),
    }

    @staticmethod
    def _from_db_object(context, compute_node, db_compute_node):
        """Converts a database entity to a formal object."""
        special_cases = set(['pci_device_pools'])
        fields = set(compute_node.fields) - special_cases
        for field in fields:
            if field == 'numa_topology':
                numa_obj = NUMATopology._from_dict(
                    db_compute_node['numa_topology'])
                compute_node.numa_topology = numa_obj
            else:
                setattr(compute_node, field, db_compute_node[field])

        pci_stats = db_compute_node.get('pci_stats')
        if pci_stats is not None:
            pci_stats = pci_device_pool.from_pci_stats(pci_stats)
        compute_node.pci_device_pools = pci_stats
        compute_node.obj_reset_changes(recursive=True)
        return compute_node

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

    @staticmethod
    def _convert_pci_stats_to_db_format(updates):
        if 'pci_device_pools' in updates:
            pools = updates.pop('pci_device_pools')
            if pools is not None:
                pools = jsonutils.dumps(pools.obj_to_primitive())
            updates['pci_stats'] = pools

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

        :param context: Security context.

        """
        values = self.obj_get_changes()
        numa_obj = values.pop('numa_topology', None)
        if numa_obj is not None:
            values['numa_topology'] = numa_obj._to_dict()

        self._convert_pci_stats_to_db_format(values)
        db_compute_node = dbapi.create_compute_node(context, values)
        self._from_db_object(context, self, db_compute_node)

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid):
        """Find a compute node based on uuid.

        :param uuid: the uuid of a compute node.
        :param context: Security context
        :returns: a :class:`ComputeNode` object.
        """
        db_compute_node = dbapi.get_compute_node(context, uuid)
        compute_node = ComputeNode._from_db_object(
            context, cls(context), db_compute_node)
        return compute_node

    @base.remotable_classmethod
    def get_by_name(cls, context, hostname):
        db_compute_node = dbapi.get_compute_node_by_hostname(
            context, hostname)
        return cls._from_db_object(context, cls(), db_compute_node)

    @base.remotable_classmethod
    def list(cls, context, limit=None, marker=None,
             sort_key=None, sort_dir=None, filters=None):
        """Return a list of ComputeNode 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 resource providers.
        :returns: a list of :class:`ComputeNode` object.

        """
        db_compute_nodes = dbapi.list_compute_nodes(
            context, limit=limit, marker=marker, sort_key=sort_key,
            sort_dir=sort_dir, filters=filters)
        return ComputeNode._from_db_object_list(
            db_compute_nodes, cls, context)

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

        :param context: Security context.
        """
        dbapi.destroy_compute_node(context, self.uuid)
        self.obj_reset_changes(recursive=True)

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

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

        :param context: Security context.
        """
        updates = self.obj_get_changes()
        numa_obj = updates.pop('numa_topology', None)
        if numa_obj is not None:
            updates['numa_topology'] = numa_obj._to_dict()
        self._convert_pci_stats_to_db_format(updates)
        dbapi.update_compute_node(context, self.uuid, updates)
        self.obj_reset_changes(recursive=True)

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

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

        :param context: Security 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))
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']

    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(), network.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

    @classmethod
    def find_candidate_subnets(cls, context, network_id, host, service_type,
                               fixed_configured):
        """Find canditate subnets for the network, host, and service_type"""
        query = cls.query_subnets_on_network(context, network_id)
        query = SubnetServiceType.query_filter_service_subnets(
            query, service_type)

        # Select candidate subnets and return them
        if not cls.is_host_set(host):
            if fixed_configured:
                # If fixed_ips in request and host is not known all subnets on
                # the network are candidates. Host/Segment will be validated
                # on port update with binding:host_id set. Allocation _cannot_
                # be deferred as requested fixed_ips would then be lost.
                return query.all()
            # If the host isn't known, we can't allocate on a routed network.
            # So, exclude any subnets attached to segments.
            return cls._query_exclude_subnets_on_segments(query).all()

        # The host is known. Consider both routed and non-routed networks
        results = cls._query_filter_by_segment_host_mapping(query, host).all()

        # For now, we're using a simplifying assumption that a host will only
        # touch one segment in a given routed network.  Raise exception
        # otherwise.  This restriction may be relaxed as use cases for multiple
        # mappings are understood.
        segment_ids = {
            subnet.segment_id
            for subnet, mapping in results if mapping
        }
        if 1 < len(segment_ids):
            raise segment_exc.HostConnectedToMultipleSegments(
                host=host, network_id=network_id)

        return [subnet for subnet, _mapping in results]

    @classmethod
    def _query_filter_by_segment_host_mapping(cls, query, host):
        # TODO(tuanvu): find OVO-like solution for handling "join queries" and
        #               write unit test for this function
        """Excludes subnets on segments not reachable by the host

        The query gets two kinds of subnets: those that are on segments that
        the host can reach and those that are not on segments at all (assumed
        reachable by all hosts). Hence, subnets on segments that the host
        *cannot* reach are excluded.
        """
        SegmentHostMapping = segment_model.SegmentHostMapping

        # A host has been provided.  Consider these two scenarios
        # 1. Not a routed network:  subnets are not on segments
        # 2. Is a routed network:  only subnets on segments mapped to host
        # The following join query returns results for either.  The two are
        # guaranteed to be mutually exclusive when subnets are created.
        query = query.add_entity(SegmentHostMapping)
        query = query.outerjoin(
            SegmentHostMapping,
            and_(cls.db_model.segment_id == SegmentHostMapping.segment_id,
                 SegmentHostMapping.host == host))

        # Essentially "segment_id IS NULL XNOR host IS NULL"
        query = query.filter(
            or_(
                and_(cls.db_model.segment_id.isnot(None),
                     SegmentHostMapping.host.isnot(None)),
                and_(cls.db_model.segment_id.is_(None),
                     SegmentHostMapping.host.is_(None))))
        return query

    @classmethod
    def query_subnets_on_network(cls, context, network_id):
        query = model_query.get_collection_query(context, cls.db_model)
        return query.filter(cls.db_model.network_id == network_id)

    @classmethod
    def _query_exclude_subnets_on_segments(cls, query):
        """Excludes all subnets associated with segments

        For the case where the host is not known, we don't consider any subnets
        that are on segments. But, we still consider subnets that are not
        associated with any segment (i.e. for non-routed networks).
        """
        return query.filter(cls.db_model.segment_id.is_(None))

    @classmethod
    def is_host_set(cls, host):
        """Utility to tell if the host is set in the port binding"""
        # This seems redundant, but its not. Host is unset if its None, '',
        # or ATTR_NOT_SPECIFIED due to differences in host binding
        # implementations.
        return host and validators.is_attr_set(host)

    @classmethod
    def network_has_no_subnet(cls, context, network_id, host, service_type):
        # Determine why we found no subnets to raise the right error
        query = cls.query_subnets_on_network(context, network_id)

        if cls.is_host_set(host):
            # Empty because host isn't mapped to a segment with a subnet?
            s_query = query.filter(cls.db_model.segment_id.isnot(None))
            if s_query.limit(1).count() != 0:
                # It is a routed network but no subnets found for host
                raise segment_exc.HostNotConnectedToAnySegment(
                    host=host, network_id=network_id)

        if not query.limit(1).count():
            # Network has *no* subnets of any kind. This isn't an error.
            return True

        # Does filtering ineligible service subnets makes the list empty?
        query = SubnetServiceType.query_filter_service_subnets(
            query, service_type)
        if query.limit(1).count():
            # No, must be a deferred IP port because there are matching
            # subnets. Happens on routed networks when host isn't known.
            raise ipam_exceptions.DeferIpam()
        return False
Exemple #30
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]