Exemple #1
0
    def test_handles_PowerActionAlreadyInProgress(self):
        request = factory.make_fake_request(factory.make_string(), "POST")
        error_message = ("Unable to execute power action: another action is "
                         "already in progress for node %s" %
                         factory.make_name("node"))
        error = PowerActionAlreadyInProgress(error_message)
        response = self.process_request(request, error)

        # The response is a redirect.
        self.assertEqual(request.path, extract_redirect(response))
Exemple #2
0
    def test__raises_power_problem(self):
        node = factory.make_Node()
        client = Mock()
        client.return_value = fail(
            PowerActionAlreadyInProgress('Houston, we have a problem.'))

        with ExpectedException(PowerProblem, "Houston, we have a problem."):
            wait_for_reactor(self.power_func)(client, node.system_id,
                                              node.hostname,
                                              node.get_effective_power_info())
Exemple #3
0
 def test_POST_power_off_returns_503_when_power_already_in_progress(self):
     machine = factory.make_Node(owner=self.user)
     exc_text = factory.make_name("exc_text")
     self.patch(node_module.Machine,
                "stop").side_effect = PowerActionAlreadyInProgress(exc_text)
     response = self.client.post(self.get_node_uri(machine),
                                 {"op": "power_off"})
     self.assertThat(response,
                     HasStatusCode(http.client.SERVICE_UNAVAILABLE))
     self.assertIn(exc_text,
                   response.content.decode(settings.DEFAULT_CHARSET))
Exemple #4
0
    def test_power_action_already_in_progress_returned_as_503(self):
        request = factory.make_fake_request(
            "/MAAS/api/2.0/" + factory.make_string(), 'POST')
        error_message = (
            "Unable to execute power action: another action is already in "
            "progress for node %s" % factory.make_name('node'))
        error = PowerActionAlreadyInProgress(error_message)
        response = self.process_request(request, error)

        self.assertEqual((http.client.SERVICE_UNAVAILABLE, error_message),
                         (response.status_code,
                          response.content.decode(settings.DEFAULT_CHARSET)))
Exemple #5
0
    def test_handles_PowerActionAlreadyInProgress(self):
        request = factory.make_fake_request(factory.make_string(), 'POST')
        error_message = ("Unable to execute power action: another action is "
                         "already in progress for node %s" %
                         factory.make_name('node'))
        error = PowerActionAlreadyInProgress(error_message)
        response = self.process_request(request, error)

        # The response is a redirect.
        self.assertEqual(request.path, extract_redirect(response))
        # An error message has been published.
        self.assertEqual([(constants.ERROR, "Error: %s" % error_message, '')],
                         request._messages.messages)
Exemple #6
0
def maybe_change_power_state(system_id,
                             hostname,
                             power_type,
                             power_change,
                             context,
                             clock=reactor):
    """Attempt to change the power state of a node.

    If there is no power action already in progress, register this
    action and then pass change_power_state() to the reactor to call
    later and then return.

    This function exists to guarantee that PowerActionAlreadyInProgress
    errors will be raised promptly, before any work is done to power the
    node on.

    :raises: PowerActionAlreadyInProgress if there's already a power
        action in progress for this node.
    """
    assert power_change in ("on", "off",
                            "cycle"), ("Unknown power change: %s" %
                                       power_change)

    power_driver = PowerDriverRegistry.get_item(power_type)
    if power_driver is None:
        raise PowerActionFail("Unknown power_type '%s'" % power_type)
    missing_packages = power_driver.detect_missing_packages()
    if len(missing_packages):
        raise PowerActionFail("'%s' package(s) are not installed" %
                              " ".join(missing_packages))

    # There should be one and only one power change for each system ID.
    if system_id in power_action_registry:
        current_power_change, d = power_action_registry[system_id]
    else:
        current_power_change, d = None, None

    if current_power_change is None:
        # Arrange for the power change to happen later; do not make the caller
        # wait, because it might take a long time. We set a timeout so that if
        # the power action doesn't return in a timely fashion (or fails
        # silently or some such) it doesn't block other actions on the node.
        d = deferLater(
            clock,
            0,
            deferWithTimeout,
            CHANGE_POWER_STATE_TIMEOUT,
            change_power_state,
            system_id,
            hostname,
            power_type,
            power_change,
            context,
            clock,
        )

        power_action_registry[system_id] = power_change, d

        # Whether we succeed or fail, we need to remove the action from the
        # registry of actions, otherwise subsequent actions will fail.
        d.addBoth(callOut, power_action_registry.pop, system_id, None)

        # Log cancellations distinctly from other errors.
        def eb_cancelled(failure):
            failure.trap(CancelledError)
            log.msg("%s: Power could not be set to %s; timed out." %
                    (hostname, power_change))
            return power_change_failure(system_id, hostname, power_change,
                                        "Timed out")

        d.addErrback(eb_cancelled)

        # Catch-all log.
        d.addErrback(log.err,
                     "%s: Power %s failed." % (hostname, power_change))

        # LP: 1761600 - Returning d will cause d to be resolved with the
        # caller. This causes power actions in the UI/API such as deploy,
        # commission, or release to wait for the power action to complete
        # before finishing.

    elif current_power_change == power_change:
        # What we want is already happening; let it continue.
        pass

    else:
        # Right now we reject conflicting power changes. However, we have the
        # Deferred (in `d`) along which the current power change is occurring,
        # so the option to cancel is available if we want it.
        raise PowerActionAlreadyInProgress(
            "Unable to change power state to '%s' for node %s: another "
            "action is already in progress for that node." %
            (power_change, hostname))