/usr/lib/python3/dist-packages/provisioningserver/power/query.py is in python3-maas-provisioningserver 2.0.0~beta3+bzr4941-0ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | # Copyright 2015-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""RPC helpers relating to power control."""
__all__ = [
"get_power_state",
"query_all_nodes",
]
from functools import partial
import sys
from provisioningserver import power
from provisioningserver.drivers.power import (
power_drivers_by_name,
PowerDriverRegistry,
)
from provisioningserver.events import (
EVENT_TYPES,
send_event_node,
)
from provisioningserver.logger.log import get_maas_logger
from provisioningserver.rpc.exceptions import (
NoSuchNode,
PowerActionFail,
)
from provisioningserver.utils.twisted import asynchronous
from twisted.internet import reactor
from twisted.internet.defer import (
DeferredList,
DeferredSemaphore,
inlineCallbacks,
returnValue,
succeed,
)
from twisted.python import log
maaslog = get_maas_logger("power")
@asynchronous
def perform_power_driver_query(system_id, hostname, power_type, context):
"""Query the node's power state.
No exception handling is performed here. This allows `get_power_state` to
perform multiple queries and only log the final error.
:param power_type: This must refer to one of the Python-based power
drivers, and *not* to a template-based one.
"""
# Get power driver for given power type
power_driver = PowerDriverRegistry[power_type]
return power_driver.query(system_id, context)
@asynchronous
@inlineCallbacks
def get_power_state(system_id, hostname, power_type, context, clock=reactor):
"""Return the power state of the given node.
:return: The string "on" or "off".
:raises PowerActionFail: When `power_type` is not queryable, or when
there's a failure when querying the node's power state.
"""
if power_type not in power.QUERY_POWER_TYPES:
# query_all_nodes() won't call this with an un-queryable power
# type, however this is left here to prevent PEBKAC.
raise PowerActionFail(
"Unknown power_type '%s'" % power_type)
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 = power_drivers_by_name.get(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:
# 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)
@inlineCallbacks
def power_query_success(system_id, hostname, state):
"""Report a node that for which power querying has succeeded."""
yield power.power_state_update(system_id, state)
@inlineCallbacks
def power_query_failure(system_id, hostname, failure):
"""Report a node that for which power querying has failed."""
message = "Power state could not be queried: %s"
message %= failure.getErrorMessage()
maaslog.error(message)
yield power.power_state_update(system_id, 'error')
yield send_event_node(
EVENT_TYPES.NODE_POWER_QUERY_FAILED,
system_id, hostname, message)
@asynchronous
def report_power_state(d, system_id, hostname):
"""Report the result of a power query.
:param d: A `Deferred` that will fire with the node's updated power state,
or an error condition. The callback/errback values are passed through
unaltered. See `get_power_state` for details.
"""
def cb(state):
d = power_query_success(system_id, hostname, state)
d.addCallback(lambda _: state)
return d
def eb(failure):
d = power_query_failure(system_id, hostname, failure)
d.addCallback(lambda _: failure)
return d
return d.addCallbacks(cb, eb)
def maaslog_report_success(node, power_state):
"""Log change in power state for node."""
if node['power_state'] != power_state:
maaslog.info(
"%s: Power state has changed from %s to %s.", node['hostname'],
node['power_state'], power_state)
return power_state
def maaslog_report_failure(node, failure):
"""Log failure to query node."""
if failure.check(PowerActionFail):
maaslog.error(
"%s: Could not query power state: %s.",
node['hostname'], failure.getErrorMessage())
elif failure.check(NoSuchNode):
maaslog.debug(
"%s: Could not update power state: "
"no such node.", node['hostname'])
else:
maaslog.error(
"%s: Failed to refresh power state: %s",
node['hostname'], failure.getErrorMessage())
# Also write out a full traceback to the server log.
log.err(failure, "Failed to refresh power state.")
def query_node(node, clock):
"""Calls `get_power_state` on the given node.
Logs to maaslog as errors and power states change.
"""
if node['system_id'] in power.power_action_registry:
maaslog.debug(
"%s: Skipping query power status, "
"power action already in progress.",
node['hostname'])
return succeed(None)
else:
d = get_power_state(
node['system_id'], node['hostname'], node['power_type'],
node['context'], clock=clock)
d = report_power_state(d, node['system_id'], node['hostname'])
d.addCallbacks(
partial(maaslog_report_success, node),
partial(maaslog_report_failure, node))
return d
def query_all_nodes(nodes, max_concurrency=5, clock=reactor):
"""Queries the given nodes for their power state.
Nodes' states are reported back to the region.
:return: A deferred, which fires once all nodes have been queried,
successfully or not.
"""
semaphore = DeferredSemaphore(tokens=max_concurrency)
queries = (
semaphore.run(query_node, node, clock)
for node in nodes if node['power_type'] in power.QUERY_POWER_TYPES)
return DeferredList(queries, consumeErrors=True)
|