Merge lp:~newell-jensen/maas/1.9-fix-1586499 into lp:maas/1.9

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: no longer in the source branch.
Merged at revision: 4584
Proposed branch: lp:~newell-jensen/maas/1.9-fix-1586499
Merge into: lp:maas/1.9
Diff against target: 286 lines (+94/-88)
3 files modified
src/provisioningserver/drivers/power/moonshot.py (+26/-17)
src/provisioningserver/drivers/power/tests/test_moonshot.py (+67/-71)
src/provisioningserver/power/__init__.py (+1/-0)
To merge this branch: bzr merge lp:~newell-jensen/maas/1.9-fix-1586499
Reviewer Review Type Date Requested Status
Newell Jensen (community) Approve
Review via email: mp+296162@code.launchpad.net

Commit message

Hand picked backport trunk r5059: Update HP Moonshot iLO4 (IPMI) power driver to set PXE booting and also make it a queryable power type.

To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Self approved backport (minimal code changes due to merge conflicts).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/drivers/power/moonshot.py'
2--- src/provisioningserver/drivers/power/moonshot.py 2016-05-25 17:04:33 +0000
3+++ src/provisioningserver/drivers/power/moonshot.py 2016-06-01 04:55:57 +0000
4@@ -15,10 +15,12 @@
5 __all__ = []
6
7
8+import os
9+import re
10+
11 from provisioningserver.drivers.power import (
12 PowerActionError,
13 PowerDriver,
14- PowerFatalError,
15 )
16 from provisioningserver.utils import shell
17 from provisioningserver.utils.shell import (
18@@ -39,28 +41,35 @@
19 return []
20
21 def _issue_ipmitool_command(
22- self, power_change, power_hwaddress=None, power_address=None,
23- power_user=None, power_pass=None, ipmitool=None, **extra):
24+ self, power_change, ipmitool=None, power_address=None,
25+ power_user=None, power_pass=None, power_hwaddress=None, **extra):
26+ """Issue ipmitool command for HP Moonshot cartridge."""
27+ env = os.environ.copy()
28+ env['LC_ALL'] = 'C'
29 command = (
30- ipmitool, '-I', 'lanplus', '-H', power_address, '-U', power_user,
31- '-P', power_pass, power_hwaddress, 'power', power_change
32- )
33+ ipmitool, '-I', 'lanplus', '-H', power_address,
34+ '-U', power_user, '-P', power_pass
35+ ) + tuple(power_hwaddress.split())
36+ if power_change == 'pxe':
37+ command += ('chassis', 'bootdev', 'pxe')
38+ else:
39+ command += ('power', power_change)
40 try:
41- output = call_and_check(command)
42+ stdout = call_and_check(command, env=env)
43+ stdout = stdout.decode('utf-8')
44 except ExternalProcessError as e:
45- raise PowerFatalError(
46- "Failed to power %s %s: %s" % (
47- power_change, power_hwaddress, e.output_as_unicode))
48+ raise PowerActionError(
49+ "Failed to execute %s for cartridge %s at %s: %s" % (
50+ command, power_hwaddress,
51+ power_address, e.output_as_unicode))
52 else:
53- if 'on' in output:
54- return 'on'
55- elif 'off' in output:
56- return 'off'
57- else:
58- raise PowerActionError(
59- "Got unknown power state from ipmipower: %s" % output)
60+ # Return output if power query
61+ if power_change == 'status':
62+ match = re.search(r'\b(on|off)\b$', stdout)
63+ return stdout if match is None else match.group(0)
64
65 def power_on(self, system_id, context):
66+ self._issue_ipmitool_command('pxe', **context)
67 self._issue_ipmitool_command('on', **context)
68
69 def power_off(self, system_id, context):
70
71=== modified file 'src/provisioningserver/drivers/power/tests/test_moonshot.py'
72--- src/provisioningserver/drivers/power/tests/test_moonshot.py 2016-05-25 17:04:33 +0000
73+++ src/provisioningserver/drivers/power/tests/test_moonshot.py 2016-06-01 04:55:57 +0000
74@@ -1,26 +1,23 @@
75-# Copyright 2015 Canonical Ltd. This software is licensed under the
76+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
77 # GNU Affero General Public License version 3 (see the file LICENSE).
78
79 """Tests for `provisioningserver.drivers.power.ipmi`."""
80
81-from __future__ import (
82- absolute_import,
83- print_function,
84- unicode_literals,
85- )
86-
87-str = None
88-
89-__metaclass__ = type
90 __all__ = []
91
92+
93+import os
94+
95 from maastesting.factory import factory
96-from maastesting.matchers import MockCalledOnceWith
97+from maastesting.matchers import (
98+ MockCalledOnceWith,
99+ MockCallsMatch,
100+)
101 from maastesting.testcase import MAASTestCase
102+from mock import call
103 from provisioningserver.drivers.power import (
104 moonshot as moonshot_module,
105 PowerActionError,
106- PowerFatalError,
107 )
108 from provisioningserver.drivers.power.moonshot import MoonshotIPMIPowerDriver
109 from provisioningserver.utils.shell import (
110@@ -30,23 +27,37 @@
111 from testtools.matchers import Equals
112
113
114-def make_parameters():
115+def make_context():
116 return {
117- 'power_hwaddress': factory.make_name('power_hwaddress'),
118+ 'ipmitool': factory.make_name('ipmitool'),
119 'power_address': factory.make_name('power_address'),
120 'power_user': factory.make_name('power_user'),
121 'power_pass': factory.make_name('power_pass'),
122- 'ipmitool': factory.make_name('ipmitool'),
123+ 'power_hwaddress': factory.make_string(spaces=True),
124 }
125
126
127-def make_ipmitool_command(
128- power_change, power_hwaddress, power_address,
129- power_user, power_pass, ipmitool):
130+def make_command(
131+ ipmitool, power_address, power_user, power_pass,
132+ power_hwaddress):
133 return (
134 ipmitool, '-I', 'lanplus', '-H', power_address, '-U', power_user,
135- '-P', power_pass, power_hwaddress, 'power', power_change
136- )
137+ '-P', power_pass
138+ ) + tuple(power_hwaddress.split())
139+
140+
141+def make_pxe_command(context):
142+ return make_command(
143+ context['ipmitool'], context['power_address'], context['power_user'],
144+ context['power_pass'], context['power_hwaddress']
145+ ) + ('chassis', 'bootdev', 'pxe')
146+
147+
148+def make_ipmitool_command(power_change, context):
149+ return make_command(
150+ context['ipmitool'], context['power_address'], context['power_user'],
151+ context['power_pass'], context['power_hwaddress']
152+ ) + ('power', power_change)
153
154
155 class TestMoonshotIPMIPowerDriver(MAASTestCase):
156@@ -65,63 +76,47 @@
157 missing = driver.detect_missing_packages()
158 self.assertItemsEqual([], missing)
159
160- def test__issue_ipmitool_command_issues_power_on(self):
161- context = make_parameters()
162- power_change = 'on'
163- ipmitool_command = make_ipmitool_command(power_change, **context)
164- moonshot_driver = MoonshotIPMIPowerDriver()
165- call_and_check_mock = self.patch(moonshot_module, 'call_and_check')
166- call_and_check_mock.return_value = power_change
167-
168- result = moonshot_driver._issue_ipmitool_command(
169- power_change, **context)
170-
171- self.expectThat(
172- call_and_check_mock, MockCalledOnceWith(ipmitool_command))
173- self.expectThat(result, Equals(power_change))
174-
175- def test__issue_ipmitool_command_issues_power_off(self):
176- context = make_parameters()
177- power_change = 'off'
178- ipmitool_command = make_ipmitool_command(power_change, **context)
179- moonshot_driver = MoonshotIPMIPowerDriver()
180- call_and_check_mock = self.patch(moonshot_module, 'call_and_check')
181- call_and_check_mock.return_value = power_change
182-
183- result = moonshot_driver._issue_ipmitool_command(
184- power_change, **context)
185-
186- self.expectThat(
187- call_and_check_mock, MockCalledOnceWith(ipmitool_command))
188- self.expectThat(result, Equals(power_change))
189-
190- def test__issue_ipmitool_command_raises_power_action_error(self):
191- context = make_parameters()
192- power_change = 'other'
193- ipmitool_command = make_ipmitool_command(power_change, **context)
194- moonshot_driver = MoonshotIPMIPowerDriver()
195- call_and_check_mock = self.patch(moonshot_module, 'call_and_check')
196- call_and_check_mock.return_value = power_change
197-
198- self.assertRaises(
199- PowerActionError, moonshot_driver._issue_ipmitool_command,
200- power_change, **context)
201- self.expectThat(
202- call_and_check_mock, MockCalledOnceWith(ipmitool_command))
203-
204- def test__issue_ipmitool_raises_power_fatal_error(self):
205- context = make_parameters()
206+ def test__issue_ipmitool_command_sets_pxe_boot(self):
207+ context = make_context()
208+ env = os.environ.copy()
209+ env['LC_ALL'] = 'C'
210+ pxe_command = make_pxe_command(context)
211+ moonshot_driver = MoonshotIPMIPowerDriver()
212+ call_and_check_mock = self.patch(moonshot_module, 'call_and_check')
213+
214+ moonshot_driver._issue_ipmitool_command('pxe', **context)
215+
216+ self.assertThat(
217+ call_and_check_mock, MockCalledOnceWith(pxe_command, env=env))
218+
219+ def test__issue_ipmitool_command_returns_stdout_if_no_match(self):
220+ context = make_context()
221+ env = os.environ.copy()
222+ env['LC_ALL'] = 'C'
223+ ipmitool_command = make_ipmitool_command('status', context)
224+ moonshot_driver = MoonshotIPMIPowerDriver()
225+ call_and_check_mock = self.patch(moonshot_module, 'call_and_check')
226+ call_and_check_mock.return_value = b'other'
227+
228+ result = moonshot_driver._issue_ipmitool_command('status', **context)
229+
230+ self.expectThat(
231+ call_and_check_mock, MockCalledOnceWith(ipmitool_command, env=env))
232+ self.expectThat(result, Equals('other'))
233+
234+ def test__issue_ipmitool_raises_power_action_error(self):
235+ context = make_context()
236 moonshot_driver = MoonshotIPMIPowerDriver()
237 call_and_check_mock = self.patch(moonshot_module, 'call_and_check')
238 call_and_check_mock.side_effect = (
239 ExternalProcessError(1, "ipmitool something"))
240
241 self.assertRaises(
242- PowerFatalError, moonshot_driver._issue_ipmitool_command,
243+ PowerActionError, moonshot_driver._issue_ipmitool_command,
244 'status', **context)
245
246 def test_power_on_calls__issue_ipmitool_command(self):
247- context = make_parameters()
248+ context = make_context()
249 moonshot_driver = MoonshotIPMIPowerDriver()
250 _issue_ipmitool_command_mock = self.patch(
251 moonshot_driver, '_issue_ipmitool_command')
252@@ -129,10 +124,11 @@
253 moonshot_driver.power_on(system_id, context)
254
255 self.assertThat(
256- _issue_ipmitool_command_mock, MockCalledOnceWith('on', **context))
257+ _issue_ipmitool_command_mock, MockCallsMatch(
258+ call('pxe', **context), call('on', **context)))
259
260 def test_power_off_calls__issue_ipmitool_command(self):
261- context = make_parameters()
262+ context = make_context()
263 moonshot_driver = MoonshotIPMIPowerDriver()
264 _issue_ipmitool_command_mock = self.patch(
265 moonshot_driver, '_issue_ipmitool_command')
266@@ -143,7 +139,7 @@
267 _issue_ipmitool_command_mock, MockCalledOnceWith('off', **context))
268
269 def test_power_query_calls__issue_ipmitool_command(self):
270- context = make_parameters()
271+ context = make_context()
272 moonshot_driver = MoonshotIPMIPowerDriver()
273 _issue_ipmitool_command_mock = self.patch(
274 moonshot_driver, '_issue_ipmitool_command')
275
276=== modified file 'src/provisioningserver/power/__init__.py'
277--- src/provisioningserver/power/__init__.py 2015-12-04 20:57:06 +0000
278+++ src/provisioningserver/power/__init__.py 2016-06-01 04:55:57 +0000
279@@ -32,6 +32,7 @@
280 'amt',
281 'hmc',
282 'ipmi',
283+ 'moonshot',
284 'mscm',
285 'msftocs',
286 'sm15k',

Subscribers

People subscribed via source and target branches