Ejemplo n.º 1
0
def validate_sort_dir(sort_dir):
    if sort_dir not in ['asc', 'desc']:
        raise exception.ClientSideError(
            _("Invalid sort direction: %s. "
              "Acceptable values are "
              "'asc' or 'desc'") % sort_dir)
    return sort_dir
Ejemplo n.º 2
0
def combine_args(funcdef, akw, allow_override=False):
    newargs, newkwargs = [], {}
    for args, kwargs in akw:
        for i, arg in enumerate(args):
            n = funcdef.arguments[i].name
            if not allow_override and n in newkwargs:
                raise exception.ClientSideError(
                    "Parameter %s was given several times" % n)
            newkwargs[n] = arg
        for name, value in kwargs.items():
            n = str(name)
            if not allow_override and n in newkwargs:
                raise exception.ClientSideError(
                    "Parameter %s was given several times" % n)
            newkwargs[n] = value
    return newargs, newkwargs
Ejemplo n.º 3
0
def apply_jsonpatch(doc, patch):
    """Apply a JSON patch, one operation at a time.

    If the patch fails to apply, this allows us to determine which operation
    failed, making the error message a little less cryptic.

    :param doc: The JSON document to patch.
    :param patch: The JSON patch to apply.
    :returns: The result of the patch operation.
    :raises: PatchError if the patch fails to apply.
    :raises: exception.ClientSideError if the patch adds a new root attribute.
    """
    # Prevent removal of root attributes.
    for p in patch:
        if p['op'] == 'add' and p['path'].count('/') == 1:
            if p['path'].lstrip('/') not in doc:
                msg = _('Adding a new attribute (%s) to the root of '
                        'the resource is not allowed')
                raise exception.ClientSideError(msg % p['path'])

    # Apply operations one at a time, to improve error reporting.
    for patch_op in patch:
        try:
            doc = jsonpatch.apply_patch(doc, jsonpatch.JsonPatch([patch_op]))
        except _JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch_op, reason=e)
    return doc
Ejemplo n.º 4
0
def validate_limit(limit):
    if limit is None:
        return CONF.api.max_limit

    if limit <= 0:
        raise exception.ClientSideError(_("Limit must be positive"))

    return min(CONF.api.max_limit, limit)
Ejemplo n.º 5
0
    def post(self, portgroup):
        """Create a new portgroup.

        :param portgroup: a portgroup within the request body.
        """
        if not api_utils.allow_portgroups():
            raise exception.NotFound()

        context = api.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:portgroup:create', cdict, cdict)

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        if (not api_utils.allow_portgroup_mode_properties()
                and (portgroup.mode is not wtypes.Unset
                     or portgroup.properties is not wtypes.Unset)):
            raise exception.NotAcceptable()

        if (portgroup.name
                and not api_utils.is_valid_logical_name(portgroup.name)):
            error_msg = _("Cannot create portgroup with invalid name "
                          "'%(name)s'") % {
                              'name': portgroup.name
                          }
            raise exception.ClientSideError(
                error_msg, status_code=http_client.BAD_REQUEST)

        pg_dict = portgroup.as_dict()

        api_utils.handle_post_port_like_extra_vif(pg_dict)

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not pg_dict.get('uuid'):
            pg_dict['uuid'] = uuidutils.generate_uuid()

        new_portgroup = objects.Portgroup(context, **pg_dict)

        notify.emit_start_notification(context,
                                       new_portgroup,
                                       'create',
                                       node_uuid=portgroup.node_uuid)
        with notify.handle_error_notification(context,
                                              new_portgroup,
                                              'create',
                                              node_uuid=portgroup.node_uuid):
            new_portgroup.create()
        notify.emit_end_notification(context,
                                     new_portgroup,
                                     'create',
                                     node_uuid=portgroup.node_uuid)

        # Set the HTTP Location Header
        api.response.location = link.build_url('portgroups',
                                               new_portgroup.uuid)
        return Portgroup.convert_with_links(new_portgroup)
Ejemplo n.º 6
0
    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 wsme.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 wsme.Unset:
            ret['value'] = patch.value
        return ret
Ejemplo n.º 7
0
    def post(self, portgroup):
        """Create a new portgroup.

        :param portgroup: a portgroup within the request body.
        """
        if not api_utils.allow_portgroups():
            raise exception.NotFound()

        context = api.request.context
        api_utils.check_policy('baremetal:portgroup:create')

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        if (not api_utils.allow_portgroup_mode_properties()
                and (portgroup.get('mode') or portgroup.get('properties'))):
            raise exception.NotAcceptable()

        if (portgroup.get('name')
                and not api_utils.is_valid_logical_name(portgroup['name'])):
            error_msg = _("Cannot create portgroup with invalid name "
                          "'%(name)s'") % {
                              'name': portgroup['name']
                          }
            raise exception.ClientSideError(
                error_msg, status_code=http_client.BAD_REQUEST)

        api_utils.handle_post_port_like_extra_vif(portgroup)

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not portgroup.get('uuid'):
            portgroup['uuid'] = uuidutils.generate_uuid()

        node = api_utils.replace_node_uuid_with_id(portgroup)

        new_portgroup = objects.Portgroup(context, **portgroup)

        notify.emit_start_notification(context,
                                       new_portgroup,
                                       'create',
                                       node_uuid=node.uuid)
        with notify.handle_error_notification(context,
                                              new_portgroup,
                                              'create',
                                              node_uuid=node.uuid):
            new_portgroup.create()
        notify.emit_end_notification(context,
                                     new_portgroup,
                                     'create',
                                     node_uuid=node.uuid)

        # Set the HTTP Location Header
        api.response.location = link.build_url('portgroups',
                                               new_portgroup.uuid)
        return convert_with_links(new_portgroup)
Ejemplo n.º 8
0
def parse(s, datatypes, bodyarg, encoding='utf8'):
    jload = json.load
    if not hasattr(s, 'read'):
        if isinstance(s, bytes):
            s = s.decode(encoding)
        jload = json.loads
    try:
        jdata = jload(s)
    except ValueError:
        raise exception.ClientSideError("Request is not in valid JSON format")
    if bodyarg:
        argname = list(datatypes.keys())[0]
        try:
            kw = {argname: fromjson(datatypes[argname], jdata)}
        except ValueError as e:
            raise exception.InvalidInput(argname, jdata, e.args[0])
        except exception.UnknownAttribute as e:
            # We only know the fieldname at this level, not in the
            # called function. We fill in this information here.
            e.add_fieldname(argname)
            raise
    else:
        kw = {}
        extra_args = []
        if not isinstance(jdata, dict):
            raise exception.ClientSideError("Request must be a JSON dict")
        for key in jdata:
            if key not in datatypes:
                extra_args.append(key)
            else:
                try:
                    kw[key] = fromjson(datatypes[key], jdata[key])
                except ValueError as e:
                    raise exception.InvalidInput(key, jdata[key], e.args[0])
                except exception.UnknownAttribute as e:
                    # We only know the fieldname at this level, not in the
                    # called function. We fill in this information here.
                    e.add_fieldname(key)
                    raise
        if extra_args:
            raise exception.UnknownArgument(', '.join(extra_args))
    return kw
Ejemplo n.º 9
0
def check_allow_configdrive(target, configdrive=None):
    if not configdrive:
        return

    allowed_targets = [states.ACTIVE]
    if allow_node_rebuild_with_configdrive():
        allowed_targets.append(states.REBUILD)

    if target not in allowed_targets:
        msg = (_('Adding a config drive is only supported when setting '
                 'provision state to %s') % ', '.join(allowed_targets))
        raise exception.ClientSideError(msg,
                                        status_code=http_client.BAD_REQUEST)

    try:
        jsonschema.validate(configdrive, _CONFIG_DRIVE_SCHEMA)
    except json_schema_exc.ValidationError as e:
        msg = _('Invalid configdrive format: %s') % e
        raise exception.ClientSideError(msg,
                                        status_code=http_client.BAD_REQUEST)

    if isinstance(configdrive, dict):
        if not allow_build_configdrive():
            msg = _('Providing a JSON object for configdrive is only supported'
                    ' starting with API version %(base)s.%(opr)s') % {
                        'base': versions.BASE_VERSION,
                        'opr': versions.MINOR_56_BUILD_CONFIGDRIVE
                    }
            raise exception.ClientSideError(
                msg, status_code=http_client.BAD_REQUEST)
        if ('vendor_data' in configdrive
                and not allow_configdrive_vendor_data()):
            msg = _('Providing vendor_data in configdrive is only supported'
                    ' starting with API version %(base)s.%(opr)s') % {
                        'base': versions.BASE_VERSION,
                        'opr': versions.MINOR_59_CONFIGDRIVE_VENDOR_DATA
                    }
            raise exception.ClientSideError(
                msg, status_code=http_client.BAD_REQUEST)
Ejemplo n.º 10
0
def vendor_passthru(ident, method, topic, data=None, driver_passthru=False):
    """Call a vendor passthru API extension.

    Call the vendor passthru API extension and process the method response
    to set the right return code for methods that are asynchronous or
    synchronous; Attach the return value to the response object if it's
    being served statically.

    :param ident: The resource identification. For node's vendor passthru
        this is the node's UUID, for driver's vendor passthru this is the
        driver's name.
    :param method: The vendor method name.
    :param topic: The RPC topic.
    :param data: The data passed to the vendor method. Defaults to None.
    :param driver_passthru: Boolean value. Whether this is a node or
        driver vendor passthru. Defaults to False.
    :returns: A WSME response object to be returned by the API.

    """
    if not method:
        raise exception.ClientSideError(_("Method not specified"))

    if data is None:
        data = {}

    http_method = api.request.method.upper()
    params = (api.request.context, ident, method, http_method, data, topic)
    if driver_passthru:
        response = api.request.rpcapi.driver_vendor_passthru(*params)
    else:
        response = api.request.rpcapi.vendor_passthru(*params)

    status_code = http_client.ACCEPTED if response['async'] else http_client.OK
    return_value = response['return']
    response_params = {'status_code': status_code}

    # Attach the return value to the response object
    if response.get('attach'):
        if isinstance(return_value, str):
            # If unicode, convert to bytes
            return_value = return_value.encode('utf-8')
        file_ = atypes.File(content=return_value)
        api.response.app_iter = static.FileIter(file_.file)
        # Since we've attached the return value to the response
        # object the response body should now be empty.
        return_value = None
        response_params['return_type'] = None

    return atypes.Response(return_value, **response_params)
Ejemplo n.º 11
0
def validate_trait(trait, error_prefix=_('Invalid trait')):
    error = exception.ClientSideError(
        _('%(error_prefix)s. A valid trait must be no longer than 255 '
          'characters. Standard traits are defined in the os_traits library. '
          'A custom trait must start with the prefix CUSTOM_ and use '
          'the following characters: A-Z, 0-9 and _') %
        {'error_prefix': error_prefix})
    if not isinstance(trait, str):
        raise error

    if len(trait) > 255 or len(trait) < 1:
        raise error

    if trait in STANDARD_TRAITS:
        return

    if CUSTOM_TRAIT_REGEX.match(trait) is None:
        raise error
Ejemplo n.º 12
0
def check_allow_filter_by_fault(fault):
    """Check if filtering nodes by fault is allowed.

    Version 1.42 of the API allows filtering nodes by fault.
    """
    if (fault is not None
            and api.request.version.minor < versions.MINOR_42_FAULT):
        raise exception.NotAcceptable(
            _("Request not acceptable. The minimal required API version "
              "should be %(base)s.%(opr)s") % {
                  'base': versions.BASE_VERSION,
                  'opr': versions.MINOR_42_FAULT
              })

    if fault is not None and fault not in faults.VALID_FAULTS:
        msg = (_('Unrecognized fault "%(fault)s" is specified, allowed faults '
                 'are %(valid_faults)s') % {
                     'fault': fault,
                     'valid_faults': faults.VALID_FAULTS
                 })
        raise exception.ClientSideError(msg,
                                        status_code=http_client.BAD_REQUEST)
Ejemplo n.º 13
0
def args_from_body(funcdef, body, mimetype):
    if funcdef.body_type is not None:
        datatypes = {funcdef.arguments[-1].name: funcdef.body_type}
    else:
        datatypes = dict(((a.name, a.datatype) for a in funcdef.arguments))

    if not body:
        return (), {}

    if mimetype == "application/x-www-form-urlencoded":
        # the parameters should have been parsed in params
        return (), {}
    elif mimetype not in ACCEPT_CONTENT_TYPES:
        raise exception.ClientSideError("Unknown mimetype: %s" % mimetype,
                                        status_code=415)

    try:
        kw = parse(body, datatypes, bodyarg=funcdef.body_type is not None)
    except exception.UnknownArgument:
        if not funcdef.ignore_extra_args:
            raise
        kw = {}

    return (), kw
Ejemplo n.º 14
0
    def patch(self, port_uuid, patch):
        """Update an existing port.

        :param port_uuid: UUID of a port.
        :param patch: a json PATCH document to apply to this port.
        :raises: NotAcceptable, HTTPNotFound
        """
        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        rpc_port, rpc_node = api_utils.check_port_policy_and_retrieve(
            'baremetal:port:update', port_uuid)

        context = api.request.context
        fields_to_check = set()
        for field in (self.advanced_net_fields + [
                'portgroup_uuid', 'physical_network', 'is_smartnic',
                'local_link_connection/network_type'
        ]):
            field_path = '/%s' % field
            if (api_utils.get_patch_values(patch, field_path)
                    or api_utils.is_path_removed(patch, field_path)):
                fields_to_check.add(field)
        self._check_allowed_port_fields(fields_to_check)

        port_dict = rpc_port.as_dict()
        # NOTE(lucasagomes):
        # 1) Remove node_id because it's an internal value and
        #    not present in the API object
        # 2) Add node_uuid
        port_dict['node_uuid'] = port_dict.pop('node_id', None)
        # NOTE(vsaienko):
        # 1) Remove portgroup_id because it's an internal value and
        #    not present in the API object
        # 2) Add portgroup_uuid
        port_dict['portgroup_uuid'] = port_dict.pop('portgroup_id', None)
        port = Port(**api_utils.apply_jsonpatch(port_dict, patch))

        api_utils.handle_patch_port_like_extra_vif(rpc_port, port, patch)

        if api_utils.is_path_removed(patch, '/portgroup_uuid'):
            rpc_port.portgroup_id = None

        # Update only the fields that have changed
        for field in objects.Port.fields:
            try:
                patch_val = getattr(port, field)
            except AttributeError:
                # Ignore fields that aren't exposed in the API
                continue
            if patch_val == wtypes.Unset:
                patch_val = None
            if rpc_port[field] != patch_val:
                rpc_port[field] = patch_val

        if (rpc_node.provision_state == ir_states.INSPECTING
                and api_utils.allow_inspect_wait_state()):
            msg = _('Cannot update port "%(port)s" on "%(node)s" while it is '
                    'in state "%(state)s".') % {
                        'port': rpc_port.uuid,
                        'node': rpc_node.uuid,
                        'state': ir_states.INSPECTING
                    }
            raise exception.ClientSideError(msg,
                                            status_code=http_client.CONFLICT)

        notify_extra = {
            'node_uuid': rpc_node.uuid,
            'portgroup_uuid': port.portgroup_uuid
        }
        notify.emit_start_notification(context, rpc_port, 'update',
                                       **notify_extra)
        with notify.handle_error_notification(context, rpc_port, 'update',
                                              **notify_extra):
            topic = api.request.rpcapi.get_topic_for(rpc_node)
            new_port = api.request.rpcapi.update_port(context, rpc_port, topic)

        api_port = Port.convert_with_links(new_port)
        notify.emit_end_notification(context, new_port, 'update',
                                     **notify_extra)

        return api_port
Ejemplo n.º 15
0
    def patch(self, portgroup_ident, patch):
        """Update an existing portgroup.

        :param portgroup_ident: UUID or logical name of a portgroup.
        :param patch: a json PATCH document to apply to this portgroup.
        """
        if not api_utils.allow_portgroups():
            raise exception.NotFound()

        context = api.request.context

        rpc_portgroup, rpc_node = api_utils.check_port_policy_and_retrieve(
            'baremetal:portgroup:update', portgroup_ident, portgroup=True)

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        if (not api_utils.allow_portgroup_mode_properties()
                and (api_utils.is_path_updated(patch, '/mode')
                     or api_utils.is_path_updated(patch, '/properties'))):
            raise exception.NotAcceptable()

        api_utils.patch_validate_allowed_fields(patch, PATCH_ALLOWED_FIELDS)

        names = api_utils.get_patch_values(patch, '/name')
        for name in names:
            if (name and not api_utils.is_valid_logical_name(name)):
                error_msg = _("Portgroup %(portgroup)s: Cannot change name to"
                              " invalid name '%(name)s'") % {
                                  'portgroup': portgroup_ident,
                                  'name': name
                              }
                raise exception.ClientSideError(
                    error_msg, status_code=http_client.BAD_REQUEST)

        portgroup_dict = rpc_portgroup.as_dict()

        # NOTE:
        # 1) Remove node_id because it's an internal value and
        #    not present in the API object
        # 2) Add node_uuid
        portgroup_dict.pop('node_id')
        portgroup_dict['node_uuid'] = rpc_node.uuid
        portgroup_dict = api_utils.apply_jsonpatch(portgroup_dict, patch)

        if 'mode' not in portgroup_dict:
            msg = _("'mode' is a mandatory attribute and can not be removed")
            raise exception.ClientSideError(msg)

        try:
            if portgroup_dict['node_uuid'] != rpc_node.uuid:
                rpc_node = objects.Node.get(api.request.context,
                                            portgroup_dict['node_uuid'])

        except exception.NodeNotFound as e:
            # Change error code because 404 (NotFound) is inappropriate
            # response for a POST request to patch a Portgroup
            e.code = http_client.BAD_REQUEST  # BadRequest
            raise

        api_utils.patched_validate_with_schema(portgroup_dict,
                                               PORTGROUP_PATCH_SCHEMA,
                                               PORTGROUP_PATCH_VALIDATOR)

        api_utils.patch_update_changed_fields(portgroup_dict,
                                              rpc_portgroup,
                                              fields=objects.Portgroup.fields,
                                              schema=PORTGROUP_PATCH_SCHEMA,
                                              id_map={'node_id': rpc_node.id})

        if (rpc_node.provision_state == ir_states.INSPECTING
                and api_utils.allow_inspect_wait_state()):
            msg = _('Cannot update portgroup "%(portgroup)s" on node '
                    '"%(node)s" while it is in state "%(state)s".') % {
                        'portgroup': rpc_portgroup.uuid,
                        'node': rpc_node.uuid,
                        'state': ir_states.INSPECTING
                    }
            raise exception.ClientSideError(msg,
                                            status_code=http_client.CONFLICT)

        notify.emit_start_notification(context,
                                       rpc_portgroup,
                                       'update',
                                       node_uuid=rpc_node.uuid)
        with notify.handle_error_notification(context,
                                              rpc_portgroup,
                                              'update',
                                              node_uuid=rpc_node.uuid):
            topic = api.request.rpcapi.get_topic_for(rpc_node)
            new_portgroup = api.request.rpcapi.update_portgroup(
                context, rpc_portgroup, topic)

        api_portgroup = convert_with_links(new_portgroup)
        notify.emit_end_notification(context,
                                     new_portgroup,
                                     'update',
                                     node_uuid=rpc_node.uuid)

        return api_portgroup
Ejemplo n.º 16
0
    def post(self, portgroup):
        """Create a new portgroup.

        :param portgroup: a portgroup within the request body.
        """
        if not api_utils.allow_portgroups():
            raise exception.NotFound()

        raise_node_not_found = False
        node = None
        owner = None
        lessee = None
        node_uuid = portgroup.get('node_uuid')
        try:
            # The replace_node_uuid_with_id also checks access to the node
            # and will raise an exception if access is not permitted.
            node = api_utils.replace_node_uuid_with_id(portgroup)
            owner = node.owner
            lessee = node.lessee
        except exception.NotFound:
            raise_node_not_found = True

        # While the rule is for the port, the base object that controls access
        # is the node.
        api_utils.check_owner_policy('node',
                                     'baremetal:portgroup:create',
                                     owner,
                                     lessee=lessee,
                                     conceal_node=False)
        if raise_node_not_found:
            # Delayed raise of NodeNotFound because we want to check
            # the access policy first.
            raise exception.NodeNotFound(node=node_uuid,
                                         code=http_client.BAD_REQUEST)
        context = api.request.context

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        if (not api_utils.allow_portgroup_mode_properties()
                and (portgroup.get('mode') or portgroup.get('properties'))):
            raise exception.NotAcceptable()

        if (portgroup.get('name')
                and not api_utils.is_valid_logical_name(portgroup['name'])):
            error_msg = _("Cannot create portgroup with invalid name "
                          "'%(name)s'") % {
                              'name': portgroup['name']
                          }
            raise exception.ClientSideError(
                error_msg, status_code=http_client.BAD_REQUEST)

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not portgroup.get('uuid'):
            portgroup['uuid'] = uuidutils.generate_uuid()

        new_portgroup = objects.Portgroup(context, **portgroup)

        notify.emit_start_notification(context,
                                       new_portgroup,
                                       'create',
                                       node_uuid=node.uuid)
        with notify.handle_error_notification(context,
                                              new_portgroup,
                                              'create',
                                              node_uuid=node.uuid):
            new_portgroup.create()
        notify.emit_end_notification(context,
                                     new_portgroup,
                                     'create',
                                     node_uuid=node.uuid)

        # Set the HTTP Location Header
        api.response.location = link.build_url('portgroups',
                                               new_portgroup.uuid)
        return convert_with_links(new_portgroup)
Ejemplo n.º 17
0
    def patch(self, port_uuid, patch):
        """Update an existing port.

        :param port_uuid: UUID of a port.
        :param patch: a json PATCH document to apply to this port.
        :raises: NotAcceptable, HTTPNotFound
        """
        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        api_utils.patch_validate_allowed_fields(patch, PATCH_ALLOWED_FIELDS)

        context = api.request.context
        fields_to_check = set()
        for field in (self.advanced_net_fields
                      + ['portgroup_uuid', 'physical_network',
                         'is_smartnic', 'local_link_connection/network_type']):
            field_path = '/%s' % field
            if (api_utils.get_patch_values(patch, field_path)
                    or api_utils.is_path_removed(patch, field_path)):
                fields_to_check.add(field)
        self._check_allowed_port_fields(fields_to_check)

        rpc_port, rpc_node = api_utils.check_port_policy_and_retrieve(
            'baremetal:port:update', port_uuid)

        port_dict = rpc_port.as_dict()
        # NOTE(lucasagomes):
        # 1) Remove node_id because it's an internal value and
        #    not present in the API object
        # 2) Add node_uuid
        port_dict.pop('node_id', None)
        port_dict['node_uuid'] = rpc_node.uuid
        # NOTE(vsaienko):
        # 1) Remove portgroup_id because it's an internal value and
        #    not present in the API object
        # 2) Add portgroup_uuid
        portgroup = None
        if port_dict.get('portgroup_id'):
            portgroup = objects.Portgroup.get_by_id(
                context, port_dict.pop('portgroup_id'))
        port_dict['portgroup_uuid'] = portgroup and portgroup.uuid or None

        port_dict = api_utils.apply_jsonpatch(port_dict, patch)

        api_utils.handle_patch_port_like_extra_vif(
            rpc_port, port_dict['internal_info'], patch)

        try:
            if api_utils.is_path_updated(patch, '/portgroup_uuid'):
                if port_dict.get('portgroup_uuid'):
                    portgroup = objects.Portgroup.get_by_uuid(
                        context, port_dict['portgroup_uuid'])
                else:
                    portgroup = None
        except exception.PortGroupNotFound as e:
            # Change error code because 404 (NotFound) is inappropriate
            # response for a PATCH request to change a Port
            e.code = http_client.BAD_REQUEST  # BadRequest
            raise

        try:
            if port_dict['node_uuid'] != rpc_node.uuid:
                rpc_node = objects.Node.get(
                    api.request.context, port_dict['node_uuid'])
        except exception.NodeNotFound as e:
            # Change error code because 404 (NotFound) is inappropriate
            # response for a PATCH request to change a Port
            e.code = http_client.BAD_REQUEST  # BadRequest
            raise

        api_utils.patched_validate_with_schema(
            port_dict, PORT_PATCH_SCHEMA, PORT_PATCH_VALIDATOR)

        api_utils.patch_update_changed_fields(
            port_dict, rpc_port, fields=objects.Port.fields,
            schema=PORT_PATCH_SCHEMA,
            id_map={
                'node_id': rpc_node.id,
                'portgroup_id': portgroup and portgroup.id or None
            }
        )

        if (rpc_node.provision_state == ir_states.INSPECTING
                and api_utils.allow_inspect_wait_state()):
            msg = _('Cannot update port "%(port)s" on "%(node)s" while it is '
                    'in state "%(state)s".') % {'port': rpc_port.uuid,
                                                'node': rpc_node.uuid,
                                                'state': ir_states.INSPECTING}
            raise exception.ClientSideError(msg,
                                            status_code=http_client.CONFLICT)

        if (api_utils.is_path_updated(patch, '/physical_network')
            and rpc_port['physical_network'] is not None
                and not rpc_port['physical_network']):
            raise exception.Invalid('A non-empty value is required when '
                                    'setting physical_network')

        notify_extra = {'node_uuid': rpc_node.uuid,
                        'portgroup_uuid': portgroup and portgroup.uuid or None}
        notify.emit_start_notification(context, rpc_port, 'update',
                                       **notify_extra)
        with notify.handle_error_notification(context, rpc_port, 'update',
                                              **notify_extra):
            topic = api.request.rpcapi.get_topic_for(rpc_node)
            new_port = api.request.rpcapi.update_port(context, rpc_port,
                                                      topic)

        api_port = convert_with_links(new_port)
        notify.emit_end_notification(context, new_port, 'update',
                                     **notify_extra)

        return api_port
Ejemplo n.º 18
0
    def patch(self, portgroup_ident, patch):
        """Update an existing portgroup.

        :param portgroup_ident: UUID or logical name of a portgroup.
        :param patch: a json PATCH document to apply to this portgroup.
        """
        if not api_utils.allow_portgroups():
            raise exception.NotFound()

        context = api.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:portgroup:update', cdict, cdict)

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        if (not api_utils.allow_portgroup_mode_properties()
                and (api_utils.is_path_updated(patch, '/mode')
                     or api_utils.is_path_updated(patch, '/properties'))):
            raise exception.NotAcceptable()

        rpc_portgroup = api_utils.get_rpc_portgroup_with_suffix(
            portgroup_ident)

        names = api_utils.get_patch_values(patch, '/name')
        for name in names:
            if (name and not api_utils.is_valid_logical_name(name)):
                error_msg = _("Portgroup %(portgroup)s: Cannot change name to"
                              " invalid name '%(name)s'") % {
                                  'portgroup': portgroup_ident,
                                  'name': name
                              }
                raise exception.ClientSideError(
                    error_msg, status_code=http_client.BAD_REQUEST)

        portgroup_dict = rpc_portgroup.as_dict()
        # NOTE:
        # 1) Remove node_id because it's an internal value and
        #    not present in the API object
        # 2) Add node_uuid
        portgroup_dict['node_uuid'] = portgroup_dict.pop('node_id', None)
        portgroup = Portgroup(
            **api_utils.apply_jsonpatch(portgroup_dict, patch))

        api_utils.handle_patch_port_like_extra_vif(rpc_portgroup, portgroup,
                                                   patch)

        # Update only the fields that have changed
        for field in objects.Portgroup.fields:
            try:
                patch_val = getattr(portgroup, field)
            except AttributeError:
                # Ignore fields that aren't exposed in the API
                continue
            if patch_val == wtypes.Unset:
                patch_val = None
            if rpc_portgroup[field] != patch_val:
                rpc_portgroup[field] = patch_val

        rpc_node = objects.Node.get_by_id(context, rpc_portgroup.node_id)
        if (rpc_node.provision_state == ir_states.INSPECTING
                and api_utils.allow_inspect_wait_state()):
            msg = _('Cannot update portgroup "%(portgroup)s" on node '
                    '"%(node)s" while it is in state "%(state)s".') % {
                        'portgroup': rpc_portgroup.uuid,
                        'node': rpc_node.uuid,
                        'state': ir_states.INSPECTING
                    }
            raise exception.ClientSideError(msg,
                                            status_code=http_client.CONFLICT)

        notify.emit_start_notification(context,
                                       rpc_portgroup,
                                       'update',
                                       node_uuid=rpc_node.uuid)
        with notify.handle_error_notification(context,
                                              rpc_portgroup,
                                              'update',
                                              node_uuid=rpc_node.uuid):
            topic = api.request.rpcapi.get_topic_for(rpc_node)
            new_portgroup = api.request.rpcapi.update_portgroup(
                context, rpc_portgroup, topic)

        api_portgroup = Portgroup.convert_with_links(new_portgroup)
        notify.emit_end_notification(context,
                                     new_portgroup,
                                     'update',
                                     node_uuid=api_portgroup.node_uuid)

        return api_portgroup