def power(self, node_ident, target): """Set the power state of the node. :param node_ident: the UUID or logical name of a node. :param target: The desired power state of the node. :raises: ClientSideError (HTTP 409) if a power operation is already in progress. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid or if the node is in CLEANING state. """ # TODO(lucasagomes): Test if it's able to transition to the # target state from the current one rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if target not in [ir_states.POWER_ON, ir_states.POWER_OFF, ir_states.REBOOT]: raise exception.InvalidStateRequested( action=target, node=node_ident, state=rpc_node.power_state) # Don't change power state for nodes in cleaning elif rpc_node.provision_state == ir_states.CLEANING: raise exception.InvalidStateRequested( action=target, node=node_ident, state=rpc_node.provision_state) pecan.request.rpcapi.change_node_power_state(pecan.request.context, rpc_node.uuid, target, topic) # Set the HTTP Location Header url_args = '/'.join([node_ident, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def power(self, node_uuid, target): """Set the power state of the node. :param node_uuid: UUID of a node. :param target: The desired power state of the node. :raises: ClientSideError (HTTP 409) if a power operation is already in progress. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid. """ # TODO(lucasagomes): Test if it's able to transition to the # target state from the current one rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if target not in [ ir_states.POWER_ON, ir_states.POWER_OFF, ir_states.REBOOT ]: raise exception.InvalidStateRequested(state=target, node=node_uuid) pecan.request.rpcapi.change_node_power_state(pecan.request.context, node_uuid, target, topic) # Set the HTTP Location Header url_args = '/'.join([node_uuid, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def start_deploy(task, manager, configdrive=None, event='deploy'): """Start deployment or rebuilding on a node. This function does not check the node suitability for deployment, it's left up to the caller. :param task: a TaskManager instance. :param manager: a ConductorManager to run tasks on. :param configdrive: a configdrive, if requested. :param event: event to process: deploy or rebuild. """ node = task.node if event == 'rebuild': # Note(gilliard) Clear these to force the driver to # check whether they have been changed in glance # NOTE(vdrok): If image_source is not from Glance we should # not clear kernel and ramdisk as they're input manually if glance_utils.is_glance_image( node.instance_info.get('image_source')): instance_info = node.instance_info instance_info.pop('kernel', None) instance_info.pop('ramdisk', None) node.instance_info = instance_info # Infer the image type to make sure the deploy driver # validates only the necessary variables for different # image types. # NOTE(sirushtim): The iwdi variable can be None. It's up to # the deploy driver to validate this. iwdi = images.is_whole_disk_image(task.context, node.instance_info) driver_internal_info = node.driver_internal_info driver_internal_info['is_whole_disk_image'] = iwdi node.driver_internal_info = driver_internal_info node.save() try: task.driver.power.validate(task) task.driver.deploy.validate(task) utils.validate_instance_info_traits(task.node) conductor_steps.validate_deploy_templates(task, skip_missing=True) except exception.InvalidParameterValue as e: raise exception.InstanceDeployFailure( _("Failed to validate deploy or power info for node " "%(node_uuid)s. Error: %(msg)s") % { 'node_uuid': node.uuid, 'msg': e }, code=e.code) try: task.process_event(event, callback=manager._spawn_worker, call_args=(do_node_deploy, task, manager.conductor.id, configdrive), err_handler=utils.provisioning_error_handler) except exception.InvalidState: raise exception.InvalidStateRequested(action=event, node=task.node.uuid, state=task.node.provision_state)
def provision(self, node_uuid, target): """Asynchronous trigger the provisioning of the node. This will set the target provision state of the node, and a background task will begin which actually applies the state change. This call will return a 202 (Accepted) indicating the request was accepted and is in progress; the client should continue to GET the status of this node to observe the status of the requested action. :param node_uuid: UUID of a node. :param target: The desired provision state of the node. :raises: ClientSideError (HTTP 409) if the node is already being provisioned. :raises: ClientSideError (HTTP 400) if the node is already in the requested state. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid. """ rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if target == rpc_node.provision_state: msg = (_("Node %(node)s is already in the '%(state)s' state.") % { 'node': rpc_node['uuid'], 'state': target }) raise wsme.exc.ClientSideError(msg, status_code=400) if target not in (ir_states.ACTIVE, ir_states.DELETED, ir_states.REBUILD): raise exception.InvalidStateRequested(state=target, node=node_uuid) valid_states_if_processing = [ir_states.DEPLOYFAIL] if target == ir_states.DELETED: valid_states_if_processing.append(ir_states.DEPLOYWAIT) if (rpc_node.target_provision_state is not None and rpc_node.provision_state not in valid_states_if_processing): msg = ( _('Node %s is already being provisioned or decommissioned.') % rpc_node.uuid) raise wsme.exc.ClientSideError(msg, status_code=409) # Conflict # Note that there is a race condition. The node state(s) could change # by the time the RPC call is made and the TaskManager manager gets a # lock. if target in (ir_states.ACTIVE, ir_states.REBUILD): rebuild = (target == ir_states.REBUILD) pecan.request.rpcapi.do_node_deploy(pecan.request.context, node_uuid, rebuild, topic) elif target == ir_states.DELETED: pecan.request.rpcapi.do_node_tear_down(pecan.request.context, node_uuid, topic) # Set the HTTP Location Header url_args = '/'.join([node_uuid, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def test_delete_volume_target_invalid_power_state(self, mock_dvc): mock_dvc.side_effect = exception.InvalidStateRequested( action='volume target deletion', node=self.node.uuid, state='power on') ret = self.delete('/volume/targets/%s' % self.target.uuid, headers=self.headers, expect_errors=True) self.assertEqual(http_client.BAD_REQUEST, ret.status_code) self.assertTrue(ret.json['error_message']) self.assertTrue(mock_dvc.called)
def test_delete_volume_connector_invalid_power_state(self, mock_dvc): self.node.reserve(self.context, 'fake', self.node.uuid) mock_dvc.side_effect = exception.InvalidStateRequested( action='volume connector deletion', node=self.node.uuid, state='power on') ret = self.delete('/volume/connectors/%s' % self.connector.uuid, expect_errors=True, headers=self.headers) self.assertEqual(http_client.BAD_REQUEST, ret.status_code) self.assertTrue(ret.json['error_message']) self.assertTrue(mock_dvc.called)
def provision(self, node_uuid, target): """Asynchronous trigger the provisioning of the node. This will set the target provision state of the node, and a background task will begin which actually applies the state change. This call will return a 202 (Accepted) indicating the request was accepted and is in progress; the client should continue to GET the status of this node to observe the status of the requested action. :param node_uuid: UUID of a node. :param target: The desired provision state of the node. :raises: ClientSideError (HTTP 409) if the node is already being provisioned. :raises: ClientSideError (HTTP 400) if the node is already in the requested state. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid. """ rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if rpc_node.maintenance: op = _('provisioning') raise exception.NodeInMaintenance(op=op, node=node_uuid) if rpc_node.target_provision_state is not None: msg = _('Node %s is already being provisioned.') % rpc_node['uuid'] LOG.exception(msg) raise wsme.exc.ClientSideError(msg, status_code=409) # Conflict if target == rpc_node.provision_state: msg = (_("Node %(node)s is already in the '%(state)s' state.") % { 'node': rpc_node['uuid'], 'state': target }) LOG.exception(msg) raise wsme.exc.ClientSideError(msg, status_code=400) # Note that there is a race condition. The node state(s) could change # by the time the RPC call is made and the TaskManager manager gets a # lock. if target == ir_states.ACTIVE: pecan.request.rpcapi.do_node_deploy(pecan.request.context, node_uuid, topic) elif target == ir_states.DELETED: pecan.request.rpcapi.do_node_tear_down(pecan.request.context, node_uuid, topic) else: raise exception.InvalidStateRequested(state=target, node=node_uuid)
def _validate_boot_into_iso(self, task, kwargs): """Validates if attach_iso can be called and if inputs are proper.""" if not (task.node.provision_state == states.MANAGEABLE or task.node.maintenance is True): msg = (_("The requested action 'boot_into_iso' can be performed " "only when node %(node_uuid)s is in %(state)s state or " "in 'maintenance' mode") % {'node_uuid': task.node.uuid, 'state': states.MANAGEABLE}) raise exception.InvalidStateRequested(msg) d_info = {'boot_iso_href': kwargs.get('boot_iso_href')} error_msg = _("Error validating input for boot_into_iso vendor " "passthru. Some parameters were not provided: ") deploy_utils.check_for_missing_params(d_info, error_msg) deploy_utils.validate_image_properties( task.context, {'image_source': kwargs.get('boot_iso_href')}, [])
def validate_node(task, event='deploy'): """Validate that a node is suitable for deployment/rebuilding. :param task: a TaskManager instance. :param event: event to process: deploy or rebuild. :raises: NodeInMaintenance, NodeProtected, InvalidStateRequested """ if task.node.maintenance: raise exception.NodeInMaintenance(op=_('provisioning'), node=task.node.uuid) if event == 'rebuild' and task.node.protected: raise exception.NodeProtected(node=task.node.uuid) if not task.fsm.is_actionable_event(event): raise exception.InvalidStateRequested( action=event, node=task.node.uuid, state=task.node.provision_state)
def test_replace_invalid_power_state(self, mock_upd): mock_upd.side_effect = \ exception.InvalidStateRequested( action='volume target update', node=self.node.uuid, state='power on') response = self.patch_json('/volume/targets/%s' % self.target.uuid, [{'path': '/boot_index', 'value': 0, 'op': 'replace'}], expect_errors=True, headers=self.headers) self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.BAD_REQUEST, response.status_code) self.assertTrue(response.json['error_message']) self.assertTrue(mock_upd.called) kargs = mock_upd.call_args[0][1] self.assertEqual(0, kargs.boot_index)
def test_replace_invalid_power_state(self, mock_upd): connector_id = 'test-connector-id-123' mock_upd.side_effect = \ exception.InvalidStateRequested( action='volume connector update', node=self.node.uuid, state='power on') response = self.patch_json('/volume/connectors/%s' % self.connector.uuid, [{ 'path': '/connector_id', 'value': connector_id, 'op': 'replace' }], expect_errors=True, headers=self.headers) self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.BAD_REQUEST, response.status_code) self.assertTrue(response.json['error_message']) self.assertTrue(mock_upd.called) kargs = mock_upd.call_args[0][2] self.assertEqual(connector_id, kargs.connector_id)
def provision(self, node_ident, target, configdrive=None): """Asynchronous trigger the provisioning of the node. This will set the target provision state of the node, and a background task will begin which actually applies the state change. This call will return a 202 (Accepted) indicating the request was accepted and is in progress; the client should continue to GET the status of this node to observe the status of the requested action. :param node_ident: UUID or logical name of a node. :param target: The desired provision state of the node. :param configdrive: Optional. A gzipped and base64 encoded configdrive. Only valid when setting provision state to "active". :raises: NodeLocked (HTTP 409) if the node is currently locked. :raises: ClientSideError (HTTP 409) if the node is already being provisioned. :raises: InvalidStateRequested (HTTP 400) if the requested transition is not possible from the current state. :raises: NotAcceptable (HTTP 406) if the API version specified does not allow the requested state transition. """ check_allow_management_verbs(target) rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) # Normally, we let the task manager recognize and deal with # NodeLocked exceptions. However, that isn't done until the RPC calls # below. In order to main backward compatibility with our API HTTP # response codes, we have this check here to deal with cases where # a node is already being operated on (DEPLOYING or such) and we # want to continue returning 409. Without it, we'd return 400. if rpc_node.reservation: raise exception.NodeLocked(node=rpc_node.uuid, host=rpc_node.reservation) if (target in (ir_states.ACTIVE, ir_states.REBUILD) and rpc_node.maintenance): raise exception.NodeInMaintenance(op=_('provisioning'), node=rpc_node.uuid) m = ir_states.machine.copy() m.initialize(rpc_node.provision_state) if not m.is_valid_event(ir_states.VERBS.get(target, target)): raise exception.InvalidStateRequested( action=target, node=rpc_node.uuid, state=rpc_node.provision_state) if configdrive and target != ir_states.ACTIVE: msg = (_('Adding a config drive is only supported when setting ' 'provision state to %s') % ir_states.ACTIVE) raise wsme.exc.ClientSideError(msg, status_code=400) # Note that there is a race condition. The node state(s) could change # by the time the RPC call is made and the TaskManager manager gets a # lock. if target == ir_states.ACTIVE: pecan.request.rpcapi.do_node_deploy(pecan.request.context, rpc_node.uuid, False, configdrive, topic) elif target == ir_states.REBUILD: pecan.request.rpcapi.do_node_deploy(pecan.request.context, rpc_node.uuid, True, None, topic) elif target == ir_states.DELETED: pecan.request.rpcapi.do_node_tear_down( pecan.request.context, rpc_node.uuid, topic) elif target == ir_states.VERBS['inspect']: pecan.request.rpcapi.inspect_hardware( pecan.request.context, rpc_node.uuid, topic=topic) elif target in ( ir_states.VERBS['manage'], ir_states.VERBS['provide']): pecan.request.rpcapi.do_provisioning_action( pecan.request.context, rpc_node.uuid, target, topic) else: msg = (_('The requested action "%(action)s" could not be ' 'understood.') % {'action': target}) raise exception.InvalidStateRequested(message=msg) # Set the HTTP Location Header url_args = '/'.join([node_ident, 'states']) pecan.response.location = link.build_url('nodes', url_args)