Update IPMI driver for new base class.
Fix IPMI unit tests, and add missing power states and exceptions.
This commit is contained in:
parent
70d2f67e3e
commit
e1109c9b99
@ -31,7 +31,7 @@ from oslo.config import cfg
|
||||
from ironic.common import safe_utils
|
||||
from ironic.openstack.common import excutils
|
||||
from ironic.openstack.common import log as logging
|
||||
|
||||
from ironic.openstack.common.gettextutils import _
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -234,3 +234,7 @@ class NodeNotFound(NotFound):
|
||||
|
||||
class InterfaceNotFound(NotFound):
|
||||
message = _("Interface %(iface)s could not be found.")
|
||||
|
||||
|
||||
class PowerStateFailure(IronicException):
|
||||
message = _("Failed to set node power state to %(pstate)s.")
|
||||
|
@ -26,6 +26,7 @@ health.
|
||||
|
||||
"""
|
||||
|
||||
NOSTATE = None
|
||||
NULL = None
|
||||
INIT = 'initializing'
|
||||
ACTIVE = 'active'
|
||||
@ -35,3 +36,8 @@ DEPLOYFAIL = 'deploy failed'
|
||||
DEPLOYDONE = 'deploy complete'
|
||||
DELETED = 'deleted'
|
||||
ERROR = 'error'
|
||||
|
||||
POWER_ON = 'power on'
|
||||
POWER_OFF = 'power off'
|
||||
REBOOTING = 'rebooting'
|
||||
SUSPEND = 'suspended'
|
||||
|
@ -31,7 +31,7 @@ from ironic.common import exception
|
||||
from ironic.common import paths
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.manager import base
|
||||
from ironic.drivers import base
|
||||
from ironic.openstack.common import jsonutils as json
|
||||
from ironic.openstack.common import log as logging
|
||||
from ironic.openstack.common import loopingcall
|
||||
@ -48,7 +48,7 @@ opts = [
|
||||
help='path to directory stores pidfiles of baremetal_terminal'),
|
||||
cfg.IntOpt('ipmi_power_retry',
|
||||
default=5,
|
||||
help='maximal number of retries for IPMI operations'),
|
||||
help='Maximum seconds to retry IPMI operations'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -83,13 +83,12 @@ def _get_console_pid(node_id):
|
||||
return None
|
||||
|
||||
|
||||
class IPMI(base.PowerManager):
|
||||
"""IPMI Power Driver for Baremetal Nova Compute
|
||||
|
||||
This PowerManager class provides mechanism for controlling the power state
|
||||
of physical hardware via IPMI calls. It also provides serial console access
|
||||
where available.
|
||||
class IPMIPowerDriver(base.BMCDriver):
|
||||
"""Generic IPMI Power Driver
|
||||
|
||||
This BMCDriver class provides mechanism for controlling the power state
|
||||
of physical hardware via IPMI calls. It also provides console access
|
||||
for some supported hardware.
|
||||
"""
|
||||
|
||||
def __init__(self, node, **kwargs):
|
||||
@ -135,19 +134,16 @@ class IPMI(base.PowerManager):
|
||||
finally:
|
||||
utils.delete_if_exists(pwfile)
|
||||
|
||||
def _is_power(self, state):
|
||||
out_err = self._exec_ipmitool("power status")
|
||||
return out_err[0] == ("Chassis Power is %s\n" % state)
|
||||
|
||||
def _power_on(self):
|
||||
"""Turn the power to this node ON."""
|
||||
|
||||
def _wait_for_power_on():
|
||||
"""Called at an interval until the node's power is on."""
|
||||
|
||||
if self._is_power("on"):
|
||||
self.state = states.ACTIVE
|
||||
self._update_state()
|
||||
if self.state == states.POWER_ON:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if self.retries > CONF.ipmi_power_retry:
|
||||
self.state = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
@ -159,7 +155,7 @@ class IPMI(base.PowerManager):
|
||||
|
||||
self.retries = 0
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_on)
|
||||
timer.start(interval=0.5).wait()
|
||||
timer.start(interval=1).wait()
|
||||
|
||||
def _power_off(self):
|
||||
"""Turn the power to this node OFF."""
|
||||
@ -167,9 +163,10 @@ class IPMI(base.PowerManager):
|
||||
def _wait_for_power_off():
|
||||
"""Called at an interval until the node's power is off."""
|
||||
|
||||
if self._is_power("off"):
|
||||
self.state = states.DELETED
|
||||
self._update_state()
|
||||
if self.state == states.POWER_OFF:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if self.retries > CONF.ipmi_power_retry:
|
||||
self.state = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
@ -181,37 +178,55 @@ class IPMI(base.PowerManager):
|
||||
|
||||
self.retries = 0
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_off)
|
||||
timer.start(interval=0.5).wait()
|
||||
timer.start(interval=1).wait()
|
||||
|
||||
def _set_pxe_for_next_boot(self):
|
||||
# FIXME: raise exception, not just log
|
||||
# FIXME: make into a public set-boot function
|
||||
try:
|
||||
self._exec_ipmitool("chassis bootdev pxe")
|
||||
except Exception:
|
||||
LOG.exception(_("IPMI set next bootdev failed"))
|
||||
|
||||
def activate_node(self):
|
||||
"""Turns the power to node ON."""
|
||||
if self._is_power("on") and self.state == states.ACTIVE:
|
||||
LOG.warning(_("Activate node called, but node %s "
|
||||
"is already active") % self.address)
|
||||
self._set_pxe_for_next_boot()
|
||||
self._power_on()
|
||||
def _update_state(self):
|
||||
# FIXME: better error and other-state handling
|
||||
out_err = self._exec_ipmitool("power status")
|
||||
if out_err[0] == "Chassis Power is on\n":
|
||||
self.state = states.POWER_ON
|
||||
elif out_err[0] == "Chassis Power is off\n":
|
||||
self.state = states.POWER_OFF
|
||||
else:
|
||||
self.state = states.ERROR
|
||||
|
||||
def get_power_state(self):
|
||||
"""Checks and returns current power state."""
|
||||
self._update_state()
|
||||
return self.state
|
||||
|
||||
def reboot_node(self):
|
||||
def set_power_state(self, pstate):
|
||||
"""Turn the power on or off."""
|
||||
if self.state and self.state == pstate:
|
||||
LOG.warning(_("set_power_state called with current state."))
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
self._set_pxe_for_next_boot()
|
||||
self._power_on()
|
||||
elif pstate == states.POWER_OFF:
|
||||
self._power_off()
|
||||
else:
|
||||
LOG.error(_("set_power_state called with invalid pstate."))
|
||||
|
||||
if self.state != pstate:
|
||||
raise exception.PowerStateFailure(pstate=pstate)
|
||||
|
||||
def reboot(self):
|
||||
"""Cycles the power to a node."""
|
||||
self._power_off()
|
||||
self._set_pxe_for_next_boot()
|
||||
self._power_on()
|
||||
return self.state
|
||||
|
||||
def deactivate_node(self):
|
||||
"""Turns the power to node OFF, regardless of current state."""
|
||||
self._power_off()
|
||||
return self.state
|
||||
|
||||
def is_power_on(self):
|
||||
return self._is_power("on")
|
||||
if self.state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
def start_console(self):
|
||||
if not self.port:
|
||||
|
16
ironic/tests/drivers/__init__.py
Normal file
16
ironic/tests/drivers/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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.
|
@ -29,7 +29,7 @@ from ironic import test
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.tests.db import utils as db_utils
|
||||
from ironic.manager import ipmi
|
||||
from ironic.drivers import ipmi
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -39,7 +39,7 @@ class BareMetalIPMITestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(BareMetalIPMITestCase, self).setUp()
|
||||
self.node = db_utils.get_test_node()
|
||||
self.ipmi = ipmi.IPMI(self.node)
|
||||
self.ipmi = ipmi.IPMIPowerDriver(self.node)
|
||||
|
||||
def test_construct(self):
|
||||
self.assertEqual(self.ipmi.node_id, 123)
|
||||
@ -81,32 +81,44 @@ class BareMetalIPMITestCase(test.TestCase):
|
||||
self.ipmi._exec_ipmitool('A B C')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power(self):
|
||||
def test_update_state_on(self):
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi._is_power("on")
|
||||
self.ipmi.state = states.NOSTATE
|
||||
self.ipmi._update_state()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.POWER_ON)
|
||||
|
||||
def test_power_already_on(self):
|
||||
self.flags(ipmi_power_retry=0)
|
||||
def test_update_state_off(self):
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
["Chassis Power is off\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = states.DELETED
|
||||
self.ipmi._power_on()
|
||||
self.ipmi.state = states.NOSTATE
|
||||
self.ipmi._update_state()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.ACTIVE)
|
||||
self.assertEqual(self.ipmi.state, states.POWER_OFF)
|
||||
|
||||
def test_power_on_ok(self):
|
||||
def test_update_state_error(self):
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is foobar\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = states.NOSTATE
|
||||
self.ipmi._update_state()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.ERROR)
|
||||
|
||||
def test_set_power_on_ok(self):
|
||||
self.flags(ipmi_power_retry=0)
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("set bootdev pxe").AndReturn([])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.ipmi._exec_ipmitool("power on").AndReturn([])
|
||||
@ -114,10 +126,26 @@ class BareMetalIPMITestCase(test.TestCase):
|
||||
["Chassis Power is on\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = states.DELETED
|
||||
self.ipmi._power_on()
|
||||
self.ipmi.state = states.POWER_OFF
|
||||
self.ipmi.set_power_state(states.POWER_ON)
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.ACTIVE)
|
||||
self.assertEqual(self.ipmi.state, states.POWER_ON)
|
||||
|
||||
def test_set_power_off_ok(self):
|
||||
self.flags(ipmi_power_retry=0)
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.ipmi._exec_ipmitool("power off").AndReturn([])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = states.POWER_ON
|
||||
self.ipmi._power_off()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.POWER_OFF)
|
||||
|
||||
def test_power_on_fail(self):
|
||||
self.flags(ipmi_power_retry=0)
|
||||
@ -130,7 +158,7 @@ class BareMetalIPMITestCase(test.TestCase):
|
||||
["Chassis Power is off\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = states.DELETED
|
||||
self.ipmi.state = states.POWER_OFF
|
||||
self.ipmi._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.ERROR)
|
||||
@ -152,27 +180,33 @@ class BareMetalIPMITestCase(test.TestCase):
|
||||
["Chassis Power is off\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = states.DELETED
|
||||
self.ipmi.state = states.POWER_OFF
|
||||
self.ipmi._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.ERROR)
|
||||
self.assertEqual(self.ipmi.retries, 3)
|
||||
|
||||
def test_power_off_ok(self):
|
||||
self.flags(ipmi_power_retry=0)
|
||||
def test_get_power_state(self):
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.ipmi._exec_ipmitool("power off").AndReturn([])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = states.ACTIVE
|
||||
self.ipmi._power_off()
|
||||
self.ipmi.state = states.POWER_OFF
|
||||
pstate = self.ipmi.get_power_state()
|
||||
self.assertEqual(pstate, states.POWER_OFF)
|
||||
|
||||
self.ipmi.state = states.POWER_ON
|
||||
pstate = self.ipmi.get_power_state()
|
||||
self.assertEqual(pstate, states.POWER_ON)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, states.DELETED)
|
||||
|
||||
def test_reboot(self):
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def test_get_console_pid_path(self):
|
||||
self.flags(terminal_pid_dir='/tmp')
|
||||
|
Loading…
x
Reference in New Issue
Block a user