def test_get_patch_value_remove(self): patch = [{'path': '/name', 'op': 'remove'}] path = '/name' value = utils.get_patch_value(patch, path) self.assertIsNone(value)
def test_get_patch_value_success(self): patch = [{'path': '/name', 'op': 'replace', 'value': 'node-x'}] path = '/name' value = utils.get_patch_value(patch, path) self.assertEqual('node-x', value)
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 test_get_patch_value_no_path(self): patch = [{'path': '/name', 'op': 'update', 'value': 'node-0'}] path = '/invalid' value = utils.get_patch_value(patch, path) self.assertIsNone(value)
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=409) 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 = 400 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)
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)
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 = _get_rpc_node(node_ident) # 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_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 allow_logical_names(): raise exception.NotAcceptable() if not is_valid_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 new_node = pecan.request.rpcapi.update_node(pecan.request.context, rpc_node, topic) return Node.convert_with_links(new_node)
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 = _get_rpc_node(node_ident) # 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_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 allow_logical_names(): raise exception.NotAcceptable() if not is_valid_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 new_node = pecan.request.rpcapi.update_node( pecan.request.context, rpc_node, topic) return Node.convert_with_links(new_node)