Add Wake-On-Lan driver

This patch is importing the Wake-On-Lan (WOL) driver and its documentation
from ironic to ironic-staging-driver.

Since we can't have duplicated entry points in setuptools we had to
rename the driver names as following:

pxe_wol   -> pxe_wol_iscsi
agent_wol -> pxe_wol_agent
fake_wol  -> fake_wol_fake

This patch is using the "<boot>_<power>_<deploy>" template to name the
drivers consistently.

Change-Id: I2b051494fdba7bf6ca30d8f7bb406511bf7d4d76
This commit is contained in:
Lucas Alvares Gomes 2016-01-28 14:10:23 +00:00
parent 986d47569d
commit 5ad7c7c925
16 changed files with 671 additions and 28 deletions

10
doc/source/drivers.rst Normal file
View File

@ -0,0 +1,10 @@
.. _drivers:
=================
Available drivers
=================
.. toctree::
:maxdepth: 1
drivers/wol

128
doc/source/drivers/wol.rst Normal file
View File

@ -0,0 +1,128 @@
.. _WOL:
==================
Wake-On-Lan driver
==================
Overview
========
Wake-On-Lan is a standard that allows a computer to be powered on by a
network message. This is widely available and doesn't require any fancy
hardware to work with [1]_.
The Wake-On-Lan driver is a **testing** driver not meant for
production. And useful for users that wants to try Ironic with real
bare metal instead of virtual machines.
It's important to note that Wake-On-Lan is only capable of powering on
the machine. When power off is called the driver won't take any action
and will just log a message, the power off require manual intervention
to be performed.
Also, since Wake-On-Lan does not offer any means to determine the current
power state of the machine, the driver relies on the power state set in
the Ironic database. Any calls to the API to get the power state of the
node will return the value from the Ironic's database.
Drivers
=======
pxe_wol_iscsi
^^^^^^^^^^^^^
Overview
~~~~~~~~
The ``pxe_wol_iscsi`` driver uses the Wake-On-Lan technology to control the
power state, PXE/iPXE technology for booting and the iSCSI methodology
for deploying the node.
Requirements
~~~~~~~~~~~~
* Wake-On-Lan should be enabled in the BIOS
Configuring and Enabling the driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Add ``pxe_wol_iscsi`` to the list of ``enabled_drivers`` in
*/etc/ironic/ironic.conf*. For example::
[DEFAULT]
...
enabled_drivers = pxe_ipmitool,pxe_wol_iscsi
2. Restart the Ironic conductor service::
service ironic-conductor restart
Registering a node with the Wake-On-Lan driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Nodes configured for Wake-On-Lan driver should have the ``driver``
property set to ``pxe_wol_iscsi``.
The node should have at least one port registered with it because the
Wake-On-Lan driver will use the MAC address of the ports to create the
magic packet [2]_.
The following configuration values are optional and can be added to the
node's ``driver_info`` as needed to match the network configuration:
- ``wol_host``: The broadcast IP address; defaults to
**255.255.255.255**.
- ``wol_port``: The destination port; defaults to **9**.
.. note::
Say the ``ironic-conductor`` is connected to more than one network and
the node you are trying to wake up is in the ``192.0.2.0/24`` range. The
``wol_host`` configuration should be set to **192.0.2.255** (the
broadcast IP) so the packets will get routed correctly.
The following sequence of commands can be used to enroll a node with
the Wake-On-Lan driver.
1. Create node::
ironic node-create -d pxe_wol_iscsi [-i wol_host=<broadcast ip> [ -i
wol_port=<destination port>]]
The above command ``ironic node-create`` will return UUID of the node,
which is the value of *$NODE* in the following command.
2. Associate port with the node created::
ironic port-create -n $NODE -a <MAC address>
pxe_wol_agent
^^^^^^^^^^^^^
Overview
~~~~~~~~
The ``pxe_wol_agent`` driver uses the Wake-On-Lan technology to control
the power state, PXE/iPXE technology for booting and the Ironic Python
Agent for deploying the node.
Additional requirements
~~~~~~~~~~~~~~~~~~~~~~~
* Boot device order should be set to "PXE, DISK" in the BIOS setup
* BIOS must try next boot device if PXE boot failed
* Cleaning should be disabled, see :ref:`cleaning`
* Node should be powered off before start of deploy
Configuration steps are the same as for ``pxe_wol_iscsi`` driver, replace
"pxe_wol_iscsi" with "pxe_wol_agent".
References
==========
.. [1] Wake-On-Lan - https://en.wikipedia.org/wiki/Wake-on-LAN
.. [2] Magic packet - https://en.wikipedia.org/wiki/Wake-on-LAN#Sending_the_magic_packet

View File

@ -7,6 +7,7 @@ Contents:
:maxdepth: 2
README
drivers
Indices and tables
==================

View File

@ -1,3 +1,6 @@
# Copyright 2016 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
@ -10,10 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from ironic_staging_drivers.tests import base
from ironic.common import exception
class FooTestCase(base.TestCase):
def test_foo(self):
pass
class WOLOperationError(exception.IronicException):
pass

View File

@ -0,0 +1,31 @@
# Copyright 2016 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.
import oslo_i18n as i18n
_translators = i18n.TranslatorFactory(domain='ironic-staging-drivers')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical

View File

@ -0,0 +1,40 @@
# Copyright 2016 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.
from ironic.common import exception as ironic_exception
from ironic_staging_drivers.common.i18n import _
def validate_network_port(port, port_name="Port"):
"""Validates the given port.
:param port: TCP/UDP port.
:param port_name: Name of the port.
:returns: An integer port number.
:raises: InvalidParameterValue, if the port is invalid.
"""
try:
port = int(port)
except ValueError:
raise ironic_exception.InvalidParameterValue(_(
'%(port_name)s "%(port)s" is not a valid integer.') %
{'port_name': port_name, 'port': port})
if port < 1 or port > 65535:
raise ironic_exception.InvalidParameterValue(_(
'%(port_name)s "%(port)s" is out of range. Valid port '
'numbers must be between 1 and 65535.') %
{'port_name': port_name, 'port': port})
return port

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
from oslotest import base
class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""

View File

@ -0,0 +1,196 @@
# Copyright 2016 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
from ironic.common import driver_factory
from ironic.common import exception as ironic_exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
import mock
from oslo_utils import uuidutils
from ironic_staging_drivers.common import exception
from ironic_staging_drivers.wol import power as wol_power
@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_fake')
self.driver = driver_factory.get_driver('fake_wol_fake')
self.node = obj_utils.create_test_node(self.context,
driver='fake_wol_fake')
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_power._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_power._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_fake')
with task_manager.acquire(
self.context, node.uuid, shared=True) as task:
self.assertRaises(ironic_exception.InvalidParameterValue,
wol_power._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_power._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_power._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_power._send_magic_packets(task, '255.255.255.255', 9)
expected_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'
b'\xcf-1')
mock_socket.return_value.sendto.assert_called_once_with(
expected_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_fake')
self.driver = driver_factory.get_driver('fake_wol_fake')
self.node = obj_utils.create_test_node(self.context,
driver='fake_wol_fake')
self.port = obj_utils.create_test_port(self.context,
node_id=self.node.id)
def test_get_properties(self):
expected = wol_power.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_power, '_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_power.LOG, 'info', autospec=True, spec_set=True)
@mock.patch.object(wol_power, '_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_power, '_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(ironic_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_power.LOG, 'info', autospec=True, spec_set=True)
@mock.patch.object(wol_power.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)

View File

@ -0,0 +1,67 @@
# Copyright 2016 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.
from ironic.drivers import base
from ironic.drivers.modules import agent
from ironic.drivers.modules import fake
from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules import pxe
from ironic_staging_drivers.wol import power as wol_power
class FakeWakeOnLanFakeDriver(base.BaseDriver):
"""Fake Wake-On-Lan driver."""
def __init__(self):
self.boot = fake.FakeBoot()
self.power = wol_power.WakeOnLanPower()
self.deploy = fake.FakeDeploy()
class PXEWakeOnLanISCSIDriver(base.BaseDriver):
"""PXE + WakeOnLan + iSCSI driver.
This driver implements the `core` functionality, combining
:class:`ironic.drivers.modules.pxe.PXEBoot` for boot and
:class:`ironic_staging_drivers.wol.power.WakeOnLanPower` for power
and :class:`ironic.drivers.modules.iscsi_deploy.ISCSIDeploy` for
image deployment. Implementations are in those respective classes;
this class is merely the glue between them.
"""
def __init__(self):
self.boot = pxe.PXEBoot()
self.power = wol_power.WakeOnLanPower()
self.deploy = iscsi_deploy.ISCSIDeploy()
self.vendor = iscsi_deploy.VendorPassthru()
class PXEWakeOnLanAgentDriver(base.BaseDriver):
"""PXE + WakeOnLan + Agent driver.
This driver implements the `core` functionality, combining
:class:`ironic.drivers.modules.pxe.PXEBoot` for boot and
:class:`ironic_staging_drivers.wol.power.WakeOnLanPower` for power
and :class:`ironic.drivers.modules.agent.AgentDeploy` for
image deployment. Implementations are in those respective classes;
this class is merely the glue between them.
"""
def __init__(self):
self.boot = pxe.PXEBoot()
self.power = wol_power.WakeOnLanPower()
self.deploy = agent.AgentDeploy()
self.vendor = agent.AgentVendorInterface()

View File

@ -0,0 +1,183 @@
# Copyright 2016 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 ironic.common import exception as ironic_exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers import base
from oslo_log import log
from ironic_staging_drivers.common import exception
from ironic_staging_drivers.common.i18n import _
from ironic_staging_drivers.common.i18n import _LI
from ironic_staging_drivers.common import utils
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)
port = utils.validate_network_port(port, 'wol_port')
if len(task.ports) < 1:
raise ironic_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 ironic_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)

View File

@ -0,0 +1,3 @@
---
features:
- Add the Wake-On-Lan (WOL) driver

View File

@ -3,3 +3,5 @@
# process, which may cause wedges in the gate later.
pbr>=1.6 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.utils>=3.5.0 # Apache-2.0

View File

@ -24,6 +24,9 @@ packages =
[entry_points]
ironic.drivers =
fake_wol_fake = ironic_staging_drivers.wol:FakeWakeOnLanFakeDriver
pxe_wol_iscsi = ironic_staging_drivers.wol:PXEWakeOnLanISCSIDriver
pxe_wol_agent = ironic_staging_drivers.wol:PXEWakeOnLanAgentDriver
[build_sphinx]
source-dir = doc/source

View File

@ -15,3 +15,4 @@ testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
os-testr>=0.4.1 # Apache-2.0
reno>=0.1.1 # Apache2
mock>=1.2 # BSD