Esempio n. 1
0
    def do_node_deploy(self, context, node_id, rebuild=False):
        """RPC method to initiate deployment to a node.

        Initiate the deployment of a node. Validations are done
        synchronously and the actual deploy work is performed in
        background (asynchronously).

        :param context: an admin context.
        :param node_id: the id or uuid of a node.
        :param rebuild: True if this is a rebuild request. A rebuild will
                        recreate the instance on the same node, overwriting
                        all disk. The ephemeral partition, if it exists, can
                        optionally be preserved.
        :raises: InstanceDeployFailure
        :raises: NodeInMaintenance if the node is in maintenance mode.
        :raises: NoFreeConductorWorker when there is no free worker to start
                 async task.

        """
        LOG.debug("RPC do_node_deploy called for node %s." % node_id)

        # NOTE(comstud): If the _sync_power_states() periodic task happens
        # to have locked this node, we'll fail to acquire the lock. The
        # client should perhaps retry in this case unless we decide we
        # want to add retries or extra synchronization here.
        with task_manager.acquire(context, node_id, shared=False) as task:
            node = task.node
            # May only rebuild a node in ACTIVE state
            if rebuild and (node.provision_state != states.ACTIVE):
                raise exception.InstanceDeployFailure(_(
                    "RPC do_node_deploy called to rebuild %(node)s, but "
                    "provision state is %(curstate)s. Must be %(state)s.") %
                    {'node': node.uuid, 'curstate': node.provision_state,
                     'state': states.ACTIVE})
            elif node.provision_state != states.NOSTATE and not rebuild:
                raise exception.InstanceDeployFailure(_(
                    "RPC do_node_deploy called for %(node)s, but provision "
                    "state is already %(state)s.") %
                    {'node': node.uuid, 'state': node.provision_state})

            if node.maintenance:
                raise exception.NodeInMaintenance(op=_('provisioning'),
                                                  node=node.uuid)

            try:
                task.driver.deploy.validate(task)
            except exception.InvalidParameterValue as e:
                raise exception.InstanceDeployFailure(_(
                    "RPC do_node_deploy failed to validate deploy info. "
                    "Error: %(msg)s") % {'msg': e})

            # Set target state to expose that work is in progress
            node.provision_state = states.DEPLOYING
            node.target_provision_state = states.DEPLOYDONE
            node.last_error = None
            node.save(context)
            task.spawn_after(self._spawn_worker, self._do_node_deploy,
                             context, task)
Esempio n. 2
0
    def do_node_deploy(self, context, node_id):
        """RPC method to initiate deployment to a node.

        Initiate the deployment of a node. Validations are done
        synchronously and the actual deploy work is performed in
        background (asynchronously).

        :param context: an admin context.
        :param node_id: the id or uuid of a node.
        :raises: InstanceDeployFailure
        :raises: NodeInMaintenance if the node is in maintenance mode.
        :raises: NoFreeConductorWorker when there is no free worker to start
                 async task.

        """
        LOG.debug(_("RPC do_node_deploy called for node %s.") % node_id)

        task = task_manager.TaskManager(context, node_id, shared=False)
        node = task.node
        try:
            if node.provision_state is not states.NOSTATE:
                raise exception.InstanceDeployFailure(
                    _("RPC do_node_deploy called for %(node)s, but provision "
                      "state is already %(state)s.") % {
                          'node': node.uuid,
                          'state': node.provision_state
                      })

            if node.maintenance:
                raise exception.NodeInMaintenance(op=_('provisioning'),
                                                  node=node.uuid)

            try:
                task.driver.deploy.validate(task, node)
            except exception.InvalidParameterValue as e:
                raise exception.InstanceDeployFailure(
                    _("RPC do_node_deploy failed to validate deploy info. "
                      "Error: %(msg)s") % {'msg': e})

            # Set target state to expose that work is in progress
            node.provision_state = states.DEPLOYING
            node.target_provision_state = states.DEPLOYDONE
            node.last_error = None
            node.save(context)

            # Start requested action in the background.
            thread = self._spawn_worker(self._do_node_deploy, context, task)
            # Release node lock at the end.
            thread.link(lambda t: task.release_resources())
        except Exception:
            with excutils.save_and_reraise_exception():
                # Release node lock if error occurred.
                task.release_resources()
Esempio n. 3
0
    def test_provision_node_in_maintenance_fail(self):
        with mock.patch.object(rpcapi.ConductorAPI, 'do_node_deploy') as dnd:
            ndict = dbutils.get_test_node(id=1, uuid=utils.generate_uuid(),
                                          maintenance=True)
            node = self.dbapi.create_node(ndict)
            dnd.side_effect = exception.NodeInMaintenance(op='provisioning',
                                                          node=node.uuid)

            ret = self.put_json('/nodes/%s/states/provision' % node.uuid,
                                {'target': states.ACTIVE},
                                expect_errors=True)
            self.assertEqual(400, ret.status_code)
            self.assertTrue(ret.json['error_message'])
Esempio 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 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)
Esempio n. 5
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)
Esempio n. 6
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)