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
1=== added file 'etc/maas/templates/power/ipmi.conf'
2--- etc/maas/templates/power/ipmi.conf 1970-01-01 00:00:00 +0000
3+++ etc/maas/templates/power/ipmi.conf 2015-08-17 16:53:58 +0000
4@@ -0,0 +1,4 @@
5+Section Chassis_Boot_Flags
6+ Boot_Flags_Persistent No
7+ Boot_Device PXE
8+EndSection
9
10=== added file 'etc/maas/templates/power/ipmi.template'
11--- etc/maas/templates/power/ipmi.template 1970-01-01 00:00:00 +0000
12+++ etc/maas/templates/power/ipmi.template 2015-08-17 16:53:58 +0000
13@@ -0,0 +1,110 @@
14+# -*- mode: shell-script -*-
15+#
16+# Control a system via ipmipower
17+#
18+
19+# Parameters.
20+# If power_change is 'query', echo the current state of the
21+# machine: 'on' or 'off'.
22+power_change={{power_change}}
23+power_address={{power_address}}
24+power_user={{power_user}}
25+power_pass={{power_pass}}
26+power_driver={{power_driver}}
27+power_off_mode={{power_off_mode}}
28+ipmipower={{ipmipower}}
29+ipmi_chassis_config={{ipmi_chassis_config}}
30+config={{config_dir}}/{{ipmi_config}}
31+
32+# If ip_address was supplied and power_address is not explicitly set then
33+# use ip_address because it gets discovered on-the-fly based on mac_address.
34+# We don't want to use it unilaterally because mac_address may be the host's
35+# MAC, so only fall back if someone deliberately left power_address empty.
36+{{if ip_address and not power_address}}
37+power_address={{ip_address}}
38+{{endif}}
39+
40+# This workaround is required on many BMCs, and should have no impact
41+# on BMCs that don't require it.
42+# See https://bugs.launchpad.net/maas/+bug/1287964
43+workarounds="-W opensesspriv"
44+
45+# Determines the power command needed to execute the desired
46+# action. This function receives ${power_change} as argument.
47+formulate_power_command() {
48+ case $1 in
49+ 'on') echo '--cycle --on-if-off' ;;
50+ 'off')
51+ if [ "$power_off_mode" = "soft" ];
52+ then
53+ echo '--soft'
54+ else
55+ echo '--off'
56+ fi ;;
57+ 'query') echo '--stat' ;;
58+ *)
59+ echo "Got unknown power state from ipmipower: '$1'" >&2
60+ exit 1
61+ esac
62+}
63+
64+# Issue command to ipmipower, for the given system.
65+issue_ipmi_command() {
66+ # See https://launchpad.net/bugs/1053391 for details of this workaround
67+ local driver_option="" user_option=""
68+ if [ -n "$power_driver" ]
69+ then
70+ driver_option="--driver-type=${power_driver}"
71+ fi
72+ if [ -n "$power_user" ]
73+ then
74+ user_option="-u ${power_user}"
75+ fi
76+
77+ if [ "$power_change" != "query" ]
78+ then
79+ # Use C locale to force English error messages.
80+ result=$(echo workaround |\
81+ LC_ALL=C ${ipmi_chassis_config} ${workarounds} ${driver_option} -h ${power_address} ${user_option} -p ${power_pass} --commit --filename ${config} 2>&1)
82+
83+ if echo $result | grep -q "password invalid"
84+ then
85+ echo "Invalid password" >&2
86+ exit 2
87+ fi
88+ fi
89+
90+ # Use C locale to force English error messages.
91+ result=$(echo workaround |\
92+ LC_ALL=C ${ipmipower} ${workarounds} ${driver_option} -h ${power_address} ${user_option} -p ${power_pass} "$@")
93+
94+ exit_status=$?
95+
96+ if echo $result | grep -q "password invalid"
97+ then
98+ echo "Invalid password" >&2
99+ exit 2
100+ fi
101+
102+ if [ $exit_status -ne 0 ]; then
103+ echo $result >&2
104+ exit 2
105+ fi
106+
107+ case "$result" in
108+ *:* )
109+ # Result looks like the usual IPMI output:
110+ # <ipmi-ip-address>: <on/off>, just return the <on|off>
111+ # part.
112+ echo ${result} | cut -d ':' -f 2
113+ ;;
114+ * )
115+ echo ${result};;
116+ esac
117+}
118+
119+# This script deliberately does not check the current power state
120+# before issuing the requested power command. See bug 1171418 for an
121+# explanation.
122+power_command=$(formulate_power_command ${power_change})
123+issue_ipmi_command ${power_command}
124
125=== modified file 'src/provisioningserver/drivers/power/__init__.py'
126--- src/provisioningserver/drivers/power/__init__.py 2015-08-06 00:36:14 +0000
127+++ src/provisioningserver/drivers/power/__init__.py 2015-08-17 16:53:58 +0000
128@@ -334,7 +334,6 @@
129
130 from provisioningserver.drivers.power.apc import APCPowerDriver
131 from provisioningserver.drivers.power.hmc import HMCPowerDriver
132-from provisioningserver.drivers.power.ipmi import IPMIPowerDriver
133 from provisioningserver.drivers.power.msftocs import MicrosoftOCSPowerDriver
134 from provisioningserver.drivers.power.mscm import MSCMPowerDriver
135 from provisioningserver.drivers.power.seamicro import SeaMicroPowerDriver
136@@ -345,7 +344,6 @@
137 builtin_power_drivers = [
138 APCPowerDriver(),
139 HMCPowerDriver(),
140- IPMIPowerDriver(),
141 MicrosoftOCSPowerDriver(),
142 MSCMPowerDriver(),
143 SeaMicroPowerDriver(),
144
145=== removed file 'src/provisioningserver/drivers/power/ipmi.py'
146--- src/provisioningserver/drivers/power/ipmi.py 2015-08-11 01:13:20 +0000
147+++ src/provisioningserver/drivers/power/ipmi.py 1970-01-01 00:00:00 +0000
148@@ -1,154 +0,0 @@
149-# Copyright 2015 Canonical Ltd. This software is licensed under the
150-# GNU Affero General Public License version 3 (see the file LICENSE).
151-
152-"""IPMI Power Driver."""
153-
154-from __future__ import (
155- absolute_import,
156- print_function,
157- unicode_literals,
158- )
159-
160-str = None
161-
162-__metaclass__ = type
163-__all__ = []
164-
165-from subprocess import (
166- PIPE,
167- Popen,
168-)
169-from tempfile import NamedTemporaryFile
170-
171-from provisioningserver.drivers.power import (
172- PowerAuthError,
173- PowerDriver,
174- PowerFatalError,
175-)
176-from provisioningserver.utils.network import find_ip_via_arp
177-from provisioningserver.utils.shell import (
178- call_and_check,
179- ExternalProcessError,
180-)
181-
182-
183-IPMI_CONFIG = """\
184-Section Chassis_Boot_Flags
185- Boot_Flags_Persistent No
186- Boot_Device PXE
187-EndSection
188-"""
189-
190-
191-def is_set(setting):
192- return not (setting is None or setting == "" or setting.isspace())
193-
194-
195-class IPMIPowerDriver(PowerDriver):
196-
197- name = 'ipmi'
198- description = "IPMI Power Driver."
199- settings = []
200-
201- @staticmethod
202- def _issue_ipmi_chassis_config_command(command, change, address):
203- with NamedTemporaryFile() as tmp_config:
204- # Write out the chassis configuration.
205- tmp_config.write(IPMI_CONFIG)
206- tmp_config.flush()
207- # Use it when running the chassis config command.
208- # XXX: Not using call_and_check here because we
209- # need to check stderr.
210- command = tuple(command) + ("--filename", tmp_config.name)
211- process = Popen(command, stdout=PIPE, stderr=PIPE)
212- stdout, stderr = process.communicate()
213- stderr = stderr.strip()
214- if "password invalid" in stderr:
215- raise PowerAuthError("Invalid password.")
216- if process.returncode != 0:
217- raise PowerFatalError(
218- "Failed to power %s %s: %s" % (change, address, stderr))
219-
220- @staticmethod
221- def _issue_ipmi_power_command(command, change, address):
222- command = tuple(command) # For consistency when testing.
223- try:
224- output = call_and_check(command)
225- except ExternalProcessError as e:
226- raise PowerFatalError(
227- "Failed to power %s %s: %s" % (
228- change, address, e.output_as_unicode))
229- else:
230- if 'on' in output:
231- return 'on'
232- elif 'off' in output:
233- return 'off'
234- else:
235- return output
236-
237- def _issue_ipmi_command(
238- self, power_change, power_address=None, power_user=None,
239- power_pass=None, power_driver=None, power_off_mode=None,
240- ipmipower=None, ipmi_chassis_config=None, mac_address=None,
241- **extra):
242- """Issue command to ipmipower, for the given system."""
243- # This script deliberately does not check the current power state
244- # before issuing the requested power command. See bug 1171418 for an
245- # explanation.
246-
247- if is_set(mac_address) and not is_set(power_address):
248- power_address = find_ip_via_arp(mac_address)
249-
250- # The `-W opensesspriv` workaround is required on many BMCs, and
251- # should have no impact on BMCs that don't require it.
252- # See https://bugs.launchpad.net/maas/+bug/1287964
253- ipmi_chassis_config_command = [
254- ipmi_chassis_config, '-W', 'opensesspriv']
255- ipmipower_command = [
256- ipmipower, '-W', 'opensesspriv']
257-
258- # Arguments in common between chassis config and power control. See
259- # https://launchpad.net/bugs/1053391 for details of modifying the
260- # command for power_driver and power_user.
261- common_args = []
262- if is_set(power_driver):
263- common_args.extend(("--driver-type", power_driver))
264- common_args.extend(('-h', power_address))
265- if is_set(power_user):
266- common_args.extend(("-u", power_user))
267- common_args.extend(('-p', power_pass))
268-
269- # Update the chassis config and power commands.
270- ipmi_chassis_config_command.extend(common_args)
271- ipmi_chassis_config_command.append('--commit')
272- ipmipower_command.extend(common_args)
273-
274- # Before changing state run the chassis config command.
275- if power_change in ("on", "off"):
276- self._issue_ipmi_chassis_config_command(
277- ipmi_chassis_config_command, power_change, power_address)
278-
279- # Additional arguments for the power command.
280- if power_change == 'on':
281- ipmipower_command.append('--cycle')
282- ipmipower_command.append('--on-if-off')
283- elif power_change == 'off':
284- if power_off_mode == 'soft':
285- ipmipower_command.append('--soft')
286- else:
287- ipmipower_command.append('--off')
288- elif power_change == 'query':
289- ipmipower_command.append('--stat')
290-
291- # Update or query the power state.
292- return self._issue_ipmi_power_command(
293- ipmipower_command, power_change, power_address)
294-
295- def power_on(self, system_id, **kwargs):
296- self._issue_ipmi_command('on', **kwargs)
297-
298- def power_off(self, system_id, **kwargs):
299- self._issue_ipmi_command('off', **kwargs)
300-
301- def power_query(self, system_id, **kwargs):
302- return self._issue_ipmi_command('query', **kwargs)
303
304=== removed file 'src/provisioningserver/drivers/power/tests/test_ipmi.py'
305--- src/provisioningserver/drivers/power/tests/test_ipmi.py 2015-08-11 03:56:48 +0000
306+++ src/provisioningserver/drivers/power/tests/test_ipmi.py 1970-01-01 00:00:00 +0000
307@@ -1,312 +0,0 @@
308-# Copyright 2015 Canonical Ltd. This software is licensed under the
309-# GNU Affero General Public License version 3 (see the file LICENSE).
310-
311-"""Tests for `provisioningserver.drivers.power.ipmi`."""
312-
313-from __future__ import (
314- absolute_import,
315- print_function,
316- unicode_literals,
317- )
318-
319-str = None
320-
321-__metaclass__ = type
322-__all__ = []
323-
324-import random
325-from subprocess import PIPE
326-
327-from maastesting.factory import factory
328-from maastesting.matchers import (
329- MockCalledOnceWith,
330- MockNotCalled,
331-)
332-from maastesting.testcase import MAASTestCase
333-from mock import (
334- ANY,
335- sentinel,
336-)
337-from provisioningserver.drivers.power import (
338- ipmi as ipmi_module,
339- PowerAuthError,
340- PowerFatalError,
341-)
342-from provisioningserver.drivers.power.ipmi import (
343- IPMI_CONFIG,
344- IPMIPowerDriver,
345-)
346-from provisioningserver.utils.shell import ExternalProcessError
347-from testtools.matchers import (
348- Contains,
349- Equals,
350-)
351-
352-
353-def make_parameters():
354- power_address = factory.make_name('power_address')
355- power_user = factory.make_name('power_user')
356- power_pass = factory.make_name('power_pass')
357- power_driver = factory.make_name('power_driver')
358- power_off_mode = factory.make_name('power_off_mode')
359- ipmipower = factory.make_name('ipmipower')
360- ipmi_chassis_config = factory.make_name('ipmi_chassis_config')
361- params = {
362- 'power_address': power_address,
363- 'power_user': power_user,
364- 'power_pass': power_pass,
365- 'power_driver': power_driver,
366- 'power_off_mode': power_off_mode,
367- 'ipmipower': ipmipower,
368- 'ipmi_chassis_config': ipmi_chassis_config,
369- }
370-
371- return (
372- power_address, power_user, power_pass, power_driver,
373- power_off_mode, ipmipower, ipmi_chassis_config, params
374- )
375-
376-
377-def make_ipmi_chassis_config_command(
378- ipmi_chassis_config, power_address, power_pass,
379- power_driver, power_user, tmp_config_name):
380- print(tmp_config_name)
381- return (
382- ipmi_chassis_config, '-W', 'opensesspriv', "--driver-type",
383- power_driver, '-h', power_address, '-u', power_user, '-p', power_pass,
384- '--commit', '--filename', tmp_config_name
385- )
386-
387-
388-def make_ipmipower_command(
389- ipmipower, power_address, power_pass,
390- power_driver, power_user):
391- return (
392- ipmipower, '-W', 'opensesspriv', "--driver-type", power_driver, '-h',
393- power_address, '-u', power_user, '-p', power_pass
394- )
395-
396-
397-class TestIPMIPowerDriver(MAASTestCase):
398-
399- def test__finds_power_address_from_mac_address(self):
400- (power_address, power_user, power_pass, power_driver, power_off_mode,
401- ipmipower, ipmi_chassis_config, params) = make_parameters()
402- params['mac_address'] = factory.make_mac_address()
403- params['power_address'] = random.choice((None, "", " "))
404-
405- ip_address = factory.make_ipv4_address()
406- find_ip_via_arp = self.patch(ipmi_module, 'find_ip_via_arp')
407- find_ip_via_arp.return_value = ip_address
408- power_change = random.choice(("on", "off"))
409-
410- driver = IPMIPowerDriver()
411- self.patch_autospec(driver, "_issue_ipmi_chassis_config_command")
412- self.patch_autospec(driver, "_issue_ipmi_power_command")
413- driver._issue_ipmi_command(power_change, **params)
414-
415- # The IP address is passed to _issue_ipmi_chassis_config_command.
416- self.assertThat(
417- driver._issue_ipmi_chassis_config_command,
418- MockCalledOnceWith(ANY, power_change, ip_address))
419- # The IP address is also within the command passed to
420- # _issue_ipmi_chassis_config_command.
421- self.assertThat(
422- driver._issue_ipmi_chassis_config_command.call_args[0],
423- Contains(ip_address))
424- # The IP address is passed to _issue_ipmi_power_command.
425- self.assertThat(
426- driver._issue_ipmi_power_command,
427- MockCalledOnceWith(ANY, power_change, ip_address))
428- # The IP address is also within the command passed to
429- # _issue_ipmi_power_command.
430- self.assertThat(
431- driver._issue_ipmi_power_command.call_args[0],
432- Contains(ip_address))
433-
434- def test__chassis_config_written_to_temporary_file(self):
435- NamedTemporaryFile = self.patch(ipmi_module, "NamedTemporaryFile")
436- tmpfile = NamedTemporaryFile.return_value
437- tmpfile.__enter__.return_value = tmpfile
438- tmpfile.name = factory.make_name("filename")
439-
440- IPMIPowerDriver._issue_ipmi_chassis_config_command(
441- ["true"], sentinel.change, sentinel.addr)
442-
443- self.assertThat(NamedTemporaryFile, MockCalledOnceWith())
444- self.assertThat(tmpfile.__enter__, MockCalledOnceWith())
445- self.assertThat(tmpfile.write, MockCalledOnceWith(IPMI_CONFIG))
446- self.assertThat(tmpfile.flush, MockCalledOnceWith())
447- self.assertThat(tmpfile.__exit__, MockCalledOnceWith(None, None, None))
448-
449- def test__issue_ipmi_command_issues_power_on(self):
450- (power_address, power_user, power_pass, power_driver, power_off_mode,
451- ipmipower, ipmi_chassis_config, params) = make_parameters()
452- ipmi_chassis_config_command = make_ipmi_chassis_config_command(
453- ipmi_chassis_config, power_address, power_pass, power_driver,
454- power_user, ANY)
455- ipmipower_command = make_ipmipower_command(
456- ipmipower, power_address, power_pass, power_driver, power_user)
457- ipmipower_command += ('--cycle', '--on-if-off')
458- ipmi_power_driver = IPMIPowerDriver()
459- popen_mock = self.patch(ipmi_module, 'Popen')
460- process = popen_mock.return_value
461- process.communicate.return_value = (None, '')
462- process.returncode = 0
463- call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
464- call_and_check_mock.return_value = 'on'
465-
466- result = ipmi_power_driver._issue_ipmi_command('on', **params)
467-
468- self.expectThat(
469- popen_mock, MockCalledOnceWith(
470- ipmi_chassis_config_command, stdout=PIPE, stderr=PIPE))
471- self.expectThat(
472- call_and_check_mock, MockCalledOnceWith(ipmipower_command))
473- self.expectThat(result, Equals('on'))
474-
475- def test__issue_ipmi_command_issues_power_off(self):
476- (power_address, power_user, power_pass, power_driver, power_off_mode,
477- ipmipower, ipmi_chassis_config, params) = make_parameters()
478- ipmi_chassis_config_command = make_ipmi_chassis_config_command(
479- ipmi_chassis_config, power_address, power_pass, power_driver,
480- power_user, ANY)
481- ipmipower_command = make_ipmipower_command(
482- ipmipower, power_address, power_pass, power_driver, power_user)
483- ipmipower_command += ('--off', )
484- ipmi_power_driver = IPMIPowerDriver()
485- popen_mock = self.patch(ipmi_module, 'Popen')
486- process = popen_mock.return_value
487- process.communicate.return_value = (None, '')
488- process.returncode = 0
489- call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
490- call_and_check_mock.return_value = 'off'
491-
492- result = ipmi_power_driver._issue_ipmi_command('off', **params)
493-
494- self.expectThat(
495- popen_mock, MockCalledOnceWith(
496- ipmi_chassis_config_command, stdout=PIPE, stderr=PIPE))
497- self.expectThat(
498- call_and_check_mock, MockCalledOnceWith(ipmipower_command))
499- self.expectThat(result, Equals('off'))
500-
501- def test__issue_ipmi_command_issues_power_off_soft_mode(self):
502- (power_address, power_user, power_pass, power_driver, power_off_mode,
503- ipmipower, ipmi_chassis_config, params) = make_parameters()
504- params['power_off_mode'] = 'soft'
505- ipmi_chassis_config_command = make_ipmi_chassis_config_command(
506- ipmi_chassis_config, power_address, power_pass, power_driver,
507- power_user, ANY)
508- ipmipower_command = make_ipmipower_command(
509- ipmipower, power_address, power_pass, power_driver, power_user)
510- ipmipower_command += ('--soft', )
511- ipmi_power_driver = IPMIPowerDriver()
512- popen_mock = self.patch(ipmi_module, 'Popen')
513- process = popen_mock.return_value
514- process.communicate.return_value = (None, '')
515- process.returncode = 0
516- call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
517- call_and_check_mock.return_value = 'off'
518-
519- result = ipmi_power_driver._issue_ipmi_command('off', **params)
520-
521- self.expectThat(
522- popen_mock, MockCalledOnceWith(
523- ipmi_chassis_config_command, stdout=PIPE, stderr=PIPE))
524- self.expectThat(
525- call_and_check_mock, MockCalledOnceWith(ipmipower_command))
526- self.expectThat(result, Equals('off'))
527-
528- def test__issue_ipmi_command_issues_power_query(self):
529- (power_address, power_user, power_pass, power_driver, power_off_mode,
530- ipmipower, ipmi_chassis_config, params) = make_parameters()
531- ipmipower_command = make_ipmipower_command(
532- ipmipower, power_address, power_pass, power_driver, power_user)
533- ipmipower_command += ('--stat', )
534- ipmi_power_driver = IPMIPowerDriver()
535- popen_mock = self.patch(ipmi_module, 'Popen')
536- process = popen_mock.return_value
537- process.communicate.return_value = (None, '')
538- process.returncode = 0
539- call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
540- call_and_check_mock.return_value = 'other'
541-
542- result = ipmi_power_driver._issue_ipmi_command('query', **params)
543-
544- self.expectThat(popen_mock, MockNotCalled())
545- self.expectThat(
546- call_and_check_mock, MockCalledOnceWith(ipmipower_command))
547- self.expectThat(result, Equals('other'))
548-
549- def test__issue_ipmi_command_issues_raises_power_auth_error(self):
550- _, _, _, _, _, _, _, params = make_parameters()
551- ipmi_power_driver = IPMIPowerDriver()
552- popen_mock = self.patch(ipmi_module, 'Popen')
553- process = popen_mock.return_value
554- process.communicate.return_value = (None, 'password invalid')
555- process.returncode = 0
556-
557- self.assertRaises(
558- PowerAuthError, ipmi_power_driver._issue_ipmi_command,
559- 'on', **params)
560-
561- def test__issue_ipmi_command_issues_raises_power_fatal_error(self):
562- _, _, _, _, _, _, _, params = make_parameters()
563- ipmi_power_driver = IPMIPowerDriver()
564- popen_mock = self.patch(ipmi_module, 'Popen')
565- process = popen_mock.return_value
566- process.communicate.return_value = (None, '')
567- process.returncode = -1
568-
569- self.assertRaises(
570- PowerFatalError, ipmi_power_driver._issue_ipmi_command,
571- 'on', **params)
572-
573- def test__issue_ipmi_command_issues_catches_external_process_error(self):
574- _, _, _, _, _, _, _, params = make_parameters()
575- ipmi_power_driver = IPMIPowerDriver()
576- popen_mock = self.patch(ipmi_module, 'Popen')
577- process = popen_mock.return_value
578- process.communicate.return_value = (None, '')
579- process.returncode = 0
580- call_and_check_mock = self.patch(ipmi_module, 'call_and_check')
581- call_and_check_mock.side_effect = (
582- ExternalProcessError(1, "ipmipower something"))
583-
584- self.assertRaises(
585- PowerFatalError, ipmi_power_driver._issue_ipmi_command,
586- 'on', **params)
587-
588- def test_power_on_calls__issue_ipmi_command(self):
589- _, _, _, _, _, _, _, params = make_parameters()
590- ipmi_power_driver = IPMIPowerDriver()
591- _issue_ipmi_command_mock = self.patch(
592- ipmi_power_driver, '_issue_ipmi_command')
593- system_id = factory.make_name('system_id')
594- ipmi_power_driver.power_on(system_id, **params)
595-
596- self.assertThat(
597- _issue_ipmi_command_mock, MockCalledOnceWith('on', **params))
598-
599- def test_power_off_calls__issue_ipmi_command(self):
600- _, _, _, _, _, _, _, params = make_parameters()
601- ipmi_power_driver = IPMIPowerDriver()
602- _issue_ipmi_command_mock = self.patch(
603- ipmi_power_driver, '_issue_ipmi_command')
604- system_id = factory.make_name('system_id')
605- ipmi_power_driver.power_off(system_id, **params)
606-
607- self.assertThat(
608- _issue_ipmi_command_mock, MockCalledOnceWith('off', **params))
609-
610- def test_power_query_calls__issue_ipmi_command(self):
611- _, _, _, _, _, _, _, params = make_parameters()
612- ipmi_power_driver = IPMIPowerDriver()
613- _issue_ipmi_command_mock = self.patch(
614- ipmi_power_driver, '_issue_ipmi_command')
615- system_id = factory.make_name('system_id')
616- ipmi_power_driver.power_query(system_id, **params)
617-
618- self.assertThat(
619- _issue_ipmi_command_mock, MockCalledOnceWith('query', **params))
620
621=== modified file 'src/provisioningserver/power/tests/test_poweraction.py'
622--- src/provisioningserver/power/tests/test_poweraction.py 2015-08-06 00:36:14 +0000
623+++ src/provisioningserver/power/tests/test_poweraction.py 2015-08-17 16:53:58 +0000
624@@ -202,6 +202,21 @@
625 locate_config('templates/power'),
626 PowerAction(power_type).get_config_basedir())
627
628+ def test_ipmi_script_includes_config_dir(self):
629+ conf_dir = factory.make_name('power_config_dir')
630+ self.configure_power_config_dir(conf_dir)
631+ action = PowerAction('ipmi')
632+ script = action.render_template(
633+ action.get_template(),
634+ action.update_context(dict(
635+ power_change='on', power_address='mystystem',
636+ power_user='me', power_pass='me', ipmipower='echo',
637+ ipmi_chassis_config='echo', config_dir='dir',
638+ ipmi_config='file.conf', power_driver='LAN',
639+ ip_address='', power_off_mode='hard')),
640+ )
641+ self.assertIn(conf_dir, script)
642+
643 def test_moonshot_checks_state(self):
644 # We can't test the moonshot template in detail (and it may be
645 # customized), but by making it use "echo" instead of a real
646@@ -223,7 +238,7 @@
647 class TestTemplateContext(MAASTestCase):
648
649 def make_stubbed_power_action(self):
650- power_action = PowerAction("moonshot")
651+ power_action = PowerAction("ipmi")
652 render_template = self.patch(power_action, "render_template")
653 render_template.return_value = "echo done"
654 return power_action