Esempio n. 1
0
    def patch(self, chassis_uuid, patch):
        """Update an existing chassis.

        :param chassis_uuid: UUID of a chassis.
        :param patch: a json PATCH document to apply to this chassis.
        """
        context = api.request.context
        api_utils.check_policy('baremetal:chassis:update')

        api_utils.patch_validate_allowed_fields(patch,
                                                CHASSIS_SCHEMA['properties'])

        rpc_chassis = objects.Chassis.get_by_uuid(context, chassis_uuid)
        chassis = api_utils.apply_jsonpatch(rpc_chassis.as_dict(), patch)

        api_utils.patched_validate_with_schema(chassis, CHASSIS_SCHEMA,
                                               CHASSIS_VALIDATOR)

        api_utils.patch_update_changed_fields(chassis,
                                              rpc_chassis,
                                              fields=objects.Chassis.fields,
                                              schema=CHASSIS_SCHEMA)

        notify.emit_start_notification(context, rpc_chassis, 'update')
        with notify.handle_error_notification(context, rpc_chassis, 'update'):
            rpc_chassis.save()
        notify.emit_end_notification(context, rpc_chassis, 'update')
        return convert_with_links(rpc_chassis)
Esempio n. 2
0
    def patch(self, template_ident, patch=None):
        """Update an existing deploy template.

        :param template_ident: UUID or logical name of a deploy template.
        :param patch: a json PATCH document to apply to this deploy template.
        """
        api_utils.check_policy('baremetal:deploy_template:update')

        api_utils.patch_validate_allowed_fields(patch, PATCH_ALLOWED_FIELDS)

        context = api.request.context
        rpc_template = api_utils.get_rpc_deploy_template_with_suffix(
            template_ident)

        template = rpc_template.as_dict()

        # apply the patch
        template = api_utils.apply_jsonpatch(template, patch)

        # validate the result with the patch schema
        for step in template.get('steps', []):
            api_utils.patched_validate_with_schema(
                step, api_utils.DEPLOY_STEP_SCHEMA)
        api_utils.patched_validate_with_schema(template, TEMPLATE_SCHEMA,
                                               TEMPLATE_VALIDATOR)

        api_utils.patch_update_changed_fields(
            template,
            rpc_template,
            fields=objects.DeployTemplate.fields,
            schema=TEMPLATE_SCHEMA)

        # NOTE(mgoddard): There could be issues with concurrent updates of a
        # template. This is particularly true for the complex 'steps' field,
        # where operations such as modifying a single step could result in
        # changes being lost, e.g. two requests concurrently appending a step
        # to the same template could result in only one of the steps being
        # added, due to the read/modify/write nature of this patch operation.
        # This issue should not be present for 'simple' string fields, or
        # complete replacement of the steps (the only operation supported by
        # the openstack baremetal CLI). It's likely that this is an issue for
        # other resources, even those modified in the conductor under a lock.
        # This is due to the fact that the patch operation is always applied in
        # the API. Ways to avoid this include passing the patch to the
        # conductor to apply while holding a lock, or a collision detection
        # & retry mechansim using e.g. the updated_at field.
        notify.emit_start_notification(context, rpc_template, 'update')
        with notify.handle_error_notification(context, rpc_template, 'update'):
            rpc_template.save()

        api_template = convert_with_links(rpc_template)
        notify.emit_end_notification(context, rpc_template, 'update')

        return api_template
Esempio n. 3
0
    def patch(self, allocation_ident, patch):
        """Update an existing allocation.

        :param allocation_ident: UUID or logical name of an allocation.
        :param patch: a json PATCH document to apply to this allocation.
        """
        if not api_utils.allow_allocation_update():
            raise webob_exc.HTTPMethodNotAllowed(
                _("The API version does not allow updating allocations"))

        context = api.request.context
        rpc_allocation = api_utils.check_allocation_policy_and_retrieve(
            'baremetal:allocation:update', allocation_ident)
        self._validate_patch(patch)
        names = api_utils.get_patch_values(patch, '/name')
        for name in names:
            if name and not api_utils.is_valid_logical_name(name):
                msg = _("Cannot update allocation with invalid name "
                        "'%(name)s'") % {
                            'name': name
                        }
                raise exception.Invalid(msg)
        allocation_dict = rpc_allocation.as_dict()
        allocation_dict = api_utils.apply_jsonpatch(rpc_allocation.as_dict(),
                                                    patch)
        api_utils.patched_validate_with_schema(allocation_dict,
                                               ALLOCATION_SCHEMA,
                                               ALLOCATION_VALIDATOR)

        api_utils.patch_update_changed_fields(allocation_dict,
                                              rpc_allocation,
                                              fields=objects.Allocation.fields,
                                              schema=ALLOCATION_SCHEMA)

        notify.emit_start_notification(context, rpc_allocation, 'update')
        with notify.handle_error_notification(context, rpc_allocation,
                                              'update'):
            rpc_allocation.save()
        notify.emit_end_notification(context, rpc_allocation, 'update')
        return convert_with_links(rpc_allocation)
Esempio n. 4
0
    def patch(self, target_uuid, patch):
        """Update an existing volume target.

        :param target_uuid: UUID of a volume target.
        :param patch: a json PATCH document to apply to this volume target.

        :returns: API-serializable volume target object.

        :raises: OperationNotPermitted if accessed with specifying a
                 parent node.
        :raises: PatchError if a given patch can not be applied.
        :raises: InvalidParameterValue if the volume target's UUID is being
                 changed
        :raises: NodeLocked if the node is already locked
        :raises: NodeNotFound if the node associated with the volume target
                 does not exist
        :raises: VolumeTargetNotFound if the volume target cannot be found
        :raises: VolumeTargetBootIndexAlreadyExists if a volume target already
                 exists with the same node ID and boot index values
        :raises: InvalidUUID if invalid node UUID is passed in the patch.
        :raises: InvalidStateRequested If a node associated with the
                 volume target is not powered off.
        """
        context = api.request.context
        api_utils.check_policy('baremetal:volume:update')

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        api_utils.patch_validate_allowed_fields(patch, PATCH_ALLOWED_FIELDS)

        values = api_utils.get_patch_values(patch, '/node_uuid')
        for value in values:
            if not uuidutils.is_uuid_like(value):
                message = _("Expected a UUID for node_uuid, but received "
                            "%(uuid)s.") % {
                                'uuid': str(value)
                            }
                raise exception.InvalidUUID(message=message)

        rpc_target = objects.VolumeTarget.get_by_uuid(context, target_uuid)
        target_dict = rpc_target.as_dict()
        # NOTE(smoriya):
        # 1) Remove node_id because it's an internal value and
        #    not present in the API object
        # 2) Add node_uuid
        rpc_node = api_utils.replace_node_id_with_uuid(target_dict)

        target_dict = api_utils.apply_jsonpatch(target_dict, patch)

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

        api_utils.patched_validate_with_schema(target_dict, TARGET_SCHEMA,
                                               TARGET_VALIDATOR)

        api_utils.patch_update_changed_fields(
            target_dict,
            rpc_target,
            fields=objects.VolumeTarget.fields,
            schema=TARGET_SCHEMA,
            id_map={'node_id': rpc_node.id})

        notify.emit_start_notification(context,
                                       rpc_target,
                                       'update',
                                       node_uuid=rpc_node.uuid)
        with notify.handle_error_notification(context,
                                              rpc_target,
                                              'update',
                                              node_uuid=rpc_node.uuid):
            topic = api.request.rpcapi.get_topic_for(rpc_node)
            new_target = api.request.rpcapi.update_volume_target(
                context, rpc_target, topic)

        api_target = convert_with_links(new_target)
        notify.emit_end_notification(context,
                                     new_target,
                                     'update',
                                     node_uuid=rpc_node.uuid)
        return api_target
Esempio n. 5
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
Esempio n. 6
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
Esempio n. 7
0
    def patch(self, connector_uuid, patch):
        """Update an existing volume connector.

        :param connector_uuid: UUID of a volume connector.
        :param patch: a json PATCH document to apply to this volume connector.

        :returns: API-serializable volume connector object.

        :raises: OperationNotPermitted if accessed with specifying a
                 parent node.
        :raises: PatchError if a given patch can not be applied.
        :raises: VolumeConnectorNotFound if no volume connector exists with
                 the specified UUID.
        :raises: InvalidParameterValue if the volume connector's UUID is being
                 changed
        :raises: NodeLocked if node is locked by another conductor
        :raises: NodeNotFound if the node associated with the connector does
                 not exist
        :raises: VolumeConnectorTypeAndIdAlreadyExists if another connector
                 already exists with the same values for type and connector_id
                 fields
        :raises: InvalidUUID if invalid node UUID is passed in the patch.
        :raises: InvalidStateRequested If a node associated with the
                 volume connector is not powered off.
        """
        context = api.request.context

        rpc_connector, rpc_node = api_utils.check_volume_policy_and_retrieve(
            'baremetal:volume:update',
            connector_uuid,
            target=False)

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        api_utils.patch_validate_allowed_fields(patch, PATCH_ALLOWED_FIELDS)

        for value in api_utils.get_patch_values(patch, '/node_uuid'):
            if not uuidutils.is_uuid_like(value):
                message = _("Expected a UUID for node_uuid, but received "
                            "%(uuid)s.") % {'uuid': str(value)}
                raise exception.InvalidUUID(message=message)

        connector_dict = rpc_connector.as_dict()
        # NOTE(smoriya):
        # 1) Remove node_id because it's an internal value and
        #    not present in the API object
        # 2) Add node_uuid
        rpc_node = api_utils.replace_node_id_with_uuid(connector_dict)

        connector_dict = api_utils.apply_jsonpatch(connector_dict, patch)

        try:
            if connector_dict['node_uuid'] != rpc_node.uuid:
                rpc_node = objects.Node.get(
                    api.request.context, connector_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(
            connector_dict, CONNECTOR_SCHEMA, CONNECTOR_VALIDATOR)

        api_utils.patch_update_changed_fields(
            connector_dict, rpc_connector,
            fields=objects.VolumeConnector.fields,
            schema=CONNECTOR_SCHEMA, id_map={'node_id': rpc_node.id}
        )

        notify.emit_start_notification(context, rpc_connector, 'update',
                                       node_uuid=rpc_node.uuid)
        with notify.handle_error_notification(context, rpc_connector, 'update',
                                              node_uuid=rpc_node.uuid):
            topic = api.request.rpcapi.get_topic_for(rpc_node)
            new_connector = api.request.rpcapi.update_volume_connector(
                context, rpc_connector, topic)

        api_connector = convert_with_links(new_connector)
        notify.emit_end_notification(context, new_connector, 'update',
                                     node_uuid=rpc_node.uuid)
        return api_connector