示例#1
0
 def test_validate_string_type_pattern_exception_message(self):
     regex = '^[a-zA-Z0-9]*$'
     v = types.StringType(pattern=regex)
     try:
         v.validate('_')
         self.assertFail()
     except ValueError as e:
         self.assertIn(regex, str(e))
示例#2
0
    def test_validate_string_type_precompile(self):
        precompile = re.compile('^[a-zA-Z0-9]*$')
        v = types.StringType(min_length=1, max_length=10, pattern=precompile)

        # Test a pattern validation
        v.validate('a')
        v.validate('A')
        self.assertRaises(ValueError, v.validate, '_')
示例#3
0
    def test_validate_string_type(self):
        v = types.StringType(min_length=1,
                             max_length=10,
                             pattern='^[a-zA-Z0-9]*$')
        v.validate('1')
        v.validate('12345')
        v.validate('1234567890')
        self.assertRaises(ValueError, v.validate, '')
        self.assertRaises(ValueError, v.validate, '12345678901')

        # Test a pattern validation
        v.validate('a')
        v.validate('A')
        self.assertRaises(ValueError, v.validate, '_')
示例#4
0
文件: chassis.py 项目: younkun/ironic
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 = None
    """A list containing a self link and associated chassis links"""

    nodes = None
    """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.make_link('self', url, 'chassis',
                               chassis.uuid + "/nodes"),
                link.make_link('bookmark',
                               url,
                               'chassis',
                               chassis.uuid + "/nodes",
                               bookmark=True)
            ]
        chassis.links = [
            link.make_link('self', url, 'chassis', chassis.uuid),
            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)
示例#5
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')
示例#6
0
文件: port.py 项目: younkun/ironic
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)
示例#7
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