Wake-On-Lan Power interface
This patch implements a simple Wake-On-Lan power interface. For those that does not have any fancy hardware just old PCs at home. Wake-On-Lan is only capable of powering ON the machine, so it's recommended to use the DIB ramdisk for testing the deployments with it because it does a soft reboot on the machine at the end of the deployment. After the machine is unprovisioned, you'll have to manually power it off :-) This patch also doesn't implement SecureON password feature for Wake-On-Lan, I left a TODO in the code for those willing to implement it. Implements: blueprint wol-power-driver Change-Id: I6c0f98ef1cab1ebfb4a7e1d0aaae29672db1c5a4
This commit is contained in:
parent
7916ff927a
commit
c92fac1e5a
@ -582,3 +582,7 @@ class UcsOperationError(IronicException):
|
|||||||
class UcsConnectionError(IronicException):
|
class UcsConnectionError(IronicException):
|
||||||
message = _("Cisco UCS client: connection failed for node "
|
message = _("Cisco UCS client: connection failed for node "
|
||||||
"%(node)s. Reason: %(error)s")
|
"%(node)s. Reason: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class WolOperationError(IronicException):
|
||||||
|
pass
|
||||||
|
@ -46,6 +46,7 @@ from ironic.drivers.modules import ssh
|
|||||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
from ironic.drivers.modules.ucs import power as ucs_power
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
|
from ironic.drivers.modules import wol
|
||||||
from ironic.drivers import utils
|
from ironic.drivers import utils
|
||||||
|
|
||||||
|
|
||||||
@ -260,3 +261,11 @@ class FakeUcsDriver(base.BaseDriver):
|
|||||||
self.power = ucs_power.Power()
|
self.power = ucs_power.Power()
|
||||||
self.deploy = fake.FakeDeploy()
|
self.deploy = fake.FakeDeploy()
|
||||||
self.management = ucs_mgmt.UcsManagement()
|
self.management = ucs_mgmt.UcsManagement()
|
||||||
|
|
||||||
|
|
||||||
|
class FakeWakeOnLanDriver(base.BaseDriver):
|
||||||
|
"""Fake Wake-On-Lan driver."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.power = wol.WakeOnLanPower()
|
||||||
|
self.deploy = fake.FakeDeploy()
|
||||||
|
184
ironic/drivers/modules/wol.py
Normal file
184
ironic/drivers/modules/wol.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# Copyright 2015 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ironic Wake-On-Lan power manager.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
from ironic.common.i18n import _LI
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers import base
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIRED_PROPERTIES = {}
|
||||||
|
OPTIONAL_PROPERTIES = {
|
||||||
|
'wol_host': _('Broadcast IP address; defaults to '
|
||||||
|
'255.255.255.255. Optional.'),
|
||||||
|
'wol_port': _("Destination port; defaults to 9. Optional."),
|
||||||
|
}
|
||||||
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||||
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||||
|
|
||||||
|
|
||||||
|
def _send_magic_packets(task, dest_host, dest_port):
|
||||||
|
"""Create and send magic packets.
|
||||||
|
|
||||||
|
Creates and sends a magic packet for each MAC address registered in
|
||||||
|
the Node.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:param dest_host: The broadcast to this IP address.
|
||||||
|
:param dest_port: The destination port.
|
||||||
|
:raises: WolOperationError if an error occur when connecting to the
|
||||||
|
host or sending the magic packets
|
||||||
|
|
||||||
|
"""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
with contextlib.closing(s) as sock:
|
||||||
|
for port in task.ports:
|
||||||
|
address = port.address.replace(':', '')
|
||||||
|
|
||||||
|
# TODO(lucasagomes): Implement sending the magic packets with
|
||||||
|
# SecureON password feature. If your NIC is capable of, you can
|
||||||
|
# set the password of your SecureON using the ethtool utility.
|
||||||
|
data = 'FFFFFFFFFFFF' + (address * 16)
|
||||||
|
packet = bytearray.fromhex(data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock.sendto(packet, (dest_host, dest_port))
|
||||||
|
except socket.error as e:
|
||||||
|
msg = (_("Failed to send Wake-On-Lan magic packets to "
|
||||||
|
"node %(node)s port %(port)s. Error: %(error)s") %
|
||||||
|
{'node': task.node.uuid, 'port': port.address,
|
||||||
|
'error': e})
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.WolOperationError(msg)
|
||||||
|
|
||||||
|
# let's not flood the network with broadcast packets
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_parameters(task):
|
||||||
|
driver_info = task.node.driver_info
|
||||||
|
host = driver_info.get('wol_host', '255.255.255.255')
|
||||||
|
port = driver_info.get('wol_port', 9)
|
||||||
|
try:
|
||||||
|
port = int(port)
|
||||||
|
except ValueError:
|
||||||
|
raise exception.InvalidParameterValue(_(
|
||||||
|
'Wake-On-Lan port must be an integer'))
|
||||||
|
|
||||||
|
if len(task.ports) < 1:
|
||||||
|
raise exception.MissingParameterValue(_(
|
||||||
|
'Wake-On-Lan needs at least one port resource to be '
|
||||||
|
'registered in the node'))
|
||||||
|
|
||||||
|
return {'host': host, 'port': port}
|
||||||
|
|
||||||
|
|
||||||
|
class WakeOnLanPower(base.PowerInterface):
|
||||||
|
"""Wake-On-Lan Driver for Ironic
|
||||||
|
|
||||||
|
This PowerManager class provides a mechanism for controlling power
|
||||||
|
state via Wake-On-Lan.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_properties(self):
|
||||||
|
return COMMON_PROPERTIES
|
||||||
|
|
||||||
|
def validate(self, task):
|
||||||
|
"""Validate driver.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:raises: InvalidParameterValue if parameters are invalid.
|
||||||
|
:raises: MissingParameterValue if required parameters are missing.
|
||||||
|
|
||||||
|
"""
|
||||||
|
_parse_parameters(task)
|
||||||
|
|
||||||
|
def get_power_state(self, task):
|
||||||
|
"""Not supported. Get the current power state of the task's node.
|
||||||
|
|
||||||
|
This operation is not supported by the Wake-On-Lan driver. So
|
||||||
|
value returned will be from the database and may not reflect
|
||||||
|
the actual state of the system.
|
||||||
|
|
||||||
|
:returns: POWER_OFF if power state is not set otherwise return
|
||||||
|
the node's power_state value from the database.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pstate = task.node.power_state
|
||||||
|
return states.POWER_OFF if pstate is states.NOSTATE else pstate
|
||||||
|
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
def set_power_state(self, task, pstate):
|
||||||
|
"""Wakes the task's node on power on. Powering off is not supported.
|
||||||
|
|
||||||
|
Wakes the task's node on. Wake-On-Lan does not support powering
|
||||||
|
the task's node off so, just log it.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:param pstate: The desired power state, one of ironic.common.states
|
||||||
|
POWER_ON, POWER_OFF.
|
||||||
|
:raises: InvalidParameterValue if parameters are invalid.
|
||||||
|
:raises: MissingParameterValue if required parameters are missing.
|
||||||
|
:raises: WolOperationError if an error occur when sending the
|
||||||
|
magic packets
|
||||||
|
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
params = _parse_parameters(task)
|
||||||
|
if pstate == states.POWER_ON:
|
||||||
|
_send_magic_packets(task, params['host'], params['port'])
|
||||||
|
elif pstate == states.POWER_OFF:
|
||||||
|
LOG.info(_LI('Power off called for node %s. Wake-On-Lan does not '
|
||||||
|
'support this operation. Manual intervention '
|
||||||
|
'required to perform this action.'), node.uuid)
|
||||||
|
else:
|
||||||
|
raise exception.InvalidParameterValue(_(
|
||||||
|
"set_power_state called for Node %(node)s with invalid "
|
||||||
|
"power state %(pstate)s.") % {'node': node.uuid,
|
||||||
|
'pstate': pstate})
|
||||||
|
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
def reboot(self, task):
|
||||||
|
"""Not supported. Cycles the power to the task's node.
|
||||||
|
|
||||||
|
This operation is not fully supported by the Wake-On-Lan
|
||||||
|
driver. So this method will just try to power the task's node on.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:raises: InvalidParameterValue if parameters are invalid.
|
||||||
|
:raises: MissingParameterValue if required parameters are missing.
|
||||||
|
:raises: WolOperationError if an error occur when sending the
|
||||||
|
magic packets
|
||||||
|
|
||||||
|
"""
|
||||||
|
LOG.info(_LI('Reboot called for node %s. Wake-On-Lan does '
|
||||||
|
'not fully support this operation. Trying to '
|
||||||
|
'power on the node.'), task.node.uuid)
|
||||||
|
self.set_power_state(task, states.POWER_ON)
|
@ -44,6 +44,7 @@ from ironic.drivers.modules import ssh
|
|||||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
from ironic.drivers.modules.ucs import power as ucs_power
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
|
from ironic.drivers.modules import wol
|
||||||
from ironic.drivers import utils
|
from ironic.drivers import utils
|
||||||
|
|
||||||
|
|
||||||
@ -309,3 +310,19 @@ class PXEAndUcsDriver(base.BaseDriver):
|
|||||||
self.deploy = pxe.PXEDeploy()
|
self.deploy = pxe.PXEDeploy()
|
||||||
self.management = ucs_mgmt.UcsManagement()
|
self.management = ucs_mgmt.UcsManagement()
|
||||||
self.vendor = pxe.VendorPassthru()
|
self.vendor = pxe.VendorPassthru()
|
||||||
|
|
||||||
|
|
||||||
|
class PXEAndWakeOnLanDriver(base.BaseDriver):
|
||||||
|
"""PXE + WakeOnLan driver.
|
||||||
|
|
||||||
|
This driver implements the `core` functionality, combining
|
||||||
|
:class:`ironic.drivers.modules.wol.WakeOnLanPower` for power on
|
||||||
|
:class:`ironic.driver.modules.pxe.PXE` for image deployment.
|
||||||
|
Implementations are in those respective classes;
|
||||||
|
this class is merely the glue between them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.power = wol.WakeOnLanPower()
|
||||||
|
self.deploy = pxe.PXEDeploy()
|
||||||
|
self.vendor = pxe.VendorPassthru()
|
||||||
|
194
ironic/tests/drivers/test_wol.py
Normal file
194
ironic/tests/drivers/test_wol.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
# Copyright 2015 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 Wake-On-Lan driver module."""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from ironic.common import driver_factory
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers.modules import wol
|
||||||
|
from ironic.tests.conductor import utils as mgr_utils
|
||||||
|
from ironic.tests.db import base as db_base
|
||||||
|
from ironic.tests.objects import utils as obj_utils
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(time, 'sleep', lambda *_: None)
|
||||||
|
class WakeOnLanPrivateMethodTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(WakeOnLanPrivateMethodTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver='fake_wol')
|
||||||
|
self.driver = driver_factory.get_driver('fake_wol')
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_wol')
|
||||||
|
self.port = obj_utils.create_test_port(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
|
||||||
|
def test__parse_parameters(self):
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
params = wol._parse_parameters(task)
|
||||||
|
self.assertEqual('255.255.255.255', params['host'])
|
||||||
|
self.assertEqual(9, params['port'])
|
||||||
|
|
||||||
|
def test__parse_parameters_non_default_params(self):
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
task.node.driver_info = {'wol_host': '1.2.3.4',
|
||||||
|
'wol_port': 7}
|
||||||
|
params = wol._parse_parameters(task)
|
||||||
|
self.assertEqual('1.2.3.4', params['host'])
|
||||||
|
self.assertEqual(7, params['port'])
|
||||||
|
|
||||||
|
def test__parse_parameters_no_ports_fail(self):
|
||||||
|
node = obj_utils.create_test_node(
|
||||||
|
self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
driver='fake_wol')
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, node.uuid, shared=True) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
wol._parse_parameters, task)
|
||||||
|
|
||||||
|
@mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
|
||||||
|
def test_send_magic_packets(self, mock_socket):
|
||||||
|
fake_socket = mock.Mock(spec=socket, spec_set=True)
|
||||||
|
mock_socket.return_value = fake_socket()
|
||||||
|
obj_utils.create_test_port(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
address='aa:bb:cc:dd:ee:ff',
|
||||||
|
node_id=self.node.id)
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
wol._send_magic_packets(task, '255.255.255.255', 9)
|
||||||
|
|
||||||
|
expected_calls = [
|
||||||
|
mock.call(),
|
||||||
|
mock.call().setsockopt(socket.SOL_SOCKET,
|
||||||
|
socket.SO_BROADCAST, 1),
|
||||||
|
mock.call().sendto(mock.ANY, ('255.255.255.255', 9)),
|
||||||
|
mock.call().sendto(mock.ANY, ('255.255.255.255', 9)),
|
||||||
|
mock.call().close()]
|
||||||
|
|
||||||
|
fake_socket.assert_has_calls(expected_calls)
|
||||||
|
self.assertEqual(1, mock_socket.call_count)
|
||||||
|
|
||||||
|
@mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
|
||||||
|
def test_send_magic_packets_network_sendto_error(self, mock_socket):
|
||||||
|
fake_socket = mock.Mock(spec=socket, spec_set=True)
|
||||||
|
fake_socket.return_value.sendto.side_effect = socket.error('boom')
|
||||||
|
mock_socket.return_value = fake_socket()
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
self.assertRaises(exception.WolOperationError,
|
||||||
|
wol._send_magic_packets,
|
||||||
|
task, '255.255.255.255', 9)
|
||||||
|
self.assertEqual(1, mock_socket.call_count)
|
||||||
|
# assert sendt0() was invoked
|
||||||
|
fake_socket.return_value.sendto.assert_called_once_with(
|
||||||
|
mock.ANY, ('255.255.255.255', 9))
|
||||||
|
|
||||||
|
@mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
|
||||||
|
def test_magic_packet_format(self, mock_socket):
|
||||||
|
fake_socket = mock.Mock(spec=socket, spec_set=True)
|
||||||
|
mock_socket.return_value = fake_socket()
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
wol._send_magic_packets(task, '255.255.255.255', 9)
|
||||||
|
|
||||||
|
expct_packet = (b'\xff\xff\xff\xff\xff\xffRT\x00\xcf-1RT\x00'
|
||||||
|
b'\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT'
|
||||||
|
b'\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00'
|
||||||
|
b'\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT'
|
||||||
|
b'\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1')
|
||||||
|
mock_socket.return_value.sendto.assert_called_once_with(
|
||||||
|
expct_packet, ('255.255.255.255', 9))
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(time, 'sleep', lambda *_: None)
|
||||||
|
class WakeOnLanDriverTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(WakeOnLanDriverTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver='fake_wol')
|
||||||
|
self.driver = driver_factory.get_driver('fake_wol')
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_wol')
|
||||||
|
self.port = obj_utils.create_test_port(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
|
||||||
|
def test_get_properties(self):
|
||||||
|
expected = wol.COMMON_PROPERTIES
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
self.assertEqual(expected, task.driver.get_properties())
|
||||||
|
|
||||||
|
def test_get_power_state(self):
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
task.node.power_state = states.POWER_ON
|
||||||
|
pstate = task.driver.power.get_power_state(task)
|
||||||
|
self.assertEqual(states.POWER_ON, pstate)
|
||||||
|
|
||||||
|
def test_get_power_state_nostate(self):
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=True) as task:
|
||||||
|
task.node.power_state = states.NOSTATE
|
||||||
|
pstate = task.driver.power.get_power_state(task)
|
||||||
|
self.assertEqual(states.POWER_OFF, pstate)
|
||||||
|
|
||||||
|
@mock.patch.object(wol, '_send_magic_packets', autospec=True,
|
||||||
|
spec_set=True)
|
||||||
|
def test_set_power_state_power_on(self, mock_magic):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.power.set_power_state(task, states.POWER_ON)
|
||||||
|
mock_magic.assert_called_once_with(task, '255.255.255.255', 9)
|
||||||
|
|
||||||
|
@mock.patch.object(wol.LOG, 'info', autospec=True, spec_set=True)
|
||||||
|
@mock.patch.object(wol, '_send_magic_packets', autospec=True,
|
||||||
|
spec_set=True)
|
||||||
|
def test_set_power_state_power_off(self, mock_magic, mock_log):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.power.set_power_state(task, states.POWER_OFF)
|
||||||
|
mock_log.assert_called_once_with(mock.ANY, self.node.uuid)
|
||||||
|
# assert magic packets weren't sent
|
||||||
|
self.assertFalse(mock_magic.called)
|
||||||
|
|
||||||
|
@mock.patch.object(wol, '_send_magic_packets', autospec=True,
|
||||||
|
spec_set=True)
|
||||||
|
def test_set_power_state_power_fail(self, mock_magic):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
task.driver.power.set_power_state,
|
||||||
|
task, 'wrong-state')
|
||||||
|
# assert magic packets weren't sent
|
||||||
|
self.assertFalse(mock_magic.called)
|
||||||
|
|
||||||
|
@mock.patch.object(wol.LOG, 'info', autospec=True, spec_set=True)
|
||||||
|
@mock.patch.object(wol.WakeOnLanPower, 'set_power_state', autospec=True,
|
||||||
|
spec_set=True)
|
||||||
|
def test_reboot(self, mock_power, mock_log):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.power.reboot(task)
|
||||||
|
mock_log.assert_called_once_with(mock.ANY, self.node.uuid)
|
||||||
|
mock_power.assert_called_once_with(task.driver.power, task,
|
||||||
|
states.POWER_ON)
|
@ -56,6 +56,7 @@ ironic.drivers =
|
|||||||
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
||||||
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
||||||
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
||||||
|
fake_wol = ironic.drivers.fake:FakeWakeOnLanDriver
|
||||||
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
|
||||||
@ -70,6 +71,7 @@ ironic.drivers =
|
|||||||
pxe_amt = ironic.drivers.pxe:PXEAndAMTDriver
|
pxe_amt = ironic.drivers.pxe:PXEAndAMTDriver
|
||||||
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
||||||
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
||||||
|
pxe_wol = ironic.drivers.pxe:PXEAndWakeOnLanDriver
|
||||||
|
|
||||||
ironic.database.migration_backend =
|
ironic.database.migration_backend =
|
||||||
sqlalchemy = ironic.db.sqlalchemy.migration
|
sqlalchemy = ironic.db.sqlalchemy.migration
|
||||||
|
Loading…
x
Reference in New Issue
Block a user