Merge "Add AMT-PXE-Driver Power&Management&Vendor Interface"

This commit is contained in:
Jenkins 2015-03-12 14:34:06 +00:00 committed by Gerrit Code Review
commit e9ee20d8d6
12 changed files with 1101 additions and 7 deletions

View File

@ -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

View File

@ -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]
# #

View File

@ -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()

View 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()

View 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)

View 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)

View File

@ -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()

View 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)

View 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)

View 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)

View File

@ -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

View File

@ -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