Merge lp:~allenap/maas/revert-ipmi-power-driver into lp:~maas-committers/maas/trunk
- revert-ipmi-power-driver
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
Description of the change
MAAS Lander (maas-lander) wrote : | # |
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://
Get:1 http://
Get:2 http://
Ign http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Get:6 http://
Hit http://
Get:7 http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:8 http://
Hit http://
Get:9 http://
Hit http://
Hit http://
Get:10 http://
Get:11 http://
Get:12 http://
Hit http://
Hit http://
Ign http://
Ign http://
Fetched 1,961 kB in 3s (568 kB/s)
Reading package lists...
sudo DEBIAN_
--
Preview Diff
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 |
lgtm!