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)
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
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)
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)
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)
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)
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)
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)
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
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
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)
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
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
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)