Merge "Wake up AMT interface before send request"

This commit is contained in:
Jenkins 2015-12-02 09:14:54 +00:00 committed by Gerrit Code Review
commit 25e8ef33fe
8 changed files with 132 additions and 11 deletions

View File

@ -489,6 +489,15 @@
# value)
#protocol=http
# Time interval (in seconds) for successive awake call
# to AMT interface, this depends on the IdleTimeout
# setting on AMT interface. AMT Interface will go to
# sleep after 60 seconds of inactivity by default.
# IdleTimeout=0 means AMT will not go to sleep at all.
# Setting awake_interval=0 will disable awake call. (integer
# value)
#awake_interval=60
#
# Options defined in ironic.drivers.modules.amt.power

View File

@ -333,7 +333,8 @@ class IPMIFailure(IronicException):
class AMTConnectFailure(IronicException):
_msg_fmt = _("Failed to connect to AMT service.")
_msg_fmt = _("Failed to connect to AMT service. This could be caused "
"by the wrong amt_address or bad network environment.")
class AMTFailure(IronicException):

View File

@ -10,12 +10,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Common functionalities for AMT Driver
"""
import time
from xml.etree import ElementTree
from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
@ -25,6 +26,7 @@ from ironic.common import boot_devices
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common import utils
pywsman = importutils.try_import('pywsman')
@ -50,6 +52,15 @@ opts = [
default='http',
help=_('Protocol used for AMT endpoint, '
'support http/https')),
cfg.IntOpt('awake_interval',
default=60,
min=0,
help=_('Time interval (in seconds) for successive awake call '
'to AMT interface, this depends on the IdleTimeout '
'setting on AMT interface. AMT Interface will go to '
'sleep after 60 seconds of inactivity by default. '
'IdleTimeout=0 means AMT will not go to sleep at all. '
'Setting awake_interval=0 will disable awake call.')),
]
CONF = cfg.CONF
@ -75,6 +86,9 @@ AMT_PROTOCOL_PORT_MAP = {
# ReturnValue constants
RET_SUCCESS = '0'
# A dict cache last awake call to AMT Interface
AMT_AWAKE_CACHE = {}
class Client(object):
"""AMT client.
@ -206,3 +220,36 @@ def xml_find(doc, namespace, item):
query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace,
'item': item})
return tree.find(query)
def awake_amt_interface(node):
"""Wake up AMT interface.
AMT interface goes to sleep after a period of time if the host is off.
This method will ping AMT interface to wake it up. Because there is
no guarantee that the AMT address in driver_info is correct, only
ping the IP five times which is enough to wake it up.
:param node: an Ironic node object.
:raises: AMTConnectFailure if unable to connect to the server.
"""
awake_interval = CONF.amt.awake_interval
if awake_interval == 0:
return
now = time.time()
last_awake = AMT_AWAKE_CACHE.get(node.uuid, 0)
if now - last_awake > awake_interval:
cmd_args = ['ping', '-i', 0.2, '-c', 5,
node.driver_info['amt_address']]
try:
utils.execute(*cmd_args)
except processutils.ProcessExecutionError as err:
LOG.error(_LE('Unable to awake AMT interface on node '
'%(node_id)s. Error: %(error)s'),
{'node_id': node.uuid, 'error': err})
raise exception.AMTConnectFailure()
else:
LOG.debug(('Successfully awakened AMT interface on node '
'%(node_id)s.'), {'node_id': node.uuid})
AMT_AWAKE_CACHE[node.uuid] = now

View File

@ -72,6 +72,7 @@ def _set_boot_device_order(node, boot_device):
:raises: AMTFailure
:raises: AMTConnectFailure
"""
amt_common.awake_amt_interface(node)
client = amt_common.get_wsman_client(node)
device = amt_common.BOOT_DEVICES_MAPPING[boot_device]
doc = _generate_change_boot_order_input(device)
@ -129,6 +130,7 @@ def _enable_boot_config(node):
:raises: AMTFailure
:raises: AMTConnectFailure
"""
amt_common.awake_amt_interface(node)
client = amt_common.get_wsman_client(node)
method = 'SetBootConfigRole'
doc = _generate_enable_boot_config_input()

View File

@ -97,6 +97,7 @@ def _set_power_state(node, target_state):
:raises: AMTFailure
:raises: AMTConnectFailure
"""
amt_common.awake_amt_interface(node)
client = amt_common.get_wsman_client(node)
method = 'RequestPowerStateChange'
@ -125,8 +126,8 @@ def _power_status(node):
:returns: one of ironic.common.states POWER_OFF, POWER_ON or ERROR.
:raises: AMTFailure.
:raises: AMTConnectFailure.
"""
amt_common.awake_amt_interface(node)
client = amt_common.get_wsman_client(node)
namespace = resource_uris.CIM_AssociatedPowerManagementService
try:

View File

@ -16,9 +16,12 @@ Test class for AMT Common
"""
import mock
from oslo_concurrency import processutils
from oslo_config import cfg
import time
from ironic.common import exception
from ironic.common import utils
from ironic.drivers.modules.amt import common as amt_common
from ironic.drivers.modules.amt import resource_uris
from ironic.tests import base
@ -171,3 +174,37 @@ class AMTCommonClientTestCase(base.TestCase):
client.wsman_invoke,
options, namespace, method)
mock_pywsman.invoke.assert_called_once_with(options, namespace, method)
class AwakeAMTInterfaceTestCase(db_base.DbTestCase):
def setUp(self):
super(AwakeAMTInterfaceTestCase, self).setUp()
self.info = INFO_DICT
self.node = obj_utils.create_test_node(self.context,
driver='fake_amt',
driver_info=self.info)
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
def test_awake_amt_interface(self, mock_ex):
amt_common.awake_amt_interface(self.node)
expected_args = ['ping', '-i', 0.2, '-c', 5, '1.2.3.4']
mock_ex.assert_called_once_with(*expected_args)
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
def test_awake_amt_interface_fail(self, mock_ex):
mock_ex.side_effect = processutils.ProcessExecutionError('x')
self.assertRaises(exception.AMTConnectFailure,
amt_common.awake_amt_interface,
self.node)
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
def test_awake_amt_interface_in_cache_time(self, mock_ex):
amt_common.AMT_AWAKE_CACHE[self.node.uuid] = time.time()
amt_common.awake_amt_interface(self.node)
self.assertFalse(mock_ex.called)
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
def test_awake_amt_interface_disable(self, mock_ex):
CONF.set_override('awake_interval', 0, 'amt')
amt_common.awake_amt_interface(self.node)
self.assertFalse(mock_ex.called)

View File

@ -46,7 +46,9 @@ class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
driver='fake_amt',
driver_info=INFO_DICT)
def test__set_boot_device_order(self, mock_client_pywsman):
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
def test__set_boot_device_order(self, mock_aw, mock_client_pywsman):
namespace = resource_uris.CIM_BootConfigSetting
device = boot_devices.PXE
result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
@ -59,8 +61,11 @@ class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
mock_pywsman.invoke.assert_called_once_with(
mock.ANY, namespace, 'ChangeBootOrder', mock.ANY)
self.assertTrue(mock_aw.called)
def test__set_boot_device_order_fail(self, mock_client_pywsman):
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
def test__set_boot_device_order_fail(self, mock_aw, mock_client_pywsman):
namespace = resource_uris.CIM_BootConfigSetting
device = boot_devices.PXE
result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
@ -79,8 +84,11 @@ class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
self.assertRaises(exception.AMTConnectFailure,
amt_mgmt._set_boot_device_order, self.node, device)
self.assertTrue(mock_aw.called)
def test__enable_boot_config(self, mock_client_pywsman):
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
def test__enable_boot_config(self, mock_aw, mock_client_pywsman):
namespace = resource_uris.CIM_BootService
result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
namespace)
@ -92,8 +100,11 @@ class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
mock_pywsman.invoke.assert_called_once_with(
mock.ANY, namespace, 'SetBootConfigRole', mock.ANY)
self.assertTrue(mock_aw.called)
def test__enable_boot_config_fail(self, mock_client_pywsman):
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
def test__enable_boot_config_fail(self, mock_aw, mock_client_pywsman):
namespace = resource_uris.CIM_BootService
result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
namespace)
@ -111,6 +122,7 @@ class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
self.assertRaises(exception.AMTConnectFailure,
amt_mgmt._enable_boot_config, self.node)
self.assertTrue(mock_aw.called)
class AMTManagementTestCase(db_base.DbTestCase):

View File

@ -48,27 +48,35 @@ class AMTPowerInteralMethodsTestCase(db_base.DbTestCase):
CONF.set_override('max_attempts', 2, 'amt')
CONF.set_override('action_wait', 0, 'amt')
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
autospec=True)
def test__set_power_state(self, mock_client_pywsman):
def test__set_power_state(self, mock_client_pywsman, mock_aw):
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)
self.assertTrue(mock_aw.called)
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
autospec=True)
def test__set_power_state_fail(self, mock_client_pywsman):
def test__set_power_state_fail(self, mock_client_pywsman, mock_aw):
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)
self.assertTrue(mock_aw.called)
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
autospec=True)
def test__power_status(self, mock_gwc):
def test__power_status(self, mock_gwc, mock_aw):
namespace = resource_uris.CIM_AssociatedPowerManagementService
result_xml = test_utils.build_soap_xml([{'PowerState':
'2'}],
@ -96,15 +104,19 @@ class AMTPowerInteralMethodsTestCase(db_base.DbTestCase):
mock_client.wsman_get.return_value = mock_doc
self.assertEqual(
states.ERROR, amt_power._power_status(self.node))
self.assertTrue(mock_aw.called)
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
autospec=True)
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
autospec=True)
def test__power_status_fail(self, mock_gwc):
def test__power_status_fail(self, mock_gwc, mock_aw):
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)
self.assertTrue(mock_aw.called)
@mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
spec_set=True, autospec=True)