class Obj(atypes.Base): id = atypes.wsattr(int, mandatory=True) name = str readonly_field = atypes.wsattr(str, readonly=True) default_field = atypes.wsattr(str, default='foo') unset_me = str
class BIOSSetting(base.APIBase): """API representation of a BIOS setting.""" name = atypes.wsattr(str) value = atypes.wsattr(str) links = atypes.wsattr([link.Link], readonly=True) def __init__(self, **kwargs): self.fields = [] fields = list(objects.BIOSSetting.fields) for k in fields: if hasattr(self, k): self.fields.append(k) value = kwargs.get(k, atypes.Unset) setattr(self, k, value) @staticmethod def _convert_with_links(bios, node_uuid, url): """Add links to the bios setting.""" name = bios.name bios.links = [link.Link.make_link('self', url, 'nodes', "%s/bios/%s" % (node_uuid, name)), link.Link.make_link('bookmark', url, 'nodes', "%s/bios/%s" % (node_uuid, name), bookmark=True)] return bios @classmethod def convert_with_links(cls, rpc_bios, node_uuid): """Add links to the bios setting.""" bios = BIOSSetting(**rpc_bios.as_dict()) return cls._convert_with_links(bios, node_uuid, api.request.host_url)
class APIBase(Base): created_at = atypes.wsattr(datetime.datetime, readonly=True) """The time in UTC at which the object is created""" updated_at = atypes.wsattr(datetime.datetime, readonly=True) """The time in UTC at which the object is updated"""
class Volume(base.APIBase): """API representation of a volume root. This class exists as a root class for the volume connectors and volume targets controllers. """ links = atypes.wsattr([link.Link], readonly=True) """A list containing a self link and associated volume links""" connectors = atypes.wsattr([link.Link], readonly=True) """Links to the volume connectors resource""" targets = atypes.wsattr([link.Link], readonly=True) """Links to the volume targets resource""" @staticmethod def convert(node_ident=None): url = api.request.public_url volume = Volume() if node_ident: resource = 'nodes' args = '%s/volume/' % node_ident else: resource = 'volume' args = '' volume.links = [ link.Link.make_link('self', url, resource, args), link.Link.make_link('bookmark', url, resource, args, bookmark=True) ] volume.connectors = [ link.Link.make_link('self', url, resource, args + 'connectors'), link.Link.make_link('bookmark', url, resource, args + 'connectors', bookmark=True) ] volume.targets = [ link.Link.make_link('self', url, resource, args + 'targets'), link.Link.make_link('bookmark', url, resource, args + 'targets', bookmark=True) ] return volume
def test_wsattr_weakref_datatype(self): # If the datatype inside the wsattr ends up a weakref, it # should be converted to the real type when accessed again by # the property getter. import weakref a = types.wsattr(int) a.datatype = weakref.ref(int) assert a.datatype is int
def test_wsattr_list_datatype(self): # If the datatype inside the wsattr ends up a list of weakrefs # to types, it should be converted to the real types when # accessed again by the property getter. import weakref a = types.wsattr(int) a.datatype = [weakref.ref(int)] assert isinstance(a.datatype, list) assert a.datatype[0] is int
class DeployStepType(atypes.Base, base.AsDictMixin): """A type describing a deployment step.""" interface = atypes.wsattr(_DEPLOY_INTERFACE_TYPE, mandatory=True) step = atypes.wsattr(str, mandatory=True) args = atypes.wsattr({str: types.jsontype}, mandatory=True) priority = atypes.wsattr(atypes.IntegerType(0), mandatory=True) def __init__(self, **kwargs): self.fields = ['interface', 'step', 'args', 'priority'] for field in self.fields: value = kwargs.get(field, atypes.Unset) setattr(self, field, value) def sanitize(self): """Removes sensitive data.""" if self.args != atypes.Unset: self.args = strutils.mask_dict_password(self.args, "******")
class B2(types.Base): b2 = types.wsattr('B2')
class A(object): b = types.wsattr('B')
class A(object): bs = types.wsattr(['B'])
class MyType(object): a = types.wsattr(int)
class ABCDType(object): a_list = types.wsattr([int], name='a.list') astr = str
class VolumeConnector(base.APIBase): """API representation of a volume connector. This class enforces type checking and value constraints, and converts between the internal object model and the API representation of a volume connector. """ _node_uuid = None def _get_node_uuid(self): return self._node_uuid def _set_node_identifiers(self, value): """Set both UUID and ID of a node for VolumeConnector object :param value: UUID, ID of a node, or atypes.Unset """ if value == atypes.Unset: self._node_uuid = atypes.Unset elif value and self._node_uuid != value: try: node = objects.Node.get(api.request.context, value) self._node_uuid = node.uuid # NOTE(smoriya): Create the node_id attribute on-the-fly # to satisfy the api -> rpc object conversion. self.node_id = node.id except exception.NodeNotFound as e: # Change error code because 404 (NotFound) is inappropriate # response for a POST request to create a VolumeConnector e.code = http_client.BAD_REQUEST # BadRequest raise uuid = types.uuid """Unique UUID for this volume connector""" type = atypes.wsattr(str, mandatory=True) """The type of volume connector""" connector_id = atypes.wsattr(str, mandatory=True) """The connector_id for this volume connector""" extra = {str: types.jsontype} """The metadata for this volume connector""" node_uuid = atypes.wsproperty(types.uuid, _get_node_uuid, _set_node_identifiers, mandatory=True) """The UUID of the node this volume connector belongs to""" links = atypes.wsattr([link.Link], readonly=True) """A list containing a self link and associated volume connector links""" def __init__(self, **kwargs): self.fields = [] fields = list(objects.VolumeConnector.fields) for field in fields: # Skip fields we do not expose. if not hasattr(self, field): continue self.fields.append(field) setattr(self, field, kwargs.get(field, atypes.Unset)) # NOTE(smoriya): node_id is an attribute created on-the-fly # by _set_node_uuid(), it needs to be present in the fields so # that as_dict() will contain node_id field when converting it # before saving it in the database. self.fields.append('node_id') # NOTE(smoriya): node_uuid is not part of objects.VolumeConnector.- # fields because it's an API-only attribute self.fields.append('node_uuid') # NOTE(jtaryma): Additionally to node_uuid, node_id is handled as a # secondary identifier in case RPC volume connector object dictionary # was passed to the constructor. self.node_uuid = kwargs.get('node_uuid') or kwargs.get( 'node_id', atypes.Unset) @staticmethod def _convert_with_links(connector, url): connector.links = [ link.Link.make_link('self', url, 'volume/connectors', connector.uuid), link.Link.make_link('bookmark', url, 'volume/connectors', connector.uuid, bookmark=True) ] return connector @classmethod def convert_with_links(cls, rpc_connector, fields=None, sanitize=True): connector = VolumeConnector(**rpc_connector.as_dict()) if fields is not None: api_utils.check_for_invalid_fields(fields, connector.as_dict()) connector = cls._convert_with_links(connector, api.request.public_url) if not sanitize: return connector connector.sanitize(fields) return connector def sanitize(self, fields=None): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ if fields is not None: self.unset_fields_except(fields) # never expose the node_id attribute self.node_id = atypes.Unset @classmethod def sample(cls, expand=True): time = datetime.datetime(2000, 1, 1, 12, 0, 0) sample = cls(uuid='86cfd480-0842-4abb-8386-e46149beb82f', type='iqn', connector_id='iqn.2010-10.org.openstack:51332b70524', extra={'foo': 'bar'}, created_at=time, updated_at=time) sample._node_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' fields = None if expand else _DEFAULT_RETURN_FIELDS return cls._convert_with_links(sample, 'http://localhost:6385', fields=fields)
class Portgroup(base.APIBase): """API representation of a portgroup. This class enforces type checking and value constraints, and converts between the internal object model and the API representation of a portgroup. """ _node_uuid = None def _get_node_uuid(self): return self._node_uuid def _set_node_uuid(self, value): if value and self._node_uuid != value: if not api_utils.allow_portgroups(): self._node_uuid = atypes.Unset return try: node = objects.Node.get(api.request.context, value) self._node_uuid = node.uuid # NOTE: Create the node_id attribute on-the-fly # to satisfy the api -> rpc object # conversion. self.node_id = node.id except exception.NodeNotFound as e: # Change error code because 404 (NotFound) is inappropriate # response for a POST request to create a Portgroup e.code = http_client.BAD_REQUEST raise e elif value == atypes.Unset: self._node_uuid = atypes.Unset uuid = types.uuid """Unique UUID for this portgroup""" address = atypes.wsattr(types.macaddress) """MAC Address for this portgroup""" extra = {str: types.jsontype} """This portgroup's meta data""" internal_info = atypes.wsattr({str: types.jsontype}, readonly=True) """This portgroup's internal info""" node_uuid = atypes.wsproperty(types.uuid, _get_node_uuid, _set_node_uuid, mandatory=True) """The UUID of the node this portgroup belongs to""" name = atypes.wsattr(str) """The logical name for this portgroup""" links = None """A list containing a self link and associated portgroup links""" standalone_ports_supported = types.boolean """Indicates whether ports of this portgroup may be used as single NIC ports""" mode = atypes.wsattr(str) """The mode for this portgroup. See linux bonding documentation for details: https://www.kernel.org/doc/Documentation/networking/bonding.txt""" properties = {str: types.jsontype} """This portgroup's properties""" ports = None """Links to the collection of ports of this portgroup""" def __init__(self, **kwargs): self.fields = [] fields = list(objects.Portgroup.fields) # NOTE: node_uuid is not part of objects.Portgroup.fields # because it's an API-only attribute fields.append('node_uuid') for field in fields: # Skip fields we do not expose. if not hasattr(self, field): continue self.fields.append(field) setattr(self, field, kwargs.get(field, atypes.Unset)) # NOTE: node_id is an attribute created on-the-fly # by _set_node_uuid(), it needs to be present in the fields so # that as_dict() will contain node_id field when converting it # before saving it in the database. self.fields.append('node_id') setattr(self, 'node_uuid', kwargs.get('node_id', atypes.Unset)) @staticmethod def _convert_with_links(portgroup, url, fields=None): """Add links to the portgroup.""" if fields is None: portgroup.ports = [ link.make_link('self', url, 'portgroups', portgroup.uuid + "/ports"), link.make_link('bookmark', url, 'portgroups', portgroup.uuid + "/ports", bookmark=True) ] # never expose the node_id attribute portgroup.node_id = atypes.Unset portgroup.links = [ link.make_link('self', url, 'portgroups', portgroup.uuid), link.make_link('bookmark', url, 'portgroups', portgroup.uuid, bookmark=True) ] return portgroup @classmethod def convert_with_links(cls, rpc_portgroup, fields=None, sanitize=True): """Add links to the portgroup.""" portgroup = Portgroup(**rpc_portgroup.as_dict()) if fields is not None: api_utils.check_for_invalid_fields(fields, portgroup.as_dict()) portgroup = cls._convert_with_links(portgroup, api.request.host_url, fields=fields) if not sanitize: return portgroup portgroup.sanitize(fields) return portgroup def sanitize(self, fields=None): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ if fields is not None: self.unset_fields_except(fields) # never expose the node_id attribute self.node_id = atypes.Unset @classmethod def sample(cls, expand=True): """Return a sample of the portgroup.""" sample = cls(uuid='a594544a-2daf-420c-8775-17a8c3e0852f', address='fe:54:00:77:07:d9', name='node1-portgroup-01', extra={'foo': 'bar'}, internal_info={'baz': 'boo'}, standalone_ports_supported=True, mode='active-backup', properties={}, created_at=datetime.datetime(2000, 1, 1, 12, 0, 0), updated_at=datetime.datetime(2000, 1, 1, 12, 0, 0)) # NOTE(lucasagomes): node_uuid getter() method look at the # _node_uuid variable sample._node_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' fields = None if expand else _DEFAULT_RETURN_FIELDS return cls._convert_with_links(sample, 'http://localhost:6385', fields=fields)
class Conductor(base.APIBase): """API representation of a bare metal conductor.""" hostname = atypes.wsattr(str) """The hostname for this conductor""" conductor_group = atypes.wsattr(str) """The conductor group this conductor belongs to""" alive = types.boolean """Indicates whether this conductor is considered alive""" drivers = atypes.wsattr([str]) """The drivers enabled on this conductor""" links = None """A list containing a self link and associated conductor links""" def __init__(self, **kwargs): self.fields = [] fields = list(objects.Conductor.fields) # NOTE(kaifeng): alive is not part of objects.Conductor.fields # because it's an API-only attribute. fields.append('alive') for field in fields: # Skip fields we do not expose. if not hasattr(self, field): continue self.fields.append(field) setattr(self, field, kwargs.get(field, atypes.Unset)) @staticmethod def _convert_with_links(conductor, url, fields=None): conductor.links = [link.make_link('self', url, 'conductors', conductor.hostname), link.make_link('bookmark', url, 'conductors', conductor.hostname, bookmark=True)] return conductor @classmethod def convert_with_links(cls, rpc_conductor, fields=None): conductor = Conductor(**rpc_conductor.as_dict()) conductor.alive = not timeutils.is_older_than( conductor.updated_at, CONF.conductor.heartbeat_timeout) if fields is not None: api_utils.check_for_invalid_fields(fields, conductor.as_dict()) conductor = cls._convert_with_links(conductor, api.request.public_url, fields=fields) conductor.sanitize(fields) return conductor def sanitize(self, fields): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ if fields is not None: self.unset_fields_except(fields) @classmethod def sample(cls, expand=True): time = datetime.datetime(2000, 1, 1, 12, 0, 0) sample = cls(hostname='computer01', conductor_group='', alive=True, drivers=['ipmi'], created_at=time, updated_at=time) fields = None if expand else _DEFAULT_RETURN_FIELDS return cls._convert_with_links(sample, 'http://localhost:6385', fields=fields)
class DeployTemplate(base.APIBase): """API representation of a deploy template.""" uuid = types.uuid """Unique UUID for this deploy template.""" name = atypes.wsattr(str, mandatory=True) """The logical name for this deploy template.""" steps = atypes.wsattr([DeployStepType], mandatory=True) """The deploy steps of this deploy template.""" links = atypes.wsattr([link.Link]) """A list containing a self link and associated deploy template links.""" extra = {str: types.jsontype} """This deploy template's meta data""" def __init__(self, **kwargs): self.fields = [] fields = list(objects.DeployTemplate.fields) for field in fields: # Skip fields we do not expose. if not hasattr(self, field): continue value = kwargs.get(field, atypes.Unset) if field == 'steps' and value != atypes.Unset: value = [DeployStepType(**step) for step in value] self.fields.append(field) setattr(self, field, value) @staticmethod def validate(value): if value is None: return # The name is mandatory, but the 'mandatory' attribute support in # wsattr allows None. if value.name is None: err = _("Deploy template name cannot be None") raise exception.InvalidDeployTemplate(err=err) # The name must also be a valid trait. api_utils.validate_trait( value.name, error_prefix=_("Deploy template name must be a valid trait")) # There must be at least one step. if not value.steps: err = _("No deploy steps specified. A deploy template must have " "at least one deploy step.") raise exception.InvalidDeployTemplate(err=err) # TODO(mgoddard): Determine the consequences of allowing duplicate # steps. # * What if one step has zero priority and another non-zero? # * What if a step that is enabled by default is included in a # template? Do we override the default or add a second invocation? # Check for duplicate steps. Each interface/step combination can be # specified at most once. counter = collections.Counter((step.interface, step.step) for step in value.steps) duplicates = {key for key, count in counter.items() if count > 1} if duplicates: duplicates = {"interface: %s, step: %s" % (interface, step) for interface, step in duplicates} err = _("Duplicate deploy steps. A deploy template cannot have " "multiple deploy steps with the same interface and step. " "Duplicates: %s") % "; ".join(duplicates) raise exception.InvalidDeployTemplate(err=err) return value @staticmethod def _convert_with_links(template, url, fields=None): template.links = [ link.Link.make_link('self', url, 'deploy_templates', template.uuid), link.Link.make_link('bookmark', url, 'deploy_templates', template.uuid, bookmark=True) ] return template @classmethod def convert_with_links(cls, rpc_template, fields=None, sanitize=True): """Add links to the deploy template.""" template = DeployTemplate(**rpc_template.as_dict()) if fields is not None: api_utils.check_for_invalid_fields(fields, template.as_dict()) template = cls._convert_with_links(template, api.request.public_url, fields=fields) if sanitize: template.sanitize(fields) return template def sanitize(self, fields): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ if self.steps != atypes.Unset: for step in self.steps: step.sanitize() if fields is not None: self.unset_fields_except(fields) @classmethod def sample(cls, expand=True): time = datetime.datetime(2000, 1, 1, 12, 0, 0) template_uuid = '534e73fa-1014-4e58-969a-814cc0cb9d43' template_name = 'CUSTOM_RAID1' template_steps = [{ "interface": "raid", "step": "create_configuration", "args": { "logical_disks": [{ "size_gb": "MAX", "raid_level": "1", "is_root_volume": True }], "delete_configuration": True }, "priority": 10 }] template_extra = {'foo': 'bar'} sample = cls(uuid=template_uuid, name=template_name, steps=template_steps, extra=template_extra, created_at=time, updated_at=time) fields = None if expand else _DEFAULT_RETURN_FIELDS return cls._convert_with_links(sample, 'http://localhost:6385', fields=fields)
class JsonPatchType(base.Base): """A complex type that represents a single json-patch operation.""" path = atypes.wsattr(atypes.StringType(pattern='^(/[\\w-]+)+$'), mandatory=True) op = atypes.wsattr(atypes.Enum(str, 'add', 'replace', 'remove'), mandatory=True) value = atypes.wsattr(jsontype, default=atypes.Unset) # The class of the objects being patched. Override this in subclasses. # Should probably be a subclass of ironic.api.controllers.base.APIBase. _api_base = None # Attributes that are not required for construction, but which may not be # removed if set. Override in subclasses if needed. _extra_non_removable_attrs = set() # Set of non-removable attributes, calculated lazily. _non_removable_attrs = None @staticmethod def internal_attrs(): """Returns a list of internal attributes. Internal attributes can't be added, replaced or removed. This method may be overwritten by derived class. """ return ['/created_at', '/id', '/links', '/updated_at', '/uuid'] @classmethod def non_removable_attrs(cls): """Returns a set of names of attributes that may not be removed. Attributes whose 'mandatory' property is True are automatically added to this set. To add additional attributes to the set, override the field _extra_non_removable_attrs in subclasses, with a set of the form {'/foo', '/bar'}. """ if cls._non_removable_attrs is None: cls._non_removable_attrs = cls._extra_non_removable_attrs.copy() if cls._api_base: fields = inspect.getmembers(cls._api_base, lambda a: not inspect.isroutine(a)) for name, field in fields: if getattr(field, 'mandatory', False): cls._non_removable_attrs.add('/%s' % name) return cls._non_removable_attrs @staticmethod def validate(patch): _path = '/' + patch.path.split('/')[1] if _path in patch.internal_attrs(): msg = _("'%s' is an internal attribute and can not be updated") raise exception.ClientSideError(msg % patch.path) if patch.path in patch.non_removable_attrs() and patch.op == 'remove': msg = _("'%s' is a mandatory attribute and can not be removed") raise exception.ClientSideError(msg % patch.path) if patch.op != 'remove': if patch.value is atypes.Unset: msg = _("'add' and 'replace' operations need a value") raise exception.ClientSideError(msg) ret = {'path': patch.path, 'op': patch.op} if patch.value is not atypes.Unset: ret['value'] = patch.value return ret
class Chassis(base.APIBase): """API representation of a chassis. This class enforces type checking and value constraints, and converts between the internal object model and the API representation of a chassis. """ uuid = types.uuid """The UUID of the chassis""" description = atypes.StringType(max_length=255) """The description of the chassis""" extra = {str: types.jsontype} """The metadata of the chassis""" links = atypes.wsattr([link.Link], readonly=True) """A list containing a self link and associated chassis links""" nodes = atypes.wsattr([link.Link], readonly=True) """Links to the collection of nodes contained in this chassis""" def __init__(self, **kwargs): self.fields = [] for field in objects.Chassis.fields: # Skip fields we do not expose. if not hasattr(self, field): continue self.fields.append(field) setattr(self, field, kwargs.get(field, atypes.Unset)) @staticmethod def _convert_with_links(chassis, url, fields=None): if fields is None: chassis.nodes = [ link.Link.make_link('self', url, 'chassis', chassis.uuid + "/nodes"), link.Link.make_link('bookmark', url, 'chassis', chassis.uuid + "/nodes", bookmark=True) ] chassis.links = [ link.Link.make_link('self', url, 'chassis', chassis.uuid), link.Link.make_link('bookmark', url, 'chassis', chassis.uuid, bookmark=True) ] return chassis @classmethod def convert_with_links(cls, rpc_chassis, fields=None, sanitize=True): chassis = Chassis(**rpc_chassis.as_dict()) if fields is not None: api_utils.check_for_invalid_fields(fields, chassis.as_dict()) chassis = cls._convert_with_links(chassis, api.request.public_url, fields) if not sanitize: return chassis chassis.sanitize(fields) return chassis def sanitize(self, fields=None): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ if fields is not None: self.unset_fields_except(fields) @classmethod def sample(cls, expand=True): time = datetime.datetime(2000, 1, 1, 12, 0, 0) sample = cls(uuid='eaaca217-e7d8-47b4-bb41-3f99f20eed89', extra={}, description='Sample chassis', created_at=time, updated_at=time) fields = None if expand else _DEFAULT_RETURN_FIELDS return cls._convert_with_links(sample, 'http://localhost:6385', fields=fields)
class Port(base.APIBase): """API representation of a port. This class enforces type checking and value constraints, and converts between the internal object model and the API representation of a port. """ _node_uuid = None _portgroup_uuid = None def _get_node_uuid(self): return self._node_uuid def _set_node_uuid(self, value): if value and self._node_uuid != value: try: # FIXME(comstud): One should only allow UUID here, but # there seems to be a bug in that tests are passing an # ID. See bug #1301046 for more details. node = objects.Node.get(api.request.context, value) self._node_uuid = node.uuid # NOTE(lucasagomes): Create the node_id attribute on-the-fly # to satisfy the api -> rpc object # conversion. self.node_id = node.id except exception.NodeNotFound as e: # Change error code because 404 (NotFound) is inappropriate # response for a POST request to create a Port e.code = http_client.BAD_REQUEST # BadRequest raise elif value == atypes.Unset: self._node_uuid = atypes.Unset def _get_portgroup_uuid(self): return self._portgroup_uuid def _set_portgroup_uuid(self, value): if value and self._portgroup_uuid != value: if not api_utils.allow_portgroups_subcontrollers(): self._portgroup_uuid = atypes.Unset return try: portgroup = objects.Portgroup.get(api.request.context, value) if portgroup.node_id != self.node_id: raise exception.BadRequest( _('Port can not be added to a ' 'portgroup belonging to a ' 'different node.')) self._portgroup_uuid = portgroup.uuid # NOTE(lucasagomes): Create the portgroup_id attribute # on-the-fly to satisfy the api -> # rpc object conversion. self.portgroup_id = portgroup.id except exception.PortgroupNotFound as e: # Change error code because 404 (NotFound) is inappropriate # response for a POST request to create a Port e.code = http_client.BAD_REQUEST # BadRequest raise e elif value == atypes.Unset: self._portgroup_uuid = atypes.Unset elif value is None and api_utils.allow_portgroups_subcontrollers(): # This is to output portgroup_uuid field if API version allows this self._portgroup_uuid = None uuid = types.uuid """Unique UUID for this port""" address = atypes.wsattr(types.macaddress, mandatory=True) """MAC Address for this port""" extra = {str: types.jsontype} """This port's meta data""" internal_info = atypes.wsattr({str: types.jsontype}, readonly=True) """This port's internal information maintained by ironic""" node_uuid = atypes.wsproperty(types.uuid, _get_node_uuid, _set_node_uuid, mandatory=True) """The UUID of the node this port belongs to""" portgroup_uuid = atypes.wsproperty(types.uuid, _get_portgroup_uuid, _set_portgroup_uuid, mandatory=False) """The UUID of the portgroup this port belongs to""" pxe_enabled = types.boolean """Indicates whether pxe is enabled or disabled on the node.""" local_link_connection = types.locallinkconnectiontype """The port binding profile for the port""" physical_network = atypes.StringType(max_length=64) """The name of the physical network to which this port is connected.""" links = None """A list containing a self link and associated port links""" is_smartnic = types.boolean """Indicates whether this port is a Smart NIC port.""" def __init__(self, **kwargs): self.fields = [] fields = list(objects.Port.fields) # NOTE(lucasagomes): node_uuid is not part of objects.Port.fields # because it's an API-only attribute fields.append('node_uuid') # NOTE: portgroup_uuid is not part of objects.Port.fields # because it's an API-only attribute fields.append('portgroup_uuid') for field in fields: # Add fields we expose. if hasattr(self, field): self.fields.append(field) setattr(self, field, kwargs.get(field, atypes.Unset)) # NOTE(lucasagomes): node_id is an attribute created on-the-fly # by _set_node_uuid(), it needs to be present in the fields so # that as_dict() will contain node_id field when converting it # before saving it in the database. self.fields.append('node_id') setattr(self, 'node_uuid', kwargs.get('node_id', atypes.Unset)) # NOTE: portgroup_id is an attribute created on-the-fly # by _set_portgroup_uuid(), it needs to be present in the fields so # that as_dict() will contain portgroup_id field when converting it # before saving it in the database. self.fields.append('portgroup_id') setattr(self, 'portgroup_uuid', kwargs.get('portgroup_id', atypes.Unset)) @classmethod def convert_with_links(cls, rpc_port, fields=None, sanitize=True): port = Port(**rpc_port.as_dict()) port._validate_fields(fields) url = api.request.public_url port.links = [ link.make_link('self', url, 'ports', port.uuid), link.make_link('bookmark', url, 'ports', port.uuid, bookmark=True) ] if not sanitize: return port port.sanitize(fields=fields) return port def _validate_fields(self, fields=None): if fields is not None: api_utils.check_for_invalid_fields(fields, self.as_dict()) def sanitize(self, fields=None): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ hide_fields_in_newer_versions(self) if fields is not None: self.unset_fields_except(fields) # never expose the node_id attribute self.node_id = atypes.Unset # never expose the portgroup_id attribute self.portgroup_id = atypes.Unset @classmethod def sample(cls, expand=True): time = datetime.datetime(2000, 1, 1, 12, 0, 0) sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c', address='fe:54:00:77:07:d9', extra={'foo': 'bar'}, internal_info={}, created_at=time, updated_at=time, pxe_enabled=True, local_link_connection={ 'switch_info': 'host', 'port_id': 'Gig0/1', 'switch_id': 'aa:bb:cc:dd:ee:ff' }, physical_network='physnet1', is_smartnic=False) # NOTE(lucasagomes): node_uuid getter() method look at the # _node_uuid variable sample._node_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' sample._portgroup_uuid = '037d9a52-af89-4560-b5a3-a33283295ba2' fields = None if expand else _DEFAULT_RETURN_FIELDS return cls._convert_with_links(sample, 'http://localhost:6385', fields=fields)
class VolumeTarget(base.APIBase): """API representation of a volume target. This class enforces type checking and value constraints, and converts between the internal object model and the API representation of a volume target. """ _node_uuid = None def _get_node_uuid(self): return self._node_uuid def _set_node_identifiers(self, value): """Set both UUID and ID of a node for VolumeTarget object :param value: UUID, ID of a node, or atypes.Unset """ if value == atypes.Unset: self._node_uuid = atypes.Unset elif value and self._node_uuid != value: try: node = objects.Node.get(api.request.context, value) self._node_uuid = node.uuid # NOTE(smoriya): Create the node_id attribute on-the-fly # to satisfy the api -> rpc object conversion. self.node_id = node.id except exception.NodeNotFound as e: # Change error code because 404 (NotFound) is inappropriate # response for a POST request to create a VolumeTarget e.code = http_client.BAD_REQUEST # BadRequest raise uuid = types.uuid """Unique UUID for this volume target""" volume_type = atypes.wsattr(str, mandatory=True) """The volume_type of volume target""" properties = {str: types.jsontype} """The properties for this volume target""" boot_index = atypes.wsattr(int, mandatory=True) """The boot_index of volume target""" volume_id = atypes.wsattr(str, mandatory=True) """The volume_id for this volume target""" extra = {str: types.jsontype} """The metadata for this volume target""" node_uuid = atypes.wsproperty(types.uuid, _get_node_uuid, _set_node_identifiers, mandatory=True) """The UUID of the node this volume target belongs to""" links = atypes.wsattr([link.Link], readonly=True) """A list containing a self link and associated volume target links""" def __init__(self, **kwargs): self.fields = [] fields = list(objects.VolumeTarget.fields) for field in fields: # Skip fields we do not expose. if not hasattr(self, field): continue self.fields.append(field) setattr(self, field, kwargs.get(field, atypes.Unset)) # NOTE(smoriya): node_id is an attribute created on-the-fly # by _set_node_uuid(), it needs to be present in the fields so # that as_dict() will contain node_id field when converting it # before saving it in the database. self.fields.append('node_id') # NOTE(smoriya): node_uuid is not part of objects.VolumeTarget.- # fields because it's an API-only attribute self.fields.append('node_uuid') # NOTE(jtaryma): Additionally to node_uuid, node_id is handled as a # secondary identifier in case RPC volume target object dictionary # was passed to the constructor. self.node_uuid = kwargs.get('node_uuid') or kwargs.get('node_id', atypes.Unset) @staticmethod def _convert_with_links(target, url): target.links = [link.Link.make_link('self', url, 'volume/targets', target.uuid), link.Link.make_link('bookmark', url, 'volume/targets', target.uuid, bookmark=True) ] return target @classmethod def convert_with_links(cls, rpc_target, fields=None, sanitize=True): target = VolumeTarget(**rpc_target.as_dict()) if fields is not None: api_utils.check_for_invalid_fields(fields, target.as_dict()) target = cls._convert_with_links(target, api.request.public_url) if not sanitize: return target target.sanitize(fields) return target def sanitize(self, fields=None): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ if fields is not None: self.unset_fields_except(fields) # never expose the node_id attribute self.node_id = atypes.Unset @classmethod def sample(cls, expand=True): time = datetime.datetime(2000, 1, 1, 12, 0, 0) properties = {"auth_method": "CHAP", "auth_username": "******", "auth_password": "******", "target_iqn": "iqn.2010-10.com.example:vol-X", "target_portal": "192.168.0.123:3260", "volume_id": "a2f3ff15-b3ea-4656-ab90-acbaa1a07607", "target_lun": 0, "access_mode": "rw"} sample = cls(uuid='667808d4-622f-4629-b629-07753a19e633', volume_type='iscsi', boot_index=0, volume_id='a2f3ff15-b3ea-4656-ab90-acbaa1a07607', properties=properties, extra={'foo': 'bar'}, created_at=time, updated_at=time) sample._node_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' fields = None if expand else _DEFAULT_RETURN_FIELDS return cls._convert_with_links(sample, 'http://localhost:6385', fields=fields)
class Driver(base.Base): """API representation of a driver.""" name = str """The name of the driver""" hosts = [str] """A list of active conductors that support this driver""" type = str """Whether the driver is classic or dynamic (hardware type)""" links = atypes.wsattr([link.Link], readonly=True) """A list containing self and bookmark links""" properties = atypes.wsattr([link.Link], readonly=True) """A list containing links to driver properties""" """Default interface for a hardware type""" default_bios_interface = str default_boot_interface = str default_console_interface = str default_deploy_interface = str default_inspect_interface = str default_management_interface = str default_network_interface = str default_power_interface = str default_raid_interface = str default_rescue_interface = str default_storage_interface = str default_vendor_interface = str """A list of enabled interfaces for a hardware type""" enabled_bios_interfaces = [str] enabled_boot_interfaces = [str] enabled_console_interfaces = [str] enabled_deploy_interfaces = [str] enabled_inspect_interfaces = [str] enabled_management_interfaces = [str] enabled_network_interfaces = [str] enabled_power_interfaces = [str] enabled_raid_interfaces = [str] enabled_rescue_interfaces = [str] enabled_storage_interfaces = [str] enabled_vendor_interfaces = [str] @staticmethod def convert_with_links(name, hosts, detail=False, interface_info=None): """Convert driver/hardware type info to an API-serializable object. :param name: name of a hardware type. :param hosts: list of conductor hostnames driver is active on. :param detail: boolean, whether to include detailed info, such as the 'type' field and default/enabled interfaces fields. :param interface_info: optional list of dicts of hardware interface info. :returns: API-serializable driver object. """ driver = Driver() driver.name = name driver.hosts = hosts driver.links = [ link.Link.make_link('self', api.request.public_url, 'drivers', name), link.Link.make_link('bookmark', api.request.public_url, 'drivers', name, bookmark=True) ] if api_utils.allow_links_node_states_and_driver_properties(): driver.properties = [ link.Link.make_link('self', api.request.public_url, 'drivers', name + "/properties"), link.Link.make_link('bookmark', api.request.public_url, 'drivers', name + "/properties", bookmark=True) ] if api_utils.allow_dynamic_drivers(): # NOTE(dtantsur): only dynamic drivers (based on hardware types) # are supported starting with the Rocky release. driver.type = 'dynamic' if detail: if interface_info is None: # TODO(jroll) objectify this interface_info = (api.request.dbapi .list_hardware_type_interfaces([name])) for iface_type in driver_base.ALL_INTERFACES: default = None enabled = set() for iface in interface_info: if iface['interface_type'] == iface_type: iface_name = iface['interface_name'] enabled.add(iface_name) # NOTE(jroll) this assumes the default is the same # on all conductors if iface['default']: default = iface_name default_key = 'default_%s_interface' % iface_type enabled_key = 'enabled_%s_interfaces' % iface_type setattr(driver, default_key, default) setattr(driver, enabled_key, list(enabled)) hide_fields_in_newer_versions(driver) return driver @classmethod def sample(cls): attrs = { 'name': 'sample-driver', 'hosts': ['fake-host'], 'type': 'classic', } for iface_type in driver_base.ALL_INTERFACES: attrs['default_%s_interface' % iface_type] = None attrs['enabled_%s_interfaces' % iface_type] = None sample = cls(**attrs) return sample
class Allocation(base.APIBase): """API representation of an allocation. This class enforces type checking and value constraints, and converts between the internal object model and the API representation of a allocation. """ uuid = types.uuid """Unique UUID for this allocation""" extra = {str: types.jsontype} """This allocation's meta data""" node_uuid = atypes.wsattr(types.uuid, readonly=True) """The UUID of the node this allocation belongs to""" node = atypes.wsattr(str) """The node to backfill the allocation for (POST only)""" name = atypes.wsattr(str) """The logical name for this allocation""" links = atypes.wsattr([link.Link], readonly=True) """A list containing a self link and associated allocation links""" state = atypes.wsattr(str, readonly=True) """The current state of the allocation""" last_error = atypes.wsattr(str, readonly=True) """Last error that happened to this allocation""" resource_class = atypes.wsattr(atypes.StringType(max_length=80)) """Requested resource class for this allocation""" owner = atypes.wsattr(str) """Owner of allocation""" # NOTE(dtantsur): candidate_nodes is a list of UUIDs on the database level, # but the API level also accept names, converting them on fly. candidate_nodes = atypes.wsattr([str]) """Candidate nodes for this allocation""" traits = atypes.wsattr([str]) """Requested traits for the allocation""" def __init__(self, **kwargs): self.fields = [] fields = list(objects.Allocation.fields) # NOTE: node_uuid is not part of objects.Allocation.fields # because it's an API-only attribute fields.append('node_uuid') for field in fields: # Skip fields we do not expose. if not hasattr(self, field): continue self.fields.append(field) setattr(self, field, kwargs.get(field, atypes.Unset)) @staticmethod def _convert_with_links(allocation, url): """Add links to the allocation.""" # This field is only used in POST, never return it. allocation.node = atypes.Unset allocation.links = [ link.Link.make_link('self', url, 'allocations', allocation.uuid), link.Link.make_link('bookmark', url, 'allocations', allocation.uuid, bookmark=True) ] return allocation @classmethod def convert_with_links(cls, rpc_allocation, fields=None, sanitize=True): """Add links to the allocation.""" allocation = Allocation(**rpc_allocation.as_dict()) if rpc_allocation.node_id: try: allocation.node_uuid = objects.Node.get_by_id( api.request.context, rpc_allocation.node_id).uuid except exception.NodeNotFound: allocation.node_uuid = None else: allocation.node_uuid = None if fields is not None: api_utils.check_for_invalid_fields(fields, allocation.fields) # Make the default values consistent between POST and GET API if allocation.candidate_nodes is None: allocation.candidate_nodes = [] if allocation.traits is None: allocation.traits = [] allocation = cls._convert_with_links(allocation, api.request.host_url) if not sanitize: return allocation allocation.sanitize(fields) return allocation def sanitize(self, fields=None): """Removes sensitive and unrequested data. Will only keep the fields specified in the ``fields`` parameter. :param fields: list of fields to preserve, or ``None`` to preserve them all :type fields: list of str """ hide_fields_in_newer_versions(self) if fields is not None: self.unset_fields_except(fields) @classmethod def sample(cls): """Return a sample of the allocation.""" sample = cls(uuid='a594544a-2daf-420c-8775-17a8c3e0852f', node_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae', name='node1-allocation-01', state=ir_states.ALLOCATING, last_error=None, resource_class='baremetal', traits=['CUSTOM_GPU'], candidate_nodes=[], extra={'foo': 'bar'}, created_at=datetime.datetime(2000, 1, 1, 12, 0, 0), updated_at=datetime.datetime(2000, 1, 1, 12, 0, 0), owner=None) return cls._convert_with_links(sample, 'http://localhost:6385')
class MyBaseType(object): """Helper class, patched by objects of type MyPatchType""" mandatory = atypes.wsattr(str, mandatory=True)