Example #1
0
def change_power_state(system_id,
                       hostname,
                       power_type,
                       power_change,
                       context,
                       clock=reactor):
    """Change the power state of a node.

    This monitors the result of the power change by querying the power state
    of the node, thus attempting to ensure that the requested change has taken
    place.

    Success is reported using `power_change_success`. Power-related failures
    are reported using `power_change_failure`. Other failures must be reported
    by the caller.
    """
    yield power_change_starting(system_id, hostname, power_change)
    yield perform_power_driver_change(system_id, hostname, power_type,
                                      power_change, context)
    if power_type not in PowerDriverRegistry:
        returnValue(None)
    power_driver = PowerDriverRegistry.get_item(power_type)
    if not power_driver.queryable:
        returnValue(None)
    new_power_state = yield perform_power_driver_query(system_id, hostname,
                                                       power_type, context)
    if new_power_state == "unknown" or new_power_state == power_change:
        yield power_change_success(system_id, hostname, power_change)
    elif new_power_state == "on" and power_change == "cycle":
        yield power_change_success(system_id, hostname, new_power_state)
    returnValue(new_power_state)
Example #2
0
def perform_power_driver_change(system_id, hostname, power_type, power_change,
                                context):
    """Execute power driver `power_change` method.

    On failure the node will be marked as broken and the error will be
    re-raised to the caller.
    """
    power_driver = PowerDriverRegistry.get_item(power_type)

    if power_change == "on":
        d = power_driver.on(system_id, context)
    elif power_change == "off":
        d = power_driver.off(system_id, context)
    elif power_change == "cycle":
        d = power_driver.cycle(system_id, context)

    def power_change_failed(failure):
        message = "Power %s for the node failed: %s" % (
            power_change,
            get_error_message(failure.value),
        )
        df = power_change_failure(system_id, hostname, power_change, message)
        df.addCallback(lambda _: failure)  # Propagate the original error.
        return df

    return d.addErrback(power_change_failed)
Example #3
0
def get_all_power_types(controllers=None, ignore_errors=True):
    """Query the PowerDriverRegistry and obtain all known power driver types.

    :return: a list of power types matching the schema
        provisioningserver.drivers.power.JSON_POWER_DRIVERS_SCHEMA or
        provisioningserver.drivers.pod.JSON_POD_DRIVERS_SCHEMA
    """
    merged_types = []
    for power_type_orig in PowerDriverRegistry.get_schema(
            detect_missing_packages=False):
        power_type = deepcopy(power_type_orig)
        driver_type = power_type.get("driver_type", "power")
        name = power_type["name"]
        fields = power_type.get("fields", [])
        description = power_type["description"]
        chassis = power_type["chassis"]
        missing_packages = power_type["missing_packages"]
        queryable = power_type.get("queryable")
        add_power_driver_parameters(
            driver_type,
            name,
            description,
            chassis,
            fields,
            missing_packages,
            merged_types,
            queryable=queryable,
        )
    return sorted(merged_types, key=itemgetter("description"))
Example #4
0
 def test_get_schema(self):
     fake_driver_one = make_power_driver_base()
     fake_driver_two = make_power_driver_base()
     fake_pod_driver = make_pod_driver_base()
     PowerDriverRegistry.register_item(fake_driver_one.name,
                                       fake_driver_one)
     PowerDriverRegistry.register_item(fake_driver_two.name,
                                       fake_driver_two)
     PowerDriverRegistry.register_item(fake_pod_driver.name,
                                       fake_pod_driver)
     self.assertItemsEqual(
         [{
             'driver_type': 'power',
             'name': fake_driver_one.name,
             'description': fake_driver_one.description,
             'fields': [],
             'queryable': fake_driver_one.queryable,
             'missing_packages': fake_driver_one.detect_missing_packages(),
         }, {
             'driver_type': 'power',
             'name': fake_driver_two.name,
             'description': fake_driver_two.description,
             'fields': [],
             'queryable': fake_driver_two.queryable,
             'missing_packages': fake_driver_two.detect_missing_packages(),
         }, {
             'driver_type': 'pod',
             'name': fake_pod_driver.name,
             'description': fake_pod_driver.description,
             'fields': [],
             'queryable': fake_pod_driver.queryable,
             'missing_packages': fake_pod_driver.detect_missing_packages(),
         }], PowerDriverRegistry.get_schema())
Example #5
0
    def prepare_rack_rpc(self):
        rack_controller = factory.make_RackController()
        self.useFixture(RegionEventLoopFixture('rpc'))
        self.useFixture(RunningEventLoopFixture())

        fixture = self.useFixture(MockLiveRegionToClusterRPCFixture())
        protocol = fixture.makeCluster(rack_controller, DescribePowerTypes)
        self.power_types = PowerDriverRegistry.get_schema()
        protocol.DescribePowerTypes.side_effect = always_succeed_with(
            {'power_types': self.power_types})
        return protocol
Example #6
0
    def _interceptPowerTypesQuery(self):
        power_types = PowerDriverRegistry.get_schema(
            detect_missing_packages=False)

        @wraps(driver_parameters.get_all_power_types)
        def get_all_power_types(controllers=None, ignore_errors=True):
            # Callers can mutate this, so deep copy.
            return deepcopy(power_types)

        restore = monkey.patch(driver_parameters, "get_all_power_types",
                               get_all_power_types)
        self.addCleanup(restore)
Example #7
0
 def test_get_schema(self):
     fake_driver_one = make_power_driver_base()
     fake_driver_two = make_power_driver_base()
     fake_pod_driver = make_pod_driver_base()
     PowerDriverRegistry.register_item(fake_driver_one.name,
                                       fake_driver_one)
     PowerDriverRegistry.register_item(fake_driver_two.name,
                                       fake_driver_two)
     PowerDriverRegistry.register_item(fake_pod_driver.name,
                                       fake_pod_driver)
     self.assertItemsEqual(
         [
             {
                 "driver_type": "power",
                 "name": fake_driver_one.name,
                 "description": fake_driver_one.description,
                 "chassis": fake_driver_one.chassis,
                 "can_probe": fake_driver_one.can_probe,
                 "fields": [],
                 "queryable": fake_driver_one.queryable,
                 "missing_packages":
                 fake_driver_one.detect_missing_packages(),
             },
             {
                 "driver_type": "power",
                 "name": fake_driver_two.name,
                 "description": fake_driver_two.description,
                 "chassis": fake_driver_two.chassis,
                 "can_probe": fake_driver_two.can_probe,
                 "fields": [],
                 "queryable": fake_driver_two.queryable,
                 "missing_packages":
                 fake_driver_two.detect_missing_packages(),
             },
             {
                 "driver_type": "pod",
                 "name": fake_pod_driver.name,
                 "description": fake_pod_driver.description,
                 "chassis": fake_pod_driver.chassis,
                 "can_probe": fake_pod_driver.can_probe,
                 "fields": [],
                 "queryable": fake_pod_driver.queryable,
                 "missing_packages":
                 fake_pod_driver.detect_missing_packages(),
             },
         ],
         PowerDriverRegistry.get_schema(),
     )
Example #8
0
def get_power_state(system_id, hostname, power_type, context, clock=reactor):
    """Return the power state of the given node.

    :return: The string "on", "off" or "unknown".
    :raises PowerActionFail: When there's a failure querying the node's
        power state.
    """

    def check_power_state(state):
        if state not in ("on", "off", "unknown"):
            # This is considered an error.
            raise PowerActionFail(state)

    # Capture errors as we go along.
    exc_info = None, None, None

    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)
        )
    try:
        power_state = yield perform_power_driver_query(
            system_id, hostname, power_type, context
        )
        check_power_state(power_state)
    except Exception:
        # Hold the error; it will be reported later.
        exc_info = sys.exc_info()
    else:
        returnValue(power_state)

    # Reaching here means that things have gone wrong.
    assert exc_info != (None, None, None)
    exc_type, exc_value, exc_trace = exc_info
    raise exc_type(exc_value).with_traceback(exc_trace)
Example #9
0
 def scope_power_parameters(power_type, power_params):
     """Separate the global, bmc related power_parameters from the local,
     node-specific ones."""
     if not power_type:
         # If there is no power type, treat all params as node params.
         return ({}, power_params)
     power_driver = PowerDriverRegistry.get_item(power_type)
     if power_driver is None:
         # If there is no power driver, treat all params as node params.
         return ({}, power_params)
     power_fields = power_driver.settings
     if not power_fields:
         # If there is no parameter info, treat all params as node params.
         return ({}, power_params)
     bmc_params = {}
     node_params = {}
     for param_name in power_params:
         power_field = power_driver.get_setting(param_name)
         if (power_field and power_field.get('scope') == SETTING_SCOPE.BMC):
             bmc_params[param_name] = power_params[param_name]
         else:
             node_params[param_name] = power_params[param_name]
     return (bmc_params, node_params)
Example #10
0
def extract_ip_address(power_type, power_parameters):
    # Extract the ip_address from the power_parameters. If there is no
    # power_type, no power_parameters, or no valid value provided in the
    # power_address field, returns None.
    if not power_type or not power_parameters:
        return None
    power_driver = PowerDriverRegistry.get_item(power_type)
    if power_driver is None:
        return None
    power_type_parameters = power_driver.settings
    if not power_type_parameters:
        return None
    ip_extractor = power_driver.ip_extractor
    if not ip_extractor:
        return None
    field_value = power_parameters.get(ip_extractor.get("field_name"))
    if not field_value:
        return None
    extraction_pattern = ip_extractor.get("pattern")
    if not extraction_pattern:
        return None
    match = re.match(extraction_pattern, field_value)
    return match.group("address") if match else None
Example #11
0
 def extract_ip_address(power_type, power_parameters):
     """ Extract the ip_address from the power_parameters. If there is no
     power_type, no power_parameters, or no valid value provided in the
     power_address field, returns None. """
     if not power_type or not power_parameters:
         # Nothing to extract.
         return None
     power_driver = PowerDriverRegistry.get_item(power_type)
     if power_driver is None:
         maaslog.warning("No power driver for power type %s" % power_type)
         return None
     power_type_parameters = power_driver.settings
     if not power_type_parameters:
         maaslog.warning("No power driver settings for power type %s" %
                         power_type)
         return None
     ip_extractor = power_driver.ip_extractor
     if not ip_extractor:
         maaslog.info("No IP extractor configured for power type %s. "
                      "IP will not be extracted." % power_type)
         return None
     field_value = power_parameters.get(ip_extractor.get('field_name'))
     if not field_value:
         maaslog.warning("IP extractor field_value missing for %s" %
                         power_type)
         return None
     extraction_pattern = ip_extractor.get('pattern')
     if not extraction_pattern:
         maaslog.warning("IP extractor extraction_pattern missing for %s" %
                         power_type)
         return None
     match = re.match(extraction_pattern, field_value)
     if match:
         return match.group('address')
     # no match found - return None
     return None
Example #12
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))
Example #13
0
 def test_registry(self):
     self.assertItemsEqual([], PowerDriverRegistry)
     PowerDriverRegistry.register_item("driver", sentinel.driver)
     self.assertIn(sentinel.driver,
                   (item for name, item in PowerDriverRegistry))