Merge "Wake up AMT interface before send request"
This commit is contained in:
commit
25e8ef33fe
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user