Update IPMI driver for new base class.

Fix IPMI unit tests, and add missing power states and exceptions.
This commit is contained in:
Devananda van der Veen 2013-05-13 00:51:36 -07:00
parent 70d2f67e3e
commit e1109c9b99
5 changed files with 136 additions and 61 deletions

View File

@ -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.")

View File

@ -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'

View File

@ -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:

View 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.

View File

@ -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')