Exemplo n.º 1
0
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
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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"""
Exemplo n.º 4
0
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
Exemplo n.º 5
0
 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
Exemplo n.º 6
0
 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
Exemplo n.º 7
0
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, "******")
Exemplo n.º 8
0
 class B2(types.Base):
     b2 = types.wsattr('B2')
Exemplo n.º 9
0
 class A(object):
     b = types.wsattr('B')
Exemplo n.º 10
0
 class A(object):
     bs = types.wsattr(['B'])
Exemplo n.º 11
0
 class MyType(object):
     a = types.wsattr(int)
Exemplo n.º 12
0
 class ABCDType(object):
     a_list = types.wsattr([int], name='a.list')
     astr = str
Exemplo n.º 13
0
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)
Exemplo n.º 14
0
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)
Exemplo n.º 15
0
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)
Exemplo n.º 16
0
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)
Exemplo n.º 17
0
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
Exemplo n.º 18
0
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)
Exemplo n.º 19
0
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)
Exemplo n.º 20
0
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)
Exemplo n.º 21
0
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
Exemplo n.º 22
0
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')
Exemplo n.º 23
0
class MyBaseType(object):
    """Helper class, patched by objects of type MyPatchType"""
    mandatory = atypes.wsattr(str, mandatory=True)