Merge lp:~allenap/maas/revert-ipmi-power-driver into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Andres Rodriguez
Approved revision: no longer in the source branch.
Merged at revision: 4200
Proposed branch: lp:~allenap/maas/revert-ipmi-power-driver
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 654 lines (+130/-469)
6 files modified
etc/maas/templates/power/ipmi.conf (+4/-0)
etc/maas/templates/power/ipmi.template (+110/-0)
src/provisioningserver/drivers/power/__init__.py (+0/-2)
src/provisioningserver/drivers/power/ipmi.py (+0/-154)
src/provisioningserver/drivers/power/tests/test_ipmi.py (+0/-312)
src/provisioningserver/power/tests/test_poweraction.py (+16/-1)
To merge this branch: bzr merge lp:~allenap/maas/revert-ipmi-power-driver
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+268258@code.launchpad.net

Commit message

Revert to the template-based IPMI power driver.

The new driver is not reliable yet. Once the cause is fixed this will be landed again.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm!

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (76.9 KiB)

The attempt to merge lp:~allenap/maas/revert-ipmi-power-driver into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [63.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [63.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [92.0 kB]
Get:6 http://security.ubuntu.com trusty-security/universe Sources [29.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [328 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [114 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [229 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [133 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [599 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [308 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,961 kB in 3s (568 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth pyt...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'etc/maas/templates/power/ipmi.conf'
--- etc/maas/templates/power/ipmi.conf 1970-01-01 00:00:00 +0000
+++ etc/maas/templates/power/ipmi.conf 2015-08-17 16:53:58 +0000
@@ -0,0 +1,4 @@
1Section Chassis_Boot_Flags
2 Boot_Flags_Persistent No
3 Boot_Device PXE
4EndSection
05
=== added file 'etc/maas/templates/power/ipmi.template'
--- etc/maas/templates/power/ipmi.template 1970-01-01 00:00:00 +0000
+++ etc/maas/templates/power/ipmi.template 2015-08-17 16:53:58 +0000
@@ -0,0 +1,110 @@
1# -*- mode: shell-script -*-
2#
3# Control a system via ipmipower
4#
5
6# Parameters.
7# If power_change is 'query', echo the current state of the
8# machine: 'on' or 'off'.
9power_change={{power_change}}
10power_address={{power_address}}
11power_user={{power_user}}
12power_pass={{power_pass}}
13power_driver={{power_driver}}
14power_off_mode={{power_off_mode}}
15ipmipower={{ipmipower}}
16ipmi_chassis_config={{ipmi_chassis_config}}
17config={{config_dir}}/{{ipmi_config}}
18
19# If ip_address was supplied and power_address is not explicitly set then
20# use ip_address because it gets discovered on-the-fly based on mac_address.
21# We don't want to use it unilaterally because mac_address may be the host's
22# MAC, so only fall back if someone deliberately left power_address empty.
23{{if ip_address and not power_address}}
24power_address={{ip_address}}
25{{endif}}
26
27# This workaround is required on many BMCs, and should have no impact
28# on BMCs that don't require it.
29# See https://bugs.launchpad.net/maas/+bug/1287964
30workarounds="-W opensesspriv"
31
32# Determines the power command needed to execute the desired
33# action. This function receives ${power_change} as argument.
34formulate_power_command() {
35 case $1 in
36 'on') echo '--cycle --on-if-off' ;;
37 'off')
38 if [ "$power_off_mode" = "soft" ];
39 then
40 echo '--soft'
41 else
42 echo '--off'
43 fi ;;
44 'query') echo '--stat' ;;
45 *)
46 echo "Got unknown power state from ipmipower: '$1'" >&2
47 exit 1
48 esac
49}
50
51# Issue command to ipmipower, for the given system.
52issue_ipmi_command() {
53 # See https://launchpad.net/bugs/1053391 for details of this workaround
54 local driver_option="" user_option=""
55 if [ -n "$power_driver" ]
56 then
57 driver_option="--driver-type=${power_driver}"
58 fi
59 if [ -n "$power_user" ]
60 then
61 user_option="-u ${power_user}"
62 fi
63
64 if [ "$power_change" != "query" ]
65 then
66 # Use C locale to force English error messages.
67 result=$(echo workaround |\
68 LC_ALL=C ${ipmi_chassis_config} ${workarounds} ${driver_option} -h ${power_address} ${user_option} -p ${power_pass} --commit --filename ${config} 2>&1)
69
70 if echo $result | grep -q "password invalid"
71 then
72 echo "Invalid password" >&2
73 exit 2
74 fi
75 fi
76
77 # Use C locale to force English error messages.
78 result=$(echo workaround |\
79 LC_ALL=C ${ipmipower} ${workarounds} ${driver_option} -h ${power_address} ${user_option} -p ${power_pass} "$@")
80
81 exit_status=$?
82
83 if echo $result | grep -q "password invalid"
84 then
85 echo "Invalid password" >&2
86 exit 2
87 fi
88
89 if [ $exit_status -ne 0 ]; then
90 echo $result >&2
91 exit 2
92 fi
93
94 case "$result" in
95 *:* )
96 # Result looks like the usual IPMI output:
97 # <ipmi-ip-address>: <on/off>, just return the <on|off>
98 # part.
99 echo ${result} | cut -d ':' -f 2
100 ;;
101 * )
102 echo ${result};;
103 esac
104}
105
106# This script deliberately does not check the current power state
107# before issuing the requested power command. See bug 1171418 for an
108# explanation.
109power_command=$(formulate_power_command ${power_change})
110issue_ipmi_command ${power_command}
0111
=== modified file 'src/provisioningserver/drivers/power/__init__.py'
--- src/provisioningserver/drivers/power/__init__.py 2015-08-06 00:36:14 +0000
+++ src/provisioningserver/drivers/power/__init__.py 2015-08-17 16:53:58 +0000
@@ -334,7 +334,6 @@
334334
335from provisioningserver.drivers.power.apc import APCPowerDriver335from provisioningserver.drivers.power.apc import APCPowerDriver
336from provisioningserver.drivers.power.hmc import HMCPowerDriver336from provisioningserver.drivers.power.hmc import HMCPowerDriver
337from provisioningserver.drivers.power.ipmi import IPMIPowerDriver
338from provisioningserver.drivers.power.msftocs import MicrosoftOCSPowerDriver337from provisioningserver.drivers.power.msftocs import MicrosoftOCSPowerDriver
339from provisioningserver.drivers.power.mscm import MSCMPowerDriver338from provisioningserver.drivers.power.mscm import MSCMPowerDriver
340from provisioningserver.drivers.power.seamicro import SeaMicroPowerDriver339from provisioningserver.drivers.power.seamicro import SeaMicroPowerDriver
@@ -345,7 +344,6 @@
345builtin_power_drivers = [344builtin_power_drivers = [
346 APCPowerDriver(),345 APCPowerDriver(),
347 HMCPowerDriver(),346 HMCPowerDriver(),
348 IPMIPowerDriver(),
349 MicrosoftOCSPowerDriver(),347 MicrosoftOCSPowerDriver(),
350 MSCMPowerDriver(),348 MSCMPowerDriver(),
351 SeaMicroPowerDriver(),349 SeaMicroPowerDriver(),
352350
=== removed file 'src/provisioningserver/drivers/power/ipmi.py'
--- src/provisioningserver/drivers/power/ipmi.py 2015-08-11 01:13:20 +0000
+++ src/provisioningserver/drivers/power/ipmi.py 1970-01-01 00:00:00 +0000
@@ -1,154 +0,0 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""IPMI Power Driver."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17from subprocess import (
18 PIPE,
19 Popen,
20)
21from tempfile import NamedTemporaryFile
22
23from provisioningserver.drivers.power import (
24 PowerAuthError,
25 PowerDriver,
26 PowerFatalError,
27)
28from provisioningserver.utils.network import find_ip_via_arp
29from provisioningserver.utils.shell import (
30 call_and_check,
31 ExternalProcessError,
32)
33
34
35IPMI_CONFIG = """\
36Section Chassis_Boot_Flags
37 Boot_Flags_Persistent No
38 Boot_Device PXE
39EndSection
40"""
41
42
43def is_set(setting):
44 return not (setting is None or setting == "" or setting.isspace())
45
46
47class IPMIPowerDriver(PowerDriver):
48
49 name = 'ipmi'
50 description = "IPMI Power Driver."
51 settings = []
52
53 @staticmethod
54 def _issue_ipmi_chassis_config_command(command, change, address):
55 with NamedTemporaryFile() as tmp_config:
56 # Write out the chassis configuration.
57 tmp_config.write(IPMI_CONFIG)
58 tmp_config.flush()
59 # Use it when running the chassis config command.
60 # XXX: Not using call_and_check here because we
61 # need to check stderr.
62 command = tuple(command) + ("--filename", tmp_config.name)
63 process = Popen(command, stdout=PIPE, stderr=PIPE)
64 stdout, stderr = process.communicate()
65 stderr = stderr.strip()
66 if "password invalid" in stderr:
67 raise PowerAuthError("Invalid password.")
68 if process.returncode != 0:
69 raise PowerFatalError(
70 "Failed to power %s %s: %s" % (change, address, stderr))
71
72 @staticmethod
73 def _issue_ipmi_power_command(command, change, address):
74 command = tuple(command) # For consistency when testing.
75 try:
76 output = call_and_check(command)
77 except ExternalProcessError as e:
78 raise PowerFatalError(
79 "Failed to power %s %s: %s" % (
80 change, address, e.output_as_unicode))
81 else:
82 if 'on' in output:
83 return 'on'
84 elif 'off' in output:
85 return 'off'
86 else:
87 return output
88
89 def _issue_ipmi_command(
90 self, power_change, power_address=None, power_user=None,
91 power_pass=None, power_driver=None, power_off_mode=None,
92 ipmipower=None, ipmi_chassis_config=None, mac_address=None,
93 **extra):
94 """Issue command to ipmipower, for the given system."""
95 # This script deliberately does not check the current power state
96 # before issuing the requested power command. See bug 1171418 for an
97 # explanation.
98
99 if is_set(mac_address) and not is_set(power_address):
100 power_address = find_ip_via_arp(mac_address)
101
102 # The `-W opensesspriv` workaround is required on many BMCs, and
103 # should have no impact on BMCs that don't require it.
104 # See https://bugs.launchpad.net/maas/+bug/1287964
105 ipmi_chassis_config_command = [
106 ipmi_chassis_config, '-W', 'opensesspriv']
107 ipmipower_command = [
108 ipmipower, '-W', 'opensesspriv']
109
110 # Arguments in common between chassis config and power control. See
111 # https://launchpad.net/bugs/1053391 for details of modifying the
112 # command for power_driver and power_user.
113 common_args = []
114 if is_set(power_driver):
115 common_args.extend(("--driver-type", power_driver))
116 common_args.extend(('-h', power_address))
117 if is_set(power_user):
118 common_args.extend(("-u", power_user))
119 common_args.extend(('-p', power_pass))
120
121 # Update the chassis config and power commands.
122 ipmi_chassis_config_command.extend(common_args)
123 ipmi_chassis_config_command.append('--commit')
124 ipmipower_command.extend(common_args)
125
126 # Before changing state run the chassis config command.
127 if power_change in ("on", "off"):
128 self._issue_ipmi_chassis_config_command(
129 ipmi_chassis_config_command, power_change, power_address)
130
131 # Additional arguments for the power command.
132 if power_change == 'on':
133 ipmipower_command.append('--cycle')
134 ipmipower_command.append('--on-if-off')
135 elif power_change == 'off':
136 if power_off_mode == 'soft':
137 ipmipower_command.append('--soft')
138 else:
139 ipmipower_command.append('--off')
140 elif power_change == 'query':
141 ipmipower_command.append('--stat')
142
143 # Update or query the power state.
144 return self._issue_ipmi_power_command(
145 ipmipower_command, power_change, power_address)
146
147 def power_on(self, system_id, **kwargs):
148 self._issue_ipmi_command('on', **kwargs)
149
150 def power_off(self, system_id, **kwargs):
151 self._issue_ipmi_command('off', **kwargs)
152
153 def power_query(self, system_id, **kwargs):
154 return self._issue_ipmi_command('query', **kwargs)
1550
=== removed file 'src/provisioningserver/drivers/power/tests/test_ipmi.py'
--- src/provisioningserver/drivers/power/tests/test_ipmi.py 2015-08-11 03:56:48 +0000
+++ src/provisioningserver/drivers/power/tests/test_ipmi.py 1970-01-01 00:00:00 +0000
@@ -1,312 +0,0 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for `provisioningserver.drivers.power.ipmi`."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17import random
18from subprocess import PIPE
19
20from maastesting.factory import factory
21from maastesting.matchers import (
22 MockCalledOnceWith,
23 MockNotCalled,
24)
25from maastesting.testcase import MAASTestCase
26from mock import (
27 ANY,
28 sentinel,
29)
30from provisioningserver.drivers.power import (
31 ipmi as ipmi_module,
32 PowerAuthError,
33 PowerFatalError,
34)
35from provisioningserver.drivers.power.ipmi import (
36 IPMI_CONFIG,
37 IPMIPowerDriver,
38)
39from provisioningserver.utils.shell import ExternalProcessError
40from testtools.matchers import (
41 Contains,
42 Equals,
43)
44
45
46def make_parameters():
47 power_address = factory.make_name('power_address')
48 power_user = factory.make_name('power_user')
49 power_pass = factory.make_name('power_pass')
50 power_driver = factory.make_name('power_driver')
51 power_off_mode = factory.make_name('power_off_mode')
52 ipmipower = factory.make_name('ipmipower')
53 ipmi_chassis_config = factory.make_name('ipmi_chassis_config')
54 params = {
55 'power_address': power_address,
56 'power_user': power_user,
57 'power_pass': power_pass,
58 'power_driver': power_driver,
59 'power_off_mode': power_off_mode,
60 'ipmipower': ipmipower,
61 'ipmi_chassis_config': ipmi_chassis_config,
62 }
63
64 return (
65 power_address, power_user, power_pass, power_driver,
66 power_off_mode, ipmipower, ipmi_chassis_config, params
67 )
68
69
70def make_ipmi_chassis_config_command(
71 ipmi_chassis_config, power_address, power_pass,
72 power_driver, power_user, tmp_config_name):
73 print(tmp_config_name)
74 return (
75 ipmi_chassis_config, '-W', 'opensesspriv', "--driver-type",
76 power_driver, '-h', power_address, '-u', power_user, '-p', power_pass,
77 '--commit', '--filename', tmp_config_name
78 )
79
80
81def make_ipmipower_command(
82 ipmipower, power_address, power_pass,
83 power_driver, power_user):
84 return (
85 ipmipower, '-W', 'opensesspriv', "--driver-type", power_driver, '-h',
86 power_address, '-u', power_user, '-p', power_pass
87 )
88
89
90class TestIPMIPowerDriver(MAASTestCase):
91
92 def test__finds_power_address_from_mac_address(self):
93 (power_address, power_user, power_pass, power_driver, power_off_mode,
94 ipmipower, ipmi_chassis_config, params) = make_parameters()
95 params['mac_address'] = factory.make_mac_address()
96 params['power_address'] = random.choice((None, "", " "))
97
98 ip_address = factory.make_ipv4_address()
99 find_ip_via_arp = self.patch(ipmi_module, 'find_ip_via_arp')
100 find_ip_via_arp.return_value = ip_address
101 power_change = random.choice(("on", "off"))
102
103 driver = IPMIPowerDriver()
104 self.patch_autospec(driver, "_issue_ipmi_chassis_config_command")
105 self.patch_autospec(driver, "_issue_ipmi_power_command")
106 driver._issue_ipmi_command(power_change, **params)
107
108 # The IP address is passed to _issue_ipmi_chassis_config_command.
109 self.assertThat(
110 driver._issue_ipmi_chassis_config_command,
111 MockCalledOnceWith(ANY, power_change, ip_address))
112 # The IP address is also within the command passed to
113 # _issue_ipmi_chassis_config_command.
114 self.assertThat(
115 driver._issue_ipmi_chassis_config_command.call_args[0],
116 Contains(ip_address))
117 # The IP address is passed to _issue_ipmi_power_command.
118 self.assertThat(
119 driver._issue_ipmi_power_command,
120 MockCalledOnceWith(ANY, power_change, ip_address))
121 # The IP address is also within the command passed to
122 # _issue_ipmi_power_command.
123 self.assertThat(
124 driver._issue_ipmi_power_command.call_args[0],
125 Contains(ip_address))
126
127 def test__chassis_config_written_to_temporary_file(self):
128 NamedTemporaryFile = self.patch(ipmi_module, "NamedTemporaryFile")
129 tmpfile = NamedTemporaryFile.return_value
130 tmpfile.__enter__.return_value = tmpfile
131 tmpfile.name = factory.make_name("filename")
132
133 IPMIPowerDriver._issue_ipmi_chassis_config_command(
134 ["true"], sentinel.change, sentinel.addr)
135
136 self.assertThat(NamedTemporaryFile, MockCalledOnceWith())
137 self.assertThat(tmpfile.__enter__, MockCalledOnceWith())
138 self.assertThat(tmpfile.write, MockCalledOnceWith(IPMI_CONFIG))
139 self.assertThat(tmpfile.flush, MockCalledOnceWith())
140 self.assertThat(tmpfile.__exit__, MockCalledOnceWith(None, None, None))
141
142 def test__issue_ipmi_command_issues_power_on(self):
143 (power_address, power_user, power_pass, power_driver, power_off_mode,
144 ipmipower, ipmi_chassis_config, params) = make_parameters()
145 ipmi_chassis_config_command = make_ipmi_chassis_config_command(
146 ipmi_chassis_config, power_address, power_pass, power_driver,
147 power_user, ANY)
148 ipmipower_command = make_ipmipower_command(
149 ipmipower, power_address, power_pass, power_driver, power_user)
150 ipmipower_command += ('--cycle', '--on-if-off')
151 ipmi_power_driver = IPMIPowerDriver()
152 popen_mock = self.patch(ipmi_module, 'Popen')
153 process = popen_mock.return_value
154 process.communicate.return_value = (None, '')
155 process.returncode = 0
156 call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
157 call_and_check_mock.return_value = 'on'
158
159 result = ipmi_power_driver._issue_ipmi_command('on', **params)
160
161 self.expectThat(
162 popen_mock, MockCalledOnceWith(
163 ipmi_chassis_config_command, stdout=PIPE, stderr=PIPE))
164 self.expectThat(
165 call_and_check_mock, MockCalledOnceWith(ipmipower_command))
166 self.expectThat(result, Equals('on'))
167
168 def test__issue_ipmi_command_issues_power_off(self):
169 (power_address, power_user, power_pass, power_driver, power_off_mode,
170 ipmipower, ipmi_chassis_config, params) = make_parameters()
171 ipmi_chassis_config_command = make_ipmi_chassis_config_command(
172 ipmi_chassis_config, power_address, power_pass, power_driver,
173 power_user, ANY)
174 ipmipower_command = make_ipmipower_command(
175 ipmipower, power_address, power_pass, power_driver, power_user)
176 ipmipower_command += ('--off', )
177 ipmi_power_driver = IPMIPowerDriver()
178 popen_mock = self.patch(ipmi_module, 'Popen')
179 process = popen_mock.return_value
180 process.communicate.return_value = (None, '')
181 process.returncode = 0
182 call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
183 call_and_check_mock.return_value = 'off'
184
185 result = ipmi_power_driver._issue_ipmi_command('off', **params)
186
187 self.expectThat(
188 popen_mock, MockCalledOnceWith(
189 ipmi_chassis_config_command, stdout=PIPE, stderr=PIPE))
190 self.expectThat(
191 call_and_check_mock, MockCalledOnceWith(ipmipower_command))
192 self.expectThat(result, Equals('off'))
193
194 def test__issue_ipmi_command_issues_power_off_soft_mode(self):
195 (power_address, power_user, power_pass, power_driver, power_off_mode,
196 ipmipower, ipmi_chassis_config, params) = make_parameters()
197 params['power_off_mode'] = 'soft'
198 ipmi_chassis_config_command = make_ipmi_chassis_config_command(
199 ipmi_chassis_config, power_address, power_pass, power_driver,
200 power_user, ANY)
201 ipmipower_command = make_ipmipower_command(
202 ipmipower, power_address, power_pass, power_driver, power_user)
203 ipmipower_command += ('--soft', )
204 ipmi_power_driver = IPMIPowerDriver()
205 popen_mock = self.patch(ipmi_module, 'Popen')
206 process = popen_mock.return_value
207 process.communicate.return_value = (None, '')
208 process.returncode = 0
209 call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
210 call_and_check_mock.return_value = 'off'
211
212 result = ipmi_power_driver._issue_ipmi_command('off', **params)
213
214 self.expectThat(
215 popen_mock, MockCalledOnceWith(
216 ipmi_chassis_config_command, stdout=PIPE, stderr=PIPE))
217 self.expectThat(
218 call_and_check_mock, MockCalledOnceWith(ipmipower_command))
219 self.expectThat(result, Equals('off'))
220
221 def test__issue_ipmi_command_issues_power_query(self):
222 (power_address, power_user, power_pass, power_driver, power_off_mode,
223 ipmipower, ipmi_chassis_config, params) = make_parameters()
224 ipmipower_command = make_ipmipower_command(
225 ipmipower, power_address, power_pass, power_driver, power_user)
226 ipmipower_command += ('--stat', )
227 ipmi_power_driver = IPMIPowerDriver()
228 popen_mock = self.patch(ipmi_module, 'Popen')
229 process = popen_mock.return_value
230 process.communicate.return_value = (None, '')
231 process.returncode = 0
232 call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
233 call_and_check_mock.return_value = 'other'
234
235 result = ipmi_power_driver._issue_ipmi_command('query', **params)
236
237 self.expectThat(popen_mock, MockNotCalled())
238 self.expectThat(
239 call_and_check_mock, MockCalledOnceWith(ipmipower_command))
240 self.expectThat(result, Equals('other'))
241
242 def test__issue_ipmi_command_issues_raises_power_auth_error(self):
243 _, _, _, _, _, _, _, params = make_parameters()
244 ipmi_power_driver = IPMIPowerDriver()
245 popen_mock = self.patch(ipmi_module, 'Popen')
246 process = popen_mock.return_value
247 process.communicate.return_value = (None, 'password invalid')
248 process.returncode = 0
249
250 self.assertRaises(
251 PowerAuthError, ipmi_power_driver._issue_ipmi_command,
252 'on', **params)
253
254 def test__issue_ipmi_command_issues_raises_power_fatal_error(self):
255 _, _, _, _, _, _, _, params = make_parameters()
256 ipmi_power_driver = IPMIPowerDriver()
257 popen_mock = self.patch(ipmi_module, 'Popen')
258 process = popen_mock.return_value
259 process.communicate.return_value = (None, '')
260 process.returncode = -1
261
262 self.assertRaises(
263 PowerFatalError, ipmi_power_driver._issue_ipmi_command,
264 'on', **params)
265
266 def test__issue_ipmi_command_issues_catches_external_process_error(self):
267 _, _, _, _, _, _, _, params = make_parameters()
268 ipmi_power_driver = IPMIPowerDriver()
269 popen_mock = self.patch(ipmi_module, 'Popen')
270 process = popen_mock.return_value
271 process.communicate.return_value = (None, '')
272 process.returncode = 0
273 call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
274 call_and_check_mock.side_effect = (
275 ExternalProcessError(1, "ipmipower something"))
276
277 self.assertRaises(
278 PowerFatalError, ipmi_power_driver._issue_ipmi_command,
279 'on', **params)
280
281 def test_power_on_calls__issue_ipmi_command(self):
282 _, _, _, _, _, _, _, params = make_parameters()
283 ipmi_power_driver = IPMIPowerDriver()
284 _issue_ipmi_command_mock = self.patch(
285 ipmi_power_driver, '_issue_ipmi_command')
286 system_id = factory.make_name('system_id')
287 ipmi_power_driver.power_on(system_id, **params)
288
289 self.assertThat(
290 _issue_ipmi_command_mock, MockCalledOnceWith('on', **params))
291
292 def test_power_off_calls__issue_ipmi_command(self):
293 _, _, _, _, _, _, _, params = make_parameters()
294 ipmi_power_driver = IPMIPowerDriver()
295 _issue_ipmi_command_mock = self.patch(
296 ipmi_power_driver, '_issue_ipmi_command')
297 system_id = factory.make_name('system_id')
298 ipmi_power_driver.power_off(system_id, **params)
299
300 self.assertThat(
301 _issue_ipmi_command_mock, MockCalledOnceWith('off', **params))
302
303 def test_power_query_calls__issue_ipmi_command(self):
304 _, _, _, _, _, _, _, params = make_parameters()
305 ipmi_power_driver = IPMIPowerDriver()
306 _issue_ipmi_command_mock = self.patch(
307 ipmi_power_driver, '_issue_ipmi_command')
308 system_id = factory.make_name('system_id')
309 ipmi_power_driver.power_query(system_id, **params)
310
311 self.assertThat(
312 _issue_ipmi_command_mock, MockCalledOnceWith('query', **params))
3130
=== modified file 'src/provisioningserver/power/tests/test_poweraction.py'
--- src/provisioningserver/power/tests/test_poweraction.py 2015-08-06 00:36:14 +0000
+++ src/provisioningserver/power/tests/test_poweraction.py 2015-08-17 16:53:58 +0000
@@ -202,6 +202,21 @@
202 locate_config('templates/power'),202 locate_config('templates/power'),
203 PowerAction(power_type).get_config_basedir())203 PowerAction(power_type).get_config_basedir())
204204
205 def test_ipmi_script_includes_config_dir(self):
206 conf_dir = factory.make_name('power_config_dir')
207 self.configure_power_config_dir(conf_dir)
208 action = PowerAction('ipmi')
209 script = action.render_template(
210 action.get_template(),
211 action.update_context(dict(
212 power_change='on', power_address='mystystem',
213 power_user='me', power_pass='me', ipmipower='echo',
214 ipmi_chassis_config='echo', config_dir='dir',
215 ipmi_config='file.conf', power_driver='LAN',
216 ip_address='', power_off_mode='hard')),
217 )
218 self.assertIn(conf_dir, script)
219
205 def test_moonshot_checks_state(self):220 def test_moonshot_checks_state(self):
206 # We can't test the moonshot template in detail (and it may be221 # We can't test the moonshot template in detail (and it may be
207 # customized), but by making it use "echo" instead of a real222 # customized), but by making it use "echo" instead of a real
@@ -223,7 +238,7 @@
223class TestTemplateContext(MAASTestCase):238class TestTemplateContext(MAASTestCase):
224239
225 def make_stubbed_power_action(self):240 def make_stubbed_power_action(self):
226 power_action = PowerAction("moonshot")241 power_action = PowerAction("ipmi")
227 render_template = self.patch(power_action, "render_template")242 render_template = self.patch(power_action, "render_template")
228 render_template.return_value = "echo done"243 render_template.return_value = "echo done"
229 return power_action244 return power_action