ff51384fc1
Several OpenStack projects wrap help messages for translation. Follow suit by wrapping the help messages. Change-Id: I38572dfa40b3cb10b1c58d9b555985c58afd70aa Closes-Bug: #1413830
267 lines
9.3 KiB
Python
267 lines
9.3 KiB
Python
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
AMT Power Driver
|
|
"""
|
|
import copy
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_service import loopingcall
|
|
from oslo_utils import excutils
|
|
from oslo_utils import importutils
|
|
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _
|
|
from ironic.common.i18n import _LE
|
|
from ironic.common.i18n import _LI
|
|
from ironic.common.i18n import _LW
|
|
from ironic.common import states
|
|
from ironic.conductor import task_manager
|
|
from ironic.drivers import base
|
|
from ironic.drivers.modules.amt import common as amt_common
|
|
from ironic.drivers.modules.amt import resource_uris
|
|
|
|
pywsman = importutils.try_import('pywsman')
|
|
|
|
opts = [
|
|
cfg.IntOpt('max_attempts',
|
|
default=3,
|
|
help=_('Maximum number of times to attempt an AMT operation, '
|
|
'before failing')),
|
|
cfg.IntOpt('action_wait',
|
|
default=10,
|
|
help=_('Amount of time (in seconds) to wait, before retrying '
|
|
'an AMT operation'))
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(opts, group='amt')
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
AMT_POWER_MAP = {
|
|
states.POWER_ON: '2',
|
|
states.POWER_OFF: '8',
|
|
}
|
|
|
|
|
|
def _generate_power_action_input(action):
|
|
"""Generate Xmldoc as set_power_state input.
|
|
|
|
This generates a Xmldoc used as input for set_power_state.
|
|
|
|
:param action: the power action.
|
|
:returns: Xmldoc.
|
|
"""
|
|
method_input = "RequestPowerStateChange_INPUT"
|
|
address = 'http://schemas.xmlsoap.org/ws/2004/08/addressing'
|
|
anonymous = ('http://schemas.xmlsoap.org/ws/2004/08/addressing/'
|
|
'role/anonymous')
|
|
wsman = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
|
|
namespace = resource_uris.CIM_PowerManagementService
|
|
|
|
doc = pywsman.XmlDoc(method_input)
|
|
root = doc.root()
|
|
root.set_ns(namespace)
|
|
root.add(namespace, 'PowerState', action)
|
|
|
|
child = root.add(namespace, 'ManagedElement', None)
|
|
child.add(address, 'Address', anonymous)
|
|
|
|
grand_child = child.add(address, 'ReferenceParameters', None)
|
|
grand_child.add(wsman, 'ResourceURI', resource_uris.CIM_ComputerSystem)
|
|
|
|
g_grand_child = grand_child.add(wsman, 'SelectorSet', None)
|
|
g_g_grand_child = g_grand_child.add(wsman, 'Selector', 'ManagedSystem')
|
|
g_g_grand_child.attr_add(wsman, 'Name', 'Name')
|
|
return doc
|
|
|
|
|
|
def _set_power_state(node, target_state):
|
|
"""Set power state of the AMT Client.
|
|
|
|
:param node: a node object.
|
|
:param target_state: desired power state.
|
|
:raises: AMTFailure
|
|
:raises: AMTConnectFailure
|
|
"""
|
|
client = amt_common.get_wsman_client(node)
|
|
|
|
method = 'RequestPowerStateChange'
|
|
options = pywsman.ClientOptions()
|
|
options.add_selector('Name', 'Intel(r) AMT Power Management Service')
|
|
|
|
doc = _generate_power_action_input(AMT_POWER_MAP[target_state])
|
|
try:
|
|
client.wsman_invoke(options, resource_uris.CIM_PowerManagementService,
|
|
method, doc)
|
|
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception(_LE("Failed to set power state %(state)s for "
|
|
"node %(node_id)s with error: %(error)s."),
|
|
{'state': target_state, 'node_id': node.uuid,
|
|
'error': e})
|
|
else:
|
|
LOG.info(_LI("Power state set to %(state)s for node %(node_id)s"),
|
|
{'state': target_state, 'node_id': node.uuid})
|
|
|
|
|
|
def _power_status(node):
|
|
"""Get the power status for a node.
|
|
|
|
:param node: a node object.
|
|
:returns: one of ironic.common.states POWER_OFF, POWER_ON or ERROR.
|
|
:raises: AMTFailure.
|
|
:raises: AMTConnectFailure.
|
|
|
|
"""
|
|
client = amt_common.get_wsman_client(node)
|
|
namespace = resource_uris.CIM_AssociatedPowerManagementService
|
|
try:
|
|
doc = client.wsman_get(namespace)
|
|
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception(_LE("Failed to get power state for node %(node_id)s "
|
|
"with error: %(error)s."),
|
|
{'node_id': node.uuid, 'error': e})
|
|
|
|
item = "PowerState"
|
|
power_state = amt_common.xml_find(doc, namespace, item).text
|
|
for state in AMT_POWER_MAP:
|
|
if power_state == AMT_POWER_MAP[state]:
|
|
return state
|
|
return states.ERROR
|
|
|
|
|
|
def _set_and_wait(task, target_state):
|
|
"""Helper function for DynamicLoopingCall.
|
|
|
|
This method changes the power state and polls AMT until the desired
|
|
power state is reached.
|
|
|
|
:param task: a TaskManager instance contains the target node.
|
|
:param target_state: desired power state.
|
|
:returns: one of ironic.common.states.
|
|
:raises: PowerStateFailure if cannot set the node to target_state.
|
|
:raises: AMTFailure.
|
|
:raises: AMTConnectFailure
|
|
:raises: InvalidParameterValue
|
|
"""
|
|
node = task.node
|
|
driver = task.driver
|
|
if target_state not in (states.POWER_ON, states.POWER_OFF):
|
|
raise exception.InvalidParameterValue(_('Unsupported target_state: %s')
|
|
% target_state)
|
|
elif target_state == states.POWER_ON:
|
|
boot_device = node.driver_internal_info.get('amt_boot_device')
|
|
if boot_device and boot_device != amt_common.DEFAULT_BOOT_DEVICE:
|
|
driver.management.ensure_next_boot_device(node, boot_device)
|
|
|
|
def _wait(status):
|
|
status['power'] = _power_status(node)
|
|
if status['power'] == target_state:
|
|
raise loopingcall.LoopingCallDone()
|
|
|
|
if status['iter'] >= CONF.amt.max_attempts:
|
|
status['power'] = states.ERROR
|
|
LOG.warning(_LW("AMT failed to set power state %(state)s after "
|
|
"%(tries)s retries on node %(node_id)s."),
|
|
{'state': target_state, 'tries': status['iter'],
|
|
'node_id': node.uuid})
|
|
raise loopingcall.LoopingCallDone()
|
|
|
|
try:
|
|
_set_power_state(node, target_state)
|
|
except Exception:
|
|
# Log failures but keep trying
|
|
LOG.warning(_LW("AMT set power state %(state)s for node %(node)s "
|
|
"- Attempt %(attempt)s times of %(max_attempt)s "
|
|
"failed."),
|
|
{'state': target_state, 'node': node.uuid,
|
|
'attempt': status['iter'] + 1,
|
|
'max_attempt': CONF.amt.max_attempts})
|
|
status['iter'] += 1
|
|
|
|
status = {'power': None, 'iter': 0}
|
|
|
|
timer = loopingcall.FixedIntervalLoopingCall(_wait, status)
|
|
timer.start(interval=CONF.amt.action_wait).wait()
|
|
|
|
if status['power'] != target_state:
|
|
raise exception.PowerStateFailure(pstate=target_state)
|
|
|
|
return status['power']
|
|
|
|
|
|
class AMTPower(base.PowerInterface):
|
|
"""AMT Power interface.
|
|
|
|
This Power interface control the power of node by providing power on/off
|
|
and reset functions.
|
|
"""
|
|
|
|
def get_properties(self):
|
|
return copy.deepcopy(amt_common.COMMON_PROPERTIES)
|
|
|
|
def validate(self, task):
|
|
"""Validate the driver_info in the node.
|
|
|
|
Check if the driver_info contains correct required fields
|
|
|
|
:param task: a TaskManager instance contains the target node.
|
|
:raises: MissingParameterValue if any required parameters are missing.
|
|
:raises: InvalidParameterValue if any parameters have invalid values.
|
|
"""
|
|
# FIXME(lintan): validate hangs if unable to reach AMT, so dont
|
|
# connect to the node until bug 1314961 is resolved.
|
|
amt_common.parse_driver_info(task.node)
|
|
|
|
def get_power_state(self, task):
|
|
"""Get the power state from the node.
|
|
|
|
:param task: a TaskManager instance contains the target node.
|
|
:raises: AMTFailure.
|
|
:raises: AMTConnectFailure.
|
|
"""
|
|
return _power_status(task.node)
|
|
|
|
@task_manager.require_exclusive_lock
|
|
def set_power_state(self, task, pstate):
|
|
"""Set the power state of the node.
|
|
|
|
Turn the node power on or off.
|
|
|
|
:param task: a TaskManager instance contains the target node.
|
|
:param pstate: The desired power state of the node.
|
|
:raises: PowerStateFailure if the power cannot set to pstate.
|
|
:raises: AMTFailure.
|
|
:raises: AMTConnectFailure.
|
|
:raises: InvalidParameterValue
|
|
"""
|
|
_set_and_wait(task, pstate)
|
|
|
|
@task_manager.require_exclusive_lock
|
|
def reboot(self, task):
|
|
"""Cycle the power of the node
|
|
|
|
:param task: a TaskManager instance contains the target node.
|
|
:raises: PowerStateFailure if failed to reboot.
|
|
:raises: AMTFailure.
|
|
:raises: AMTConnectFailure.
|
|
:raises: InvalidParameterValue
|
|
"""
|
|
_set_and_wait(task, states.POWER_OFF)
|
|
_set_and_wait(task, states.POWER_ON)
|