Merge lp:~newell-jensen/maas/1.7-backport-ucsm-power-query into lp:maas/1.7

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: no longer in the source branch.
Merged at revision: 3302
Proposed branch: lp:~newell-jensen/maas/1.7-backport-ucsm-power-query
Merge into: lp:maas/1.7
Diff against target: 250 lines (+157/-12)
4 files modified
etc/maas/templates/power/ucsm.template (+58/-8)
src/provisioningserver/drivers/hardware/tests/test_ucsm.py (+73/-1)
src/provisioningserver/drivers/hardware/ucsm.py (+25/-2)
src/provisioningserver/rpc/power.py (+1/-1)
To merge this branch: bzr merge lp:~newell-jensen/maas/1.7-backport-ucsm-power-query
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+243888@code.launchpad.net

Commit message

Backport trunk r3402: Add power query capabilities to UCSM hardware.

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

lgtm!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/maas/templates/power/ucsm.template'
2--- etc/maas/templates/power/ucsm.template 2014-07-18 17:05:57 +0000
3+++ etc/maas/templates/power/ucsm.template 2014-12-06 03:15:49 +0000
4@@ -2,13 +2,63 @@
5 #
6 # Control a system via Cisco UCS Manager XML API.
7
8+# Exit with failure message.
9+# Parameters: exit code, and error message.
10+fail() {
11+ echo "$2" >&2
12+ exit $1
13+}
14+
15+issue_ucsm_command() {
16 python - << END
17+import sys
18 from provisioningserver.drivers.hardware.ucsm import power_control_ucsm
19-power_control_ucsm(
20- {{escape_py_literal(power_address) | safe}},
21- {{escape_py_literal(power_user) | safe}},
22- {{escape_py_literal(power_pass) | safe}},
23- {{escape_py_literal(uuid) | safe}},
24- {{escape_py_literal(power_change) | safe}},
25-)
26-END
27+try:
28+ power_control_ucsm(
29+ {{escape_py_literal(power_address) | safe}},
30+ {{escape_py_literal(power_user) | safe}},
31+ {{escape_py_literal(power_pass) | safe}},
32+ {{escape_py_literal(uuid) | safe}},
33+ {{escape_py_literal(power_change) | safe}},
34+ )
35+except Exception as e:
36+ # This gets in the node event log: print the exception's message
37+ # and not the stacktrace.
38+ print(unicode(e))
39+ sys.exit(1)
40+END
41+}
42+
43+query_state() {
44+python - << END
45+import sys
46+from provisioningserver.drivers.hardware.ucsm import power_state_ucsm
47+try:
48+ print(power_state_ucsm(
49+ {{escape_py_literal(power_address) | safe}},
50+ {{escape_py_literal(power_user) | safe}},
51+ {{escape_py_literal(power_pass) | safe}},
52+ {{escape_py_literal(uuid) | safe}},
53+ ))
54+except Exception as e:
55+ # This gets in the node event log: print the exception's message
56+ # and not the stacktrace.
57+ print(unicode(e))
58+ sys.exit(1)
59+END
60+}
61+
62+main() {
63+ case $1 in
64+ 'on'|'off')
65+ issue_ucsm_command
66+ ;;
67+ 'query')
68+ query_state
69+ ;;
70+ *)
71+ fail 2 "Unknown power command: '$1'"
72+ esac
73+}
74+
75+main "{{power_change}}"
76
77=== modified file 'src/provisioningserver/drivers/hardware/tests/test_ucsm.py'
78--- src/provisioningserver/drivers/hardware/tests/test_ucsm.py 2014-07-18 17:05:57 +0000
79+++ src/provisioningserver/drivers/hardware/tests/test_ucsm.py 2014-12-06 03:15:49 +0000
80@@ -50,6 +50,7 @@
81 make_request_data,
82 parse_response,
83 power_control_ucsm,
84+ power_state_ucsm,
85 probe_and_enlist_ucsm,
86 probe_servers,
87 RO_KEYS,
88@@ -60,6 +61,7 @@
89 UCSM_XML_API_Error,
90 )
91 import provisioningserver.utils as utils
92+from testtools.matchers import Equals
93
94
95 def make_api(url='http://url', user='u', password='p',
96@@ -461,7 +463,7 @@
97
98 def test_get_power_command_raises_assertion_error_on_bad_power_mode(self):
99 bad_power_mode = factory.make_name('unlikely')
100- error = self.assertRaises(AssertionError, get_power_command,
101+ error = self.assertRaises(UCSM_XML_API_Error, get_power_command,
102 bad_power_mode, None)
103 self.assertIn(bad_power_mode, error.args[0])
104
105@@ -490,6 +492,76 @@
106 MockCalledOnceWith(api, power_control, state))
107
108
109+class TestUCSMPowerState(MAASTestCase):
110+ """Tests for `power_state_ucsm`."""
111+
112+ def test_power_state_get_off(self):
113+ url = factory.make_name('url')
114+ username = factory.make_name('username')
115+ password = factory.make_name('password')
116+ uuid = factory.make_UUID()
117+ api = Mock()
118+ self.patch(ucsm, 'UCSM_XML_API').return_value = api
119+ get_servers_mock = self.patch(ucsm, 'get_servers')
120+ server = make_server()
121+ current_state = 'down'
122+ power_control = Element('lsPower', {'state': current_state})
123+ get_servers_mock.return_value = [server]
124+ get_server_power_control_mock = self.patch(
125+ ucsm, 'get_server_power_control')
126+ get_server_power_control_mock.return_value = power_control
127+
128+ power_state = power_state_ucsm(url, username, password, uuid)
129+ self.expectThat(get_servers_mock, MockCalledOnceWith(api, uuid))
130+ self.expectThat(
131+ get_server_power_control_mock,
132+ MockCalledOnceWith(api, server))
133+ self.expectThat(power_state, Equals('off'))
134+
135+ def test_power_state_get_on(self):
136+ url = factory.make_name('url')
137+ username = factory.make_name('username')
138+ password = factory.make_name('password')
139+ uuid = factory.make_UUID()
140+ api = Mock()
141+ self.patch(ucsm, 'UCSM_XML_API').return_value = api
142+ get_servers_mock = self.patch(ucsm, 'get_servers')
143+ server = make_server()
144+ current_state = 'up'
145+ power_control = Element('lsPower', {'state': current_state})
146+ get_servers_mock.return_value = [server]
147+ get_server_power_control_mock = self.patch(
148+ ucsm, 'get_server_power_control')
149+ get_server_power_control_mock.return_value = power_control
150+
151+ power_state = power_state_ucsm(url, username, password, uuid)
152+ self.expectThat(get_servers_mock, MockCalledOnceWith(api, uuid))
153+ self.expectThat(
154+ get_server_power_control_mock,
155+ MockCalledOnceWith(api, server))
156+ self.expectThat(power_state, Equals('on'))
157+
158+ def test_power_state_error_on_unknown_state(self):
159+ url = factory.make_name('url')
160+ username = factory.make_name('username')
161+ password = factory.make_name('password')
162+ uuid = factory.make_UUID()
163+ api = Mock()
164+ self.patch(ucsm, 'UCSM_XML_API').return_value = api
165+ get_servers_mock = self.patch(ucsm, 'get_servers')
166+ server = make_server()
167+ current_state = factory.make_name('error')
168+ power_control = Element('lsPower', {'state': current_state})
169+ get_servers_mock.return_value = [server]
170+ get_server_power_control_mock = self.patch(
171+ ucsm, 'get_server_power_control')
172+ get_server_power_control_mock.return_value = power_control
173+
174+ self.assertRaises(
175+ UCSM_XML_API_Error, power_state_ucsm, url,
176+ username, password, uuid)
177+
178+
179 class TestProbeAndEnlistUCSM(MAASTestCase):
180 """Tests for ``probe_and_enlist_ucsm``."""
181
182
183=== modified file 'src/provisioningserver/drivers/hardware/ucsm.py'
184--- src/provisioningserver/drivers/hardware/ucsm.py 2014-09-10 16:20:31 +0000
185+++ src/provisioningserver/drivers/hardware/ucsm.py 2014-12-06 03:15:49 +0000
186@@ -83,10 +83,16 @@
187 __metaclass__ = type
188 __all__ = [
189 'power_control_ucsm',
190+ 'power_state_ucsm',
191 'probe_and_enlist_ucsm',
192 ]
193
194
195+class UCSMState:
196+ DOWN = "down"
197+ UP = "up"
198+
199+
200 class UCSM_XML_API_Error(Exception):
201 """Failure talking to a Cisco UCS Manager."""
202
203@@ -375,8 +381,8 @@
204 elif maas_power_mode == 'off':
205 return 'admin-down'
206 else:
207- message = 'Unexpected maas power mode: %s' % (maas_power_mode)
208- raise AssertionError(message)
209+ raise UCSM_XML_API_Error(
210+ 'Unexpected maas power mode: %s' % (maas_power_mode), None)
211
212
213 def power_control_ucsm(url, username, password, uuid, maas_power_mode):
214@@ -393,6 +399,23 @@
215 set_server_power_control(api, power_control, command)
216
217
218+def power_state_ucsm(url, username, password, uuid):
219+ """Return the power state for the ucsm machine."""
220+ with logged_in(url, username, password) as api:
221+ # UUIDs are unique per server, so we get either one or zero
222+ # servers for a given UUID.
223+ [server] = get_servers(api, uuid)
224+ power_control = get_server_power_control(api, server)
225+ power_state = power_control.get('state')
226+
227+ if power_state == UCSMState.DOWN:
228+ return 'off'
229+ elif power_state == UCSMState.UP:
230+ return 'on'
231+ raise UCSM_XML_API_Error(
232+ 'Unknown power state: %s' % power_state, None)
233+
234+
235 def probe_and_enlist_ucsm(url, username, password):
236 """Probe a UCS Manager and enlist all its servers.
237
238
239=== modified file 'src/provisioningserver/rpc/power.py'
240--- src/provisioningserver/rpc/power.py 2014-12-02 15:55:34 +0000
241+++ src/provisioningserver/rpc/power.py 2014-12-06 03:15:49 +0000
242@@ -61,7 +61,7 @@
243 # state for these power types.
244 # This is meant to be temporary until all the power types support
245 # querying the power state of a node.
246-QUERY_POWER_TYPES = ['amt', 'ipmi', 'mscm', 'virsh']
247+QUERY_POWER_TYPES = ['amt', 'ipmi', 'mscm', 'ucsm', 'virsh']
248
249
250 # Timeout for change_power_state(). We set it to 2 minutes by default,

Subscribers

People subscribed via source and target branches

to all changes: