Merge "Add AMT-PXE-Driver Power&Management&Vendor Interface"
This commit is contained in:
commit
e9ee20d8d6
@ -11,7 +11,7 @@ pysnmp
|
|||||||
python-scciclient
|
python-scciclient
|
||||||
python-seamicroclient
|
python-seamicroclient
|
||||||
|
|
||||||
# The drac driver imports a python module called "pywsman", however,
|
# The drac and amt driver import a python module called "pywsman", however,
|
||||||
# this does not exist on pypi.
|
# this does not exist on pypi.
|
||||||
# It is installed by the openwsman-python (on RH) or python-openwsman (on deb)
|
# It is installed by the openwsman-python (on RH) or python-openwsman (on deb)
|
||||||
# package, from https://github.com/Openwsman/openwsman/blob/master/bindings/python/Makefile.am#L29
|
# package, from https://github.com/Openwsman/openwsman/blob/master/bindings/python/Makefile.am#L29
|
||||||
|
@ -508,6 +508,19 @@
|
|||||||
#protocol=http
|
#protocol=http
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Options defined in ironic.drivers.modules.amt.power
|
||||||
|
#
|
||||||
|
|
||||||
|
# Maximum number of times to attempt an AMT operation, before
|
||||||
|
# failing (integer value)
|
||||||
|
#max_attempts=3
|
||||||
|
|
||||||
|
# Amount of time (in seconds) to wait, before retrying an AMT
|
||||||
|
# operation (integer value)
|
||||||
|
#action_wait=10
|
||||||
|
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -23,6 +23,8 @@ from ironic.common import exception
|
|||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules import agent
|
from ironic.drivers.modules import agent
|
||||||
|
from ironic.drivers.modules.amt import management as amt_mgmt
|
||||||
|
from ironic.drivers.modules.amt import power as amt_power
|
||||||
from ironic.drivers.modules import discoverd
|
from ironic.drivers.modules import discoverd
|
||||||
from ironic.drivers.modules.drac import management as drac_mgmt
|
from ironic.drivers.modules.drac import management as drac_mgmt
|
||||||
from ironic.drivers.modules.drac import power as drac_power
|
from ironic.drivers.modules.drac import power as drac_power
|
||||||
@ -214,3 +216,16 @@ class FakeIPMIToolDiscoverdDriver(base.BaseDriver):
|
|||||||
self.vendor = ipmitool.VendorPassthru()
|
self.vendor = ipmitool.VendorPassthru()
|
||||||
self.management = ipmitool.IPMIManagement()
|
self.management = ipmitool.IPMIManagement()
|
||||||
self.inspect = discoverd.DiscoverdInspect()
|
self.inspect = discoverd.DiscoverdInspect()
|
||||||
|
|
||||||
|
|
||||||
|
class FakeAMTDriver(base.BaseDriver):
|
||||||
|
"""Fake AMT driver."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not importutils.try_import('pywsman'):
|
||||||
|
raise exception.DriverLoadError(
|
||||||
|
driver=self.__class__.__name__,
|
||||||
|
reason=_("Unable to import pywsman library"))
|
||||||
|
self.power = amt_power.AMTPower()
|
||||||
|
self.deploy = fake.FakeDeploy()
|
||||||
|
self.management = amt_mgmt.AMTManagement()
|
||||||
|
196
ironic/drivers/modules/amt/management.py
Normal file
196
ironic/drivers/modules/amt/management.py
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
#
|
||||||
|
# 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 Management Driver
|
||||||
|
"""
|
||||||
|
import copy
|
||||||
|
|
||||||
|
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.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
|
||||||
|
from ironic.openstack.common import log as logging
|
||||||
|
|
||||||
|
pywsman = importutils.try_import('pywsman')
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_boot_device_order(node, boot_device):
|
||||||
|
"""Set boot device order configuration of AMT Client.
|
||||||
|
|
||||||
|
:param node: a node object
|
||||||
|
:param boot_device: the boot device
|
||||||
|
:raises: AMTFailure
|
||||||
|
:raises: AMTConnectFailure
|
||||||
|
"""
|
||||||
|
client = amt_common.get_wsman_client(node)
|
||||||
|
source = pywsman.EndPointReference(resource_uris.CIM_BootSourceSetting,
|
||||||
|
None)
|
||||||
|
device = amt_common.BOOT_DEVICES_MAPPING[boot_device]
|
||||||
|
source.add_selector('InstanceID', device)
|
||||||
|
|
||||||
|
method = 'ChangeBootOrder'
|
||||||
|
|
||||||
|
options = pywsman.ClientOptions()
|
||||||
|
options.add_selector('InstanceID', 'Intel(r) AMT: Boot Configuration 0')
|
||||||
|
|
||||||
|
options.add_property('Source', source)
|
||||||
|
try:
|
||||||
|
client.wsman_invoke(options, resource_uris.CIM_BootConfigSetting,
|
||||||
|
method)
|
||||||
|
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.exception(_LE("Failed to set boot device %(boot_device)s for "
|
||||||
|
"node %(node_id)s with error: %(error)s."),
|
||||||
|
{'boot_device': boot_device, 'node_id': node.uuid,
|
||||||
|
'error': e})
|
||||||
|
else:
|
||||||
|
LOG.info(_LI("Successfully set boot device %(boot_device)s for "
|
||||||
|
"node %(node_id)s"),
|
||||||
|
{'boot_device': boot_device, 'node_id': node.uuid})
|
||||||
|
|
||||||
|
|
||||||
|
def _enable_boot_config(node):
|
||||||
|
"""Enable boot configuration of AMT Client.
|
||||||
|
|
||||||
|
:param node: a node object
|
||||||
|
:raises: AMTFailure
|
||||||
|
:raises: AMTConnectFailure
|
||||||
|
"""
|
||||||
|
client = amt_common.get_wsman_client(node)
|
||||||
|
config = pywsman.EndPointReference(resource_uris.CIM_BootConfigSetting,
|
||||||
|
None)
|
||||||
|
config.add_selector('InstanceID', 'Intel(r) AMT: Boot Configuration 0')
|
||||||
|
|
||||||
|
method = 'SetBootConfigRole'
|
||||||
|
|
||||||
|
options = pywsman.ClientOptions()
|
||||||
|
options.add_selector('Name', 'Intel(r) AMT Boot Service')
|
||||||
|
|
||||||
|
options.add_property('Role', '1')
|
||||||
|
options.add_property('BootConfigSetting', config)
|
||||||
|
try:
|
||||||
|
client.wsman_invoke(options, resource_uris.CIM_BootService, method)
|
||||||
|
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.exception(_LE("Failed to enable boot config for node "
|
||||||
|
"%(node_id)s with error: %(error)s."),
|
||||||
|
{'node_id': node.uuid, 'error': e})
|
||||||
|
else:
|
||||||
|
LOG.info(_LI("Successfully enabled boot config for node %(node_id)s."),
|
||||||
|
{'node_id': node.uuid})
|
||||||
|
|
||||||
|
|
||||||
|
class AMTManagement(base.ManagementInterface):
|
||||||
|
|
||||||
|
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_supported_boot_devices(self):
|
||||||
|
"""Get a list of the supported boot devices.
|
||||||
|
|
||||||
|
:returns: A list with the supported boot devices.
|
||||||
|
"""
|
||||||
|
return list(amt_common.BOOT_DEVICES_MAPPING)
|
||||||
|
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
def set_boot_device(self, task, device, persistent=False):
|
||||||
|
"""Set the boot device for the task's node.
|
||||||
|
|
||||||
|
Set the boot device to use on next boot of the node.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:param device: the boot device
|
||||||
|
:param persistent: Boolean value. True if the boot device will
|
||||||
|
persist to all future boots, False if not.
|
||||||
|
Default: False.
|
||||||
|
:raises: InvalidParameterValue if an invalid boot device is specified.
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
|
||||||
|
if device not in amt_common.BOOT_DEVICES_MAPPING:
|
||||||
|
raise exception.InvalidParameterValue(_("set_boot_device called "
|
||||||
|
"with invalid device %(device)s for node %(node_id)s.") %
|
||||||
|
{'device': device, 'node_id': node.uuid})
|
||||||
|
|
||||||
|
# AMT/vPro doesnt support set boot_device persistent, so we have to
|
||||||
|
# save amt_boot_device/amt_boot_persistent in driver_internal_info.
|
||||||
|
driver_internal_info = node.driver_internal_info
|
||||||
|
driver_internal_info['amt_boot_device'] = device
|
||||||
|
driver_internal_info['amt_boot_persistent'] = persistent
|
||||||
|
node.driver_internal_info = driver_internal_info
|
||||||
|
node.save()
|
||||||
|
|
||||||
|
def get_boot_device(self, task):
|
||||||
|
"""Get the current boot device for the task's node.
|
||||||
|
|
||||||
|
Returns the current boot device of the node.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:returns: a dictionary containing:
|
||||||
|
:boot_device: the boot device
|
||||||
|
:persistent: Whether the boot device will persist to all
|
||||||
|
future boots or not, None if it is unknown.
|
||||||
|
"""
|
||||||
|
driver_internal_info = task.node.driver_internal_info
|
||||||
|
device = driver_internal_info.get('amt_boot_device')
|
||||||
|
persistent = driver_internal_info.get('amt_boot_persistent')
|
||||||
|
if not device:
|
||||||
|
device = amt_common.DEFAULT_BOOT_DEVICE
|
||||||
|
persistent = True
|
||||||
|
return {'boot_device': device,
|
||||||
|
'persistent': persistent}
|
||||||
|
|
||||||
|
def ensure_next_boot_device(self, node, boot_device):
|
||||||
|
"""Set next boot device (one time only) of AMT Client.
|
||||||
|
|
||||||
|
:param node: a node object
|
||||||
|
:param boot_device: the boot device
|
||||||
|
:raises: AMTFailure
|
||||||
|
:raises: AMTConnectFailure
|
||||||
|
"""
|
||||||
|
driver_internal_info = node.driver_internal_info
|
||||||
|
if not driver_internal_info.get('amt_boot_persistent'):
|
||||||
|
driver_internal_info['amt_boot_device'] = (
|
||||||
|
amt_common.DEFAULT_BOOT_DEVICE)
|
||||||
|
driver_internal_info['amt_boot_persistent'] = True
|
||||||
|
node.driver_internal_info = driver_internal_info
|
||||||
|
node.save()
|
||||||
|
|
||||||
|
_set_boot_device_order(node, boot_device)
|
||||||
|
_enable_boot_config(node)
|
||||||
|
|
||||||
|
def get_sensors_data(self, task):
|
||||||
|
raise NotImplementedError()
|
266
ironic/drivers/modules/amt/power.py
Normal file
266
ironic/drivers/modules/amt/power.py
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
#
|
||||||
|
# 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_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
|
||||||
|
from ironic.openstack.common import log as logging
|
||||||
|
from ironic.openstack.common import loopingcall
|
||||||
|
|
||||||
|
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)
|
30
ironic/drivers/modules/amt/vendor.py
Normal file
30
ironic/drivers/modules/amt/vendor.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#
|
||||||
|
# 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 Vendor Methods
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ironic.common import boot_devices
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers import base
|
||||||
|
from ironic.drivers.modules import pxe
|
||||||
|
|
||||||
|
|
||||||
|
class AMTPXEVendorPassthru(pxe.VendorPassthru):
|
||||||
|
|
||||||
|
@base.passthru(['POST'], method='pass_deploy_info')
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
def _continue_deploy(self, task, **kwargs):
|
||||||
|
task.driver.management.ensure_next_boot_device(task.node,
|
||||||
|
boot_devices.PXE)
|
||||||
|
super(AMTPXEVendorPassthru, self)._continue_deploy(task, **kwargs)
|
@ -22,6 +22,9 @@ from oslo_utils import importutils
|
|||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
|
from ironic.drivers.modules.amt import management as amt_management
|
||||||
|
from ironic.drivers.modules.amt import power as amt_power
|
||||||
|
from ironic.drivers.modules.amt import vendor as amt_vendor
|
||||||
from ironic.drivers.modules import iboot
|
from ironic.drivers.modules import iboot
|
||||||
from ironic.drivers.modules.ilo import deploy as ilo_deploy
|
from ironic.drivers.modules.ilo import deploy as ilo_deploy
|
||||||
from ironic.drivers.modules.ilo import management as ilo_management
|
from ironic.drivers.modules.ilo import management as ilo_management
|
||||||
@ -234,3 +237,23 @@ class PXEAndVirtualBoxDriver(base.BaseDriver):
|
|||||||
self.deploy = pxe.PXEDeploy()
|
self.deploy = pxe.PXEDeploy()
|
||||||
self.management = virtualbox.VirtualBoxManagement()
|
self.management = virtualbox.VirtualBoxManagement()
|
||||||
self.vendor = pxe.VendorPassthru()
|
self.vendor = pxe.VendorPassthru()
|
||||||
|
|
||||||
|
|
||||||
|
class PXEAndAMTDriver(base.BaseDriver):
|
||||||
|
"""PXE + AMT driver.
|
||||||
|
|
||||||
|
This driver implements the `core` functionality, combining
|
||||||
|
:class:`ironic.drivers.amt.AMTPower` for power on/off and reboot with
|
||||||
|
:class:`ironic.driver.pxe.PXE` for image deployment. Implementations are in
|
||||||
|
those respective classes; this class is merely the glue between them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not importutils.try_import('pywsman'):
|
||||||
|
raise exception.DriverLoadError(
|
||||||
|
driver=self.__class__.__name__,
|
||||||
|
reason=_("Unable to import pywsman library"))
|
||||||
|
self.power = amt_power.AMTPower()
|
||||||
|
self.deploy = pxe.PXEDeploy()
|
||||||
|
self.management = amt_management.AMTManagement()
|
||||||
|
self.vendor = amt_vendor.AMTPXEVendorPassthru()
|
||||||
|
223
ironic/tests/drivers/amt/test_management.py
Normal file
223
ironic/tests/drivers/amt/test_management.py
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test class for AMT ManagementInterface
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic.common import boot_devices
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers.modules.amt import common as amt_common
|
||||||
|
from ironic.drivers.modules.amt import management as amt_mgmt
|
||||||
|
from ironic.drivers.modules.amt import resource_uris
|
||||||
|
from ironic.tests.conductor import utils as mgr_utils
|
||||||
|
from ironic.tests.db import base as db_base
|
||||||
|
from ironic.tests.db import utils as db_utils
|
||||||
|
from ironic.tests.drivers.drac import utils as test_utils
|
||||||
|
from ironic.tests.objects import utils as obj_utils
|
||||||
|
|
||||||
|
INFO_DICT = db_utils.get_test_amt_info()
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'pywsman')
|
||||||
|
class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AMTManagementInteralMethodsTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_amt',
|
||||||
|
driver_info=INFO_DICT)
|
||||||
|
|
||||||
|
def test__set_boot_device_order(self, mock_client_pywsman):
|
||||||
|
namespace = resource_uris.CIM_BootConfigSetting
|
||||||
|
device = boot_devices.PXE
|
||||||
|
result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
|
||||||
|
namespace)
|
||||||
|
mock_xml = test_utils.mock_wsman_root(result_xml)
|
||||||
|
mock_pywsman = mock_client_pywsman.Client.return_value
|
||||||
|
mock_pywsman.invoke.return_value = mock_xml
|
||||||
|
|
||||||
|
amt_mgmt._set_boot_device_order(self.node, device)
|
||||||
|
|
||||||
|
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
|
||||||
|
namespace, 'ChangeBootOrder')
|
||||||
|
|
||||||
|
def test__set_boot_device_order_fail(self, mock_client_pywsman):
|
||||||
|
namespace = resource_uris.CIM_BootConfigSetting
|
||||||
|
device = boot_devices.PXE
|
||||||
|
result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
|
||||||
|
namespace)
|
||||||
|
mock_xml = test_utils.mock_wsman_root(result_xml)
|
||||||
|
mock_pywsman = mock_client_pywsman.Client.return_value
|
||||||
|
mock_pywsman.invoke.return_value = mock_xml
|
||||||
|
|
||||||
|
self.assertRaises(exception.AMTFailure,
|
||||||
|
amt_mgmt._set_boot_device_order, self.node, device)
|
||||||
|
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
|
||||||
|
namespace, 'ChangeBootOrder')
|
||||||
|
|
||||||
|
mock_pywsman = mock_client_pywsman.Client.return_value
|
||||||
|
mock_pywsman.invoke.return_value = None
|
||||||
|
|
||||||
|
self.assertRaises(exception.AMTConnectFailure,
|
||||||
|
amt_mgmt._set_boot_device_order, self.node, device)
|
||||||
|
|
||||||
|
def test__enable_boot_config(self, mock_client_pywsman):
|
||||||
|
namespace = resource_uris.CIM_BootService
|
||||||
|
result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
|
||||||
|
namespace)
|
||||||
|
mock_xml = test_utils.mock_wsman_root(result_xml)
|
||||||
|
mock_pywsman = mock_client_pywsman.Client.return_value
|
||||||
|
mock_pywsman.invoke.return_value = mock_xml
|
||||||
|
|
||||||
|
amt_mgmt._enable_boot_config(self.node)
|
||||||
|
|
||||||
|
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
|
||||||
|
namespace, 'SetBootConfigRole')
|
||||||
|
|
||||||
|
def test__enable_boot_config_fail(self, mock_client_pywsman):
|
||||||
|
namespace = resource_uris.CIM_BootService
|
||||||
|
result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
|
||||||
|
namespace)
|
||||||
|
mock_xml = test_utils.mock_wsman_root(result_xml)
|
||||||
|
mock_pywsman = mock_client_pywsman.Client.return_value
|
||||||
|
mock_pywsman.invoke.return_value = mock_xml
|
||||||
|
|
||||||
|
self.assertRaises(exception.AMTFailure,
|
||||||
|
amt_mgmt._enable_boot_config, self.node)
|
||||||
|
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
|
||||||
|
namespace, 'SetBootConfigRole')
|
||||||
|
|
||||||
|
mock_pywsman = mock_client_pywsman.Client.return_value
|
||||||
|
mock_pywsman.invoke.return_value = None
|
||||||
|
|
||||||
|
self.assertRaises(exception.AMTConnectFailure,
|
||||||
|
amt_mgmt._enable_boot_config, self.node)
|
||||||
|
|
||||||
|
|
||||||
|
class AMTManagementTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AMTManagementTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
||||||
|
self.info = INFO_DICT
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_amt',
|
||||||
|
driver_info=self.info)
|
||||||
|
|
||||||
|
def test_get_properties(self):
|
||||||
|
expected = amt_common.COMMON_PROPERTIES
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(expected, task.driver.get_properties())
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'parse_driver_info')
|
||||||
|
def test_validate(self, mock_drvinfo):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.management.validate(task)
|
||||||
|
mock_drvinfo.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'parse_driver_info')
|
||||||
|
def test_validate_fail(self, mock_drvinfo):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
mock_drvinfo.side_effect = exception.InvalidParameterValue('x')
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.management.validate,
|
||||||
|
task)
|
||||||
|
|
||||||
|
def test_get_supported_boot_devices(self):
|
||||||
|
expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM]
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(expected),
|
||||||
|
sorted(task.driver.management.get_supported_boot_devices()))
|
||||||
|
|
||||||
|
def test_set_boot_device_one_time(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.management.set_boot_device(task, 'pxe')
|
||||||
|
self.assertEqual('pxe',
|
||||||
|
task.node.driver_internal_info["amt_boot_device"])
|
||||||
|
self.assertFalse(
|
||||||
|
task.node.driver_internal_info["amt_boot_persistent"])
|
||||||
|
|
||||||
|
def test_set_boot_device_persistent(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.management.set_boot_device(task, 'pxe',
|
||||||
|
persistent=True)
|
||||||
|
self.assertEqual('pxe',
|
||||||
|
task.node.driver_internal_info["amt_boot_device"])
|
||||||
|
self.assertTrue(
|
||||||
|
task.node.driver_internal_info["amt_boot_persistent"])
|
||||||
|
|
||||||
|
def test_set_boot_device_fail(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.management.set_boot_device,
|
||||||
|
task, 'fake-device')
|
||||||
|
|
||||||
|
@mock.patch.object(amt_mgmt, '_enable_boot_config')
|
||||||
|
@mock.patch.object(amt_mgmt, '_set_boot_device_order')
|
||||||
|
def test_ensure_next_boot_device_one_time(self, mock_sbdo, mock_ebc):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
device = boot_devices.PXE
|
||||||
|
task.node.driver_internal_info['amt_boot_device'] = 'pxe'
|
||||||
|
task.driver.management.ensure_next_boot_device(task.node, device)
|
||||||
|
self.assertEqual('disk',
|
||||||
|
task.node.driver_internal_info["amt_boot_device"])
|
||||||
|
self.assertTrue(
|
||||||
|
task.node.driver_internal_info["amt_boot_persistent"])
|
||||||
|
mock_sbdo.assert_called_once_with(task.node, device)
|
||||||
|
mock_ebc.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_mgmt, '_enable_boot_config')
|
||||||
|
@mock.patch.object(amt_mgmt, '_set_boot_device_order')
|
||||||
|
def test_ensure_next_boot_device_persistent(self, mock_sbdo, mock_ebc):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
device = boot_devices.PXE
|
||||||
|
task.node.driver_internal_info['amt_boot_device'] = 'pxe'
|
||||||
|
task.node.driver_internal_info['amt_boot_persistent'] = True
|
||||||
|
task.driver.management.ensure_next_boot_device(task.node, device)
|
||||||
|
self.assertEqual('pxe',
|
||||||
|
task.node.driver_internal_info["amt_boot_device"])
|
||||||
|
self.assertTrue(
|
||||||
|
task.node.driver_internal_info["amt_boot_persistent"])
|
||||||
|
mock_sbdo.assert_called_once_with(task.node, device)
|
||||||
|
mock_ebc.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
def test_get_boot_device(self):
|
||||||
|
expected = {'boot_device': boot_devices.DISK, 'persistent': True}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(expected,
|
||||||
|
task.driver.management.get_boot_device(task))
|
||||||
|
|
||||||
|
def test_get_sensor_data(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(NotImplementedError,
|
||||||
|
task.driver.management.get_sensors_data,
|
||||||
|
task)
|
260
ironic/tests/drivers/amt/test_power.py
Normal file
260
ironic/tests/drivers/amt/test_power.py
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test class for AMT ManagementInterface
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic.common import boot_devices
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers.modules.amt import common as amt_common
|
||||||
|
from ironic.drivers.modules.amt import power as amt_power
|
||||||
|
from ironic.drivers.modules.amt import resource_uris
|
||||||
|
from ironic.tests.conductor import utils as mgr_utils
|
||||||
|
from ironic.tests.db import base as db_base
|
||||||
|
from ironic.tests.db import utils as db_utils
|
||||||
|
from ironic.tests.drivers.drac import utils as test_utils
|
||||||
|
from ironic.tests.objects import utils as obj_utils
|
||||||
|
|
||||||
|
INFO_DICT = db_utils.get_test_amt_info()
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class AMTPowerInteralMethodsTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AMTPowerInteralMethodsTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
||||||
|
self.info = INFO_DICT
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_amt',
|
||||||
|
driver_info=self.info)
|
||||||
|
CONF.set_override('max_attempts', 2, 'amt')
|
||||||
|
CONF.set_override('action_wait', 0, 'amt')
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'get_wsman_client')
|
||||||
|
def test__set_power_state(self, mock_client_pywsman):
|
||||||
|
namespace = resource_uris.CIM_PowerManagementService
|
||||||
|
mock_client = mock_client_pywsman.return_value
|
||||||
|
amt_power._set_power_state(self.node, states.POWER_ON)
|
||||||
|
mock_client.wsman_invoke.assert_called_once_with(mock.ANY,
|
||||||
|
namespace, 'RequestPowerStateChange', mock.ANY)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'get_wsman_client')
|
||||||
|
def test__set_power_state_fail(self, mock_client_pywsman):
|
||||||
|
mock_client = mock_client_pywsman.return_value
|
||||||
|
mock_client.wsman_invoke.side_effect = exception.AMTFailure('x')
|
||||||
|
self.assertRaises(exception.AMTFailure,
|
||||||
|
amt_power._set_power_state,
|
||||||
|
self.node, states.POWER_ON)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'get_wsman_client')
|
||||||
|
def test__power_status(self, mock_gwc):
|
||||||
|
namespace = resource_uris.CIM_AssociatedPowerManagementService
|
||||||
|
result_xml = test_utils.build_soap_xml([{'PowerState':
|
||||||
|
'2'}],
|
||||||
|
namespace)
|
||||||
|
mock_doc = test_utils.mock_wsman_root(result_xml)
|
||||||
|
mock_client = mock_gwc.return_value
|
||||||
|
mock_client.wsman_get.return_value = mock_doc
|
||||||
|
self.assertEqual(
|
||||||
|
states.POWER_ON, amt_power._power_status(self.node))
|
||||||
|
|
||||||
|
result_xml = test_utils.build_soap_xml([{'PowerState':
|
||||||
|
'8'}],
|
||||||
|
namespace)
|
||||||
|
mock_doc = test_utils.mock_wsman_root(result_xml)
|
||||||
|
mock_client = mock_gwc.return_value
|
||||||
|
mock_client.wsman_get.return_value = mock_doc
|
||||||
|
self.assertEqual(
|
||||||
|
states.POWER_OFF, amt_power._power_status(self.node))
|
||||||
|
|
||||||
|
result_xml = test_utils.build_soap_xml([{'PowerState':
|
||||||
|
'4'}],
|
||||||
|
namespace)
|
||||||
|
mock_doc = test_utils.mock_wsman_root(result_xml)
|
||||||
|
mock_client = mock_gwc.return_value
|
||||||
|
mock_client.wsman_get.return_value = mock_doc
|
||||||
|
self.assertEqual(
|
||||||
|
states.ERROR, amt_power._power_status(self.node))
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'get_wsman_client')
|
||||||
|
def test__power_status_fail(self, mock_gwc):
|
||||||
|
mock_client = mock_gwc.return_value
|
||||||
|
mock_client.wsman_get.side_effect = exception.AMTFailure('x')
|
||||||
|
self.assertRaises(exception.AMTFailure,
|
||||||
|
amt_power._power_status,
|
||||||
|
self.node)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_power_status')
|
||||||
|
@mock.patch.object(amt_power, '_set_power_state')
|
||||||
|
def test__set_and_wait_power_on_with_boot_device(self, mock_sps,
|
||||||
|
mock_ps):
|
||||||
|
mock_snbd = mock.Mock()
|
||||||
|
target_state = states.POWER_ON
|
||||||
|
boot_device = boot_devices.PXE
|
||||||
|
mock_ps.side_effect = [states.POWER_OFF, states.POWER_ON]
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.driver_internal_info['amt_boot_device'] = boot_device
|
||||||
|
task.driver.management.ensure_next_boot_device = mock_snbd
|
||||||
|
mock_snbd.return_value = None
|
||||||
|
self.assertEqual(states.POWER_ON,
|
||||||
|
amt_power._set_and_wait(task, target_state))
|
||||||
|
mock_snbd.assert_called_with(task.node, boot_devices.PXE)
|
||||||
|
mock_sps.assert_called_once_with(task.node, states.POWER_ON)
|
||||||
|
mock_ps.assert_called_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_power_status')
|
||||||
|
@mock.patch.object(amt_power, '_set_power_state')
|
||||||
|
def test__set_and_wait_power_on_without_boot_device(self, mock_sps,
|
||||||
|
mock_ps):
|
||||||
|
target_state = states.POWER_ON
|
||||||
|
mock_ps.side_effect = [states.POWER_OFF, states.POWER_ON]
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(states.POWER_ON,
|
||||||
|
amt_power._set_and_wait(task, target_state))
|
||||||
|
mock_sps.assert_called_once_with(task.node, states.POWER_ON)
|
||||||
|
mock_ps.assert_called_with(task.node)
|
||||||
|
|
||||||
|
boot_device = boot_devices.DISK
|
||||||
|
self.node.driver_internal_info['amt_boot_device'] = boot_device
|
||||||
|
mock_ps.side_effect = [states.POWER_OFF, states.POWER_ON]
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(states.POWER_ON,
|
||||||
|
amt_power._set_and_wait(task, target_state))
|
||||||
|
mock_sps.assert_called_with(task.node, states.POWER_ON)
|
||||||
|
mock_ps.assert_called_with(task.node)
|
||||||
|
|
||||||
|
def test__set_and_wait_wrong_target_state(self):
|
||||||
|
target_state = 'fake-state'
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
amt_power._set_and_wait, task, target_state)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_power_status')
|
||||||
|
@mock.patch.object(amt_power, '_set_power_state')
|
||||||
|
def test__set_and_wait_exceed_iterations(self, mock_sps,
|
||||||
|
mock_ps):
|
||||||
|
target_state = states.POWER_ON
|
||||||
|
mock_ps.side_effect = [states.POWER_OFF, states.POWER_OFF,
|
||||||
|
states.POWER_OFF]
|
||||||
|
mock_sps.return_value = exception.AMTFailure('x')
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.PowerStateFailure,
|
||||||
|
amt_power._set_and_wait, task, target_state)
|
||||||
|
mock_sps.assert_called_with(task.node, states.POWER_ON)
|
||||||
|
mock_ps.assert_called_with(task.node)
|
||||||
|
self.assertEqual(3, mock_ps.call_count)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_power_status')
|
||||||
|
def test__set_and_wait_already_target_state(self, mock_ps):
|
||||||
|
target_state = states.POWER_ON
|
||||||
|
mock_ps.side_effect = [states.POWER_ON]
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(states.POWER_ON,
|
||||||
|
amt_power._set_and_wait(task, target_state))
|
||||||
|
mock_ps.assert_called_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_power_status')
|
||||||
|
@mock.patch.object(amt_power, '_set_power_state')
|
||||||
|
def test__set_and_wait_power_off(self, mock_sps, mock_ps):
|
||||||
|
target_state = states.POWER_OFF
|
||||||
|
mock_ps.side_effect = [states.POWER_ON, states.POWER_OFF]
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(states.POWER_OFF,
|
||||||
|
amt_power._set_and_wait(task, target_state))
|
||||||
|
mock_sps.assert_called_once_with(task.node, states.POWER_OFF)
|
||||||
|
mock_ps.assert_called_with(task.node)
|
||||||
|
|
||||||
|
|
||||||
|
class AMTPowerTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AMTPowerTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
||||||
|
self.info = INFO_DICT
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_amt',
|
||||||
|
driver_info=self.info)
|
||||||
|
|
||||||
|
def test_get_properties(self):
|
||||||
|
expected = amt_common.COMMON_PROPERTIES
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(expected, task.driver.get_properties())
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'parse_driver_info')
|
||||||
|
def test_validate(self, mock_drvinfo):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.power.validate(task)
|
||||||
|
mock_drvinfo.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_common, 'parse_driver_info')
|
||||||
|
def test_validate_fail(self, mock_drvinfo):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
mock_drvinfo.side_effect = exception.InvalidParameterValue('x')
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.power.validate,
|
||||||
|
task)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_power_status')
|
||||||
|
def test_get_power_state(self, mock_ps):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
mock_ps.return_value = states.POWER_ON
|
||||||
|
self.assertEqual(states.POWER_ON,
|
||||||
|
task.driver.power.get_power_state(task))
|
||||||
|
mock_ps.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_set_and_wait')
|
||||||
|
def test_set_power_state(self, mock_saw):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
pstate = states.POWER_ON
|
||||||
|
mock_saw.return_value = states.POWER_ON
|
||||||
|
task.driver.power.set_power_state(task, pstate)
|
||||||
|
mock_saw.assert_called_once_with(task, pstate)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_set_and_wait')
|
||||||
|
def test_set_power_state_fail(self, mock_saw):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
pstate = states.POWER_ON
|
||||||
|
mock_saw.side_effect = exception.PowerStateFailure('x')
|
||||||
|
self.assertRaises(exception.PowerStateFailure,
|
||||||
|
task.driver.power.set_power_state,
|
||||||
|
task, pstate)
|
||||||
|
mock_saw.assert_called_once_with(task, pstate)
|
||||||
|
|
||||||
|
@mock.patch.object(amt_power, '_set_and_wait')
|
||||||
|
def test_reboot(self, mock_saw):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.power.reboot(task)
|
||||||
|
calls = [mock.call(task, states.POWER_OFF),
|
||||||
|
mock.call(task, states.POWER_ON)]
|
||||||
|
mock_saw.has_calls(calls)
|
65
ironic/tests/drivers/amt/test_vendor.py
Normal file
65
ironic/tests/drivers/amt/test_vendor.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Test class for AMT Vendor methods."""
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from ironic.common import boot_devices
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers.modules import pxe
|
||||||
|
from ironic.tests.conductor import utils as mgr_utils
|
||||||
|
from ironic.tests.db import base as db_base
|
||||||
|
from ironic.tests.db import utils as db_utils
|
||||||
|
from ironic.tests.objects import utils as obj_utils
|
||||||
|
|
||||||
|
INFO_DICT = db_utils.get_test_amt_info()
|
||||||
|
|
||||||
|
|
||||||
|
class AMTPXEVendorPassthruTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AMTPXEVendorPassthruTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver="pxe_amt")
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='pxe_amt', driver_info=INFO_DICT)
|
||||||
|
|
||||||
|
def test_vendor_routes(self):
|
||||||
|
expected = ['heartbeat', 'pass_deploy_info']
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
vendor_routes = task.driver.vendor.vendor_routes
|
||||||
|
self.assertIsInstance(vendor_routes, dict)
|
||||||
|
self.assertEqual(sorted(expected), sorted(list(vendor_routes)))
|
||||||
|
|
||||||
|
def test_driver_routes(self):
|
||||||
|
expected = ['lookup']
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
driver_routes = task.driver.vendor.driver_routes
|
||||||
|
self.assertIsInstance(driver_routes, dict)
|
||||||
|
self.assertEqual(sorted(expected), sorted(list(driver_routes)))
|
||||||
|
|
||||||
|
@mock.patch.object(pxe.VendorPassthru, '_continue_deploy')
|
||||||
|
def test_vendorpassthru_continue_deploy(self, mock_pxe_vendorpassthru):
|
||||||
|
mock_ensure = mock.Mock()
|
||||||
|
kwargs = {'address': '123456'}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.node.provision_state = states.DEPLOYWAIT
|
||||||
|
task.node.target_provision_state = states.ACTIVE
|
||||||
|
task.driver.management.ensure_next_boot_device = mock_ensure
|
||||||
|
task.driver.vendor._continue_deploy(task, **kwargs)
|
||||||
|
mock_ensure.assert_called_with(task.node, boot_devices.PXE)
|
||||||
|
mock_pxe_vendorpassthru.assert_called_once_with(task, **kwargs)
|
@ -97,16 +97,17 @@ if not proliantutils:
|
|||||||
|
|
||||||
|
|
||||||
# attempt to load the external 'pywsman' library, which is required by
|
# attempt to load the external 'pywsman' library, which is required by
|
||||||
# the optional drivers.modules.drac module
|
# the optional drivers.modules.drac and drivers.modules.amt module
|
||||||
pywsman = importutils.try_import('pywsman')
|
pywsman = importutils.try_import('pywsman')
|
||||||
if not pywsman:
|
if not pywsman:
|
||||||
pywsman = mock.Mock()
|
pywsman = mock.Mock()
|
||||||
sys.modules['pywsman'] = pywsman
|
sys.modules['pywsman'] = pywsman
|
||||||
|
# Now that the external library has been mocked, if anything had already
|
||||||
# if anything has loaded the drac driver yet, reload it now that the
|
# loaded any of the drivers, reload them.
|
||||||
# external library has been mocked
|
if 'ironic.drivers.modules.drac' in sys.modules:
|
||||||
if 'ironic.drivers.modules.drac' in sys.modules:
|
reload(sys.modules['ironic.drivers.modules.drac'])
|
||||||
reload(sys.modules['ironic.drivers.modules.drac'])
|
if 'ironic.drivers.modules.amt' in sys.modules:
|
||||||
|
reload(sys.modules['ironic.drivers.modules.amt'])
|
||||||
|
|
||||||
|
|
||||||
# attempt to load the external 'iboot' library, which is required by
|
# attempt to load the external 'iboot' library, which is required by
|
||||||
|
@ -53,6 +53,7 @@ ironic.drivers =
|
|||||||
fake_snmp = ironic.drivers.fake:FakeSNMPDriver
|
fake_snmp = ironic.drivers.fake:FakeSNMPDriver
|
||||||
fake_irmc = ironic.drivers.fake:FakeIRMCDriver
|
fake_irmc = ironic.drivers.fake:FakeIRMCDriver
|
||||||
fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
|
fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
|
||||||
|
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
||||||
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
||||||
pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver
|
pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver
|
||||||
pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
|
pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
|
||||||
@ -64,6 +65,7 @@ ironic.drivers =
|
|||||||
pxe_drac = ironic.drivers.drac:PXEDracDriver
|
pxe_drac = ironic.drivers.drac:PXEDracDriver
|
||||||
pxe_snmp = ironic.drivers.pxe:PXEAndSNMPDriver
|
pxe_snmp = ironic.drivers.pxe:PXEAndSNMPDriver
|
||||||
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
|
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
|
||||||
|
pxe_amt = ironic.drivers.pxe:PXEAndAMTDriver
|
||||||
|
|
||||||
ironic.database.migration_backend =
|
ironic.database.migration_backend =
|
||||||
sqlalchemy = ironic.db.sqlalchemy.migration
|
sqlalchemy = ironic.db.sqlalchemy.migration
|
||||||
|
Loading…
Reference in New Issue
Block a user