Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
 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)
Exemplo n.º 6
0
 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)
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
 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')}, [])
Exemplo n.º 9
0
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)
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
    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)
Exemplo n.º 12
0
    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)