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)
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)
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), }
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'])]
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
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]
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
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
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)
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
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)
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
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
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)
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())
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
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()
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)
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
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)
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])
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)
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
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
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]
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))
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
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]