Exemplo 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.
        """
        rpc_chassis = objects.Chassis.get_by_uuid(pecan.request.context,
                                                  chassis_uuid)
        try:
            chassis = Chassis(
                **api_utils.apply_jsonpatch(rpc_chassis.as_dict(), patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        # Update only the fields that have changed
        for field in objects.Chassis.fields:
            try:
                patch_val = getattr(chassis, field)
            except AttributeError:
                # Ignore fields that aren't exposed in the API
                continue
            if rpc_chassis[field] != patch_val:
                rpc_chassis[field] = patch_val

        rpc_chassis.save()
        return Chassis.convert_with_links(rpc_chassis)
Exemplo n.º 2
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
Exemplo n.º 3
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()

        cdict = pecan.request.context.to_dict()
        policy.authorize('baremetal:portgroup:update', cdict, cdict)

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        rpc_portgroup = api_utils.get_rpc_portgroup(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 wsme.exc.ClientSideError(
                    error_msg, status_code=http_client.BAD_REQUEST)

        try:
            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))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        # 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(pecan.request.context,
                                          rpc_portgroup.node_id)
        topic = pecan.request.rpcapi.get_topic_for(rpc_node)

        new_portgroup = pecan.request.rpcapi.update_portgroup(
            pecan.request.context, rpc_portgroup, topic)

        return Portgroup.convert_with_links(new_portgroup)
Exemplo n.º 4
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 = pecan.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:chassis:update', cdict, cdict)

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

        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

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

        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 Chassis.convert_with_links(rpc_chassis)
Exemplo n.º 5
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.
        """
        if self._from_nodes:
            raise exception.OperationNotPermitted

        rpc_port = objects.Port.get_by_uuid(pecan.request.context, port_uuid)
        try:
            port = Port(**jsonpatch.apply_patch(rpc_port.as_dict(),
                                                jsonpatch.JsonPatch(patch)))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        # Update only the fields that have changed
        for field in objects.Port.fields:
            if rpc_port[field] != getattr(port, field):
                rpc_port[field] = getattr(port, field)

        rpc_node = objects.Node.get_by_uuid(pecan.request.context,
                                            rpc_port.node_id)
        topic = pecan.request.rpcapi.get_topic_for(rpc_node)

        new_port = pecan.request.rpcapi.update_port(pecan.request.context,
                                                    rpc_port, topic)

        return Port.convert_with_links(new_port)
Exemplo 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.
        """
        if self.from_nodes:
            raise exception.OperationNotPermitted

        rpc_port = objects.Port.get_by_uuid(pecan.request.context, port_uuid)
        try:
            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)
            port = Port(**api_utils.apply_jsonpatch(port_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        # Update only the fields that have changed
        for field in objects.Port.fields:
            if rpc_port[field] != getattr(port, field):
                rpc_port[field] = getattr(port, field)

        rpc_node = objects.Node.get_by_id(pecan.request.context,
                                          rpc_port.node_id)
        topic = pecan.request.rpcapi.get_topic_for(rpc_node)

        new_port = pecan.request.rpcapi.update_port(pecan.request.context,
                                                    rpc_port, topic)

        return Port.convert_with_links(new_port)
Exemplo n.º 7
0
    def patch(self, node_uuid, patch):
        """Update an existing node.

        :param node_uuid: UUID of a node.
        :param patch: a json PATCH document to apply to this node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)

        # Check if node is transitioning state, although nodes in DEPLOYFAIL
        # can be updated.
        if ((rpc_node.target_power_state or rpc_node.target_provision_state)
                and rpc_node.provision_state != ir_states.DEPLOYFAIL):
            msg = _("Node %s can not be updated while a state transition "
                    "is in progress.")
            raise wsme.exc.ClientSideError(msg % node_uuid, status_code=409)

        try:
            node_dict = rpc_node.as_dict()
            # NOTE(lucasagomes):
            # 1) Remove chassis_id because it's an internal value and
            #    not present in the API object
            # 2) Add chassis_uuid
            node_dict['chassis_uuid'] = node_dict.pop('chassis_id', None)
            node = Node(**api_utils.apply_jsonpatch(node_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

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

        # NOTE(deva): we calculate the rpc topic here in case node.driver
        #             has changed, so that update is sent to the
        #             new conductor, not the old one which may fail to
        #             load the new driver.
        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = 400
            raise e

        new_node = pecan.request.rpcapi.update_node(pecan.request.context,
                                                    rpc_node, topic)

        return Node.convert_with_links(new_node)
Exemplo n.º 8
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
        """
        cdict = pecan.request.context.to_dict()
        policy.authorize('baremetal:port:update', cdict, cdict)

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()
        if not api_utils.allow_port_advanced_net_fields():
            for field in self.advanced_net_fields:
                field_path = '/%s' % field
                if (api_utils.get_patch_values(patch, field_path)
                        or api_utils.is_path_removed(patch, field_path)):
                    raise exception.NotAcceptable()

        rpc_port = objects.Port.get_by_uuid(pecan.request.context, port_uuid)
        try:
            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)
            port = Port(**api_utils.apply_jsonpatch(port_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        # 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

        rpc_node = objects.Node.get_by_id(pecan.request.context,
                                          rpc_port.node_id)
        topic = pecan.request.rpcapi.get_topic_for(rpc_node)

        new_port = pecan.request.rpcapi.update_port(pecan.request.context,
                                                    rpc_port, topic)

        return Port.convert_with_links(new_port)
Exemplo n.º 9
0
    def patch(self, node_uuid, patch):
        """Update an existing node.

        :param node_uuid: UUID of a node.
        :param patch: a json PATCH document to apply to this node.
        """
        if self._from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)

        # Check if node is transitioning state
        if rpc_node['target_power_state'] or \
             rpc_node['target_provision_state']:
            msg = _("Node %s can not be updated while a state transition "
                    "is in progress.")
            raise wsme.exc.ClientSideError(msg % node_uuid, status_code=409)

        try:
            node = Node(**jsonpatch.apply_patch(rpc_node.as_dict(),
                                                jsonpatch.JsonPatch(patch)))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        # Update only the fields that have changed
        for field in objects.Node.fields:
            if rpc_node[field] != getattr(node, field):
                rpc_node[field] = getattr(node, field)

        # NOTE(deva): we calculate the rpc topic here in case node.driver
        #             has changed, so that update is sent to the
        #             new conductor, not the old one which may fail to
        #             load the new driver.
        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = 400
            raise e

        try:
            new_node = pecan.request.rpcapi.update_node(
                pecan.request.context, rpc_node, topic)
        except Exception as e:
            with excutils.save_and_reraise_exception():
                LOG.exception(e)

        return Node.convert_with_links(new_node)
    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')

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

        try:
            template_dict = rpc_template.as_dict()
            template = DeployTemplate(
                **api_utils.apply_jsonpatch(template_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)
        template.validate(template)
        self._update_changed_fields(template, rpc_template)

        # 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 = DeployTemplate.convert_with_links(rpc_template)
        notify.emit_end_notification(context, rpc_template, 'update')

        return api_template
Exemplo n.º 11
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
        """
        context = pecan.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:port:update', cdict, cdict)

        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        fields_to_check = set()
        for field in (self.advanced_net_fields +
                      ['portgroup_uuid', 'physical_network']):
            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 = objects.Port.get_by_uuid(context, port_uuid)
        try:
            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))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        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

        rpc_node = objects.Node.get_by_id(context, rpc_port.node_id)
        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 = pecan.request.rpcapi.get_topic_for(rpc_node)
            new_port = pecan.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
Exemplo n.º 12
0
    def patch(self, node_ident, patch):
        """Update an existing node.

        :param node_ident: UUID or logical name of a node.
        :param patch: a json PATCH document to apply to this node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = api_utils.get_rpc_node(node_ident)

        # Check if node is transitioning state, although nodes in some states
        # can be updated.
        if ((rpc_node.maintenance or
                rpc_node.provision_state == ir_states.CLEANING) and
                patch == [{'op': 'remove', 'path': '/instance_uuid'}]):
            # Allow node.instance_uuid removal during cleaning, but not other
            # operations. Also allow it during maintenance, to break
            # association with Nova in case of serious problems.
            # TODO(JoshNang) remove node.instance_uuid when removing
            # instance_info and stop removing node.instance_uuid in the Nova
            # Ironic driver. Bug: 1436568
            LOG.debug('Removing instance uuid %(instance)s from node %(node)s',
                      {'instance': rpc_node.instance_uuid,
                       'node': rpc_node.uuid})
        elif ((rpc_node.target_power_state or rpc_node.target_provision_state)
                and rpc_node.provision_state not in
                ir_states.UPDATE_ALLOWED_STATES):
            msg = _("Node %s can not be updated while a state transition "
                    "is in progress.")
            raise wsme.exc.ClientSideError(msg % node_ident, status_code=409)

        # Verify that if we're patching 'name' that it is a valid
        name = api_utils.get_patch_value(patch, '/name')
        if name:
            if not api_utils.allow_node_logical_names():
                raise exception.NotAcceptable()
            if not api_utils.is_valid_node_name(name):
                msg = _("Node %(node)s: Cannot change name to invalid "
                        "name '%(name)s'")
                raise wsme.exc.ClientSideError(msg % {'node': node_ident,
                                                      'name': name},
                                               status_code=400)

        try:
            node_dict = rpc_node.as_dict()
            # NOTE(lucasagomes):
            # 1) Remove chassis_id because it's an internal value and
            #    not present in the API object
            # 2) Add chassis_uuid
            node_dict['chassis_uuid'] = node_dict.pop('chassis_id', None)
            node = Node(**api_utils.apply_jsonpatch(node_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

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

        # NOTE(deva): we calculate the rpc topic here in case node.driver
        #             has changed, so that update is sent to the
        #             new conductor, not the old one which may fail to
        #             load the new driver.
        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = 400
            raise e

        # NOTE(lucasagomes): If it's changing the driver and the console
        # is enabled we prevent updating it because the new driver will
        # not be able to stop a console started by the previous one.
        delta = rpc_node.obj_what_changed()
        if 'driver' in delta and rpc_node.console_enabled:
            raise wsme.exc.ClientSideError(
                _("Node %s can not update the driver while the console is "
                  "enabled. Please stop the console first.") % node_ident,
                status_code=409)

        new_node = pecan.request.rpcapi.update_node(
            pecan.request.context, rpc_node, topic)

        return Node.convert_with_links(new_node)
Exemplo n.º 13
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 = pecan.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:volume:update', cdict, cdict)

        if self.parent_node_ident:
            raise exception.OperationNotPermitted()

        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': six.text_type(value)
                            }
                raise exception.InvalidUUID(message=message)

        rpc_connector = objects.VolumeConnector.get_by_uuid(
            context, connector_uuid)
        try:
            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
            connector_dict['node_uuid'] = connector_dict.pop('node_id', None)
            connector = VolumeConnector(
                **api_utils.apply_jsonpatch(connector_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

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

        rpc_node = objects.Node.get_by_id(context, rpc_connector.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 = pecan.request.rpcapi.get_topic_for(rpc_node)
            new_connector = pecan.request.rpcapi.update_volume_connector(
                context, rpc_connector, topic)

        api_connector = VolumeConnector.convert_with_links(new_connector)
        notify.emit_end_notification(context,
                                     new_connector,
                                     'update',
                                     node_uuid=rpc_node.uuid)
        return api_connector
Exemplo n.º 14
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 = pecan.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 wsme.exc.ClientSideError(
                    error_msg, status_code=http_client.BAD_REQUEST)

        try:
            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))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        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 wsme.exc.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 = pecan.request.rpcapi.get_topic_for(rpc_node)
            new_portgroup = pecan.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
Exemplo n.º 15
0
    def patch(self, node_ident, patch):
        """Update an existing node.

        :param node_ident: UUID or logical name of a node.
        :param patch: a json PATCH document to apply to this node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = api_utils.get_rpc_node(node_ident)

        # TODO(lucasagomes): This code is here for backward compatibility
        # with old nova Ironic drivers that will attempt to remove the
        # instance even if it's already deleted in Ironic. This conditional
        # should be removed in the next cycle (Mitaka).
        remove_inst_uuid_patch = [{'op': 'remove', 'path': '/instance_uuid'}]
        if (rpc_node.provision_state in (ir_states.CLEANING,
                                         ir_states.CLEANWAIT)
            and patch == remove_inst_uuid_patch):
            # The instance_uuid is already removed as part of the node's
            # tear down, skip this update.
            return Node.convert_with_links(rpc_node)
        elif rpc_node.maintenance and patch == remove_inst_uuid_patch:
            LOG.debug('Removing instance uuid %(instance)s from node %(node)s',
                      {'instance': rpc_node.instance_uuid,
                       'node': rpc_node.uuid})
        # Check if node is transitioning state, although nodes in some states
        # can be updated.
        elif (rpc_node.target_provision_state and rpc_node.provision_state
              not in ir_states.UPDATE_ALLOWED_STATES):
            msg = _("Node %s can not be updated while a state transition "
                    "is in progress.")
            raise wsme.exc.ClientSideError(
                msg % node_ident, status_code=http_client.CONFLICT)

        name = api_utils.get_patch_value(patch, '/name')
        error_msg = _("Node %(node)s: Cannot change name to invalid "
                      "name '%(name)s'") % {'node': node_ident, 'name': name}
        self._check_name_acceptable(name, error_msg)
        try:
            node_dict = rpc_node.as_dict()
            # NOTE(lucasagomes):
            # 1) Remove chassis_id because it's an internal value and
            #    not present in the API object
            # 2) Add chassis_uuid
            node_dict['chassis_uuid'] = node_dict.pop('chassis_id', None)
            node = Node(**api_utils.apply_jsonpatch(node_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)
        self._update_changed_fields(node, rpc_node)
        # NOTE(deva): we calculate the rpc topic here in case node.driver
        #             has changed, so that update is sent to the
        #             new conductor, not the old one which may fail to
        #             load the new driver.
        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = http_client.BAD_REQUEST
            raise e
        self._check_driver_changed_and_console_enabled(rpc_node, node_ident)
        new_node = pecan.request.rpcapi.update_node(
            pecan.request.context, rpc_node, topic)

        return Node.convert_with_links(new_node)