Merge lp:~newell-jensen/maas/add-ucsm-power-query-bug-1389416 into lp:~maas-committers/maas/trunk

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: no longer in the source branch.
Merged at revision: 3402
Proposed branch: lp:~newell-jensen/maas/add-ucsm-power-query-bug-1389416
Merge into: lp:~maas-committers/maas/trunk
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/add-ucsm-power-query-bug-1389416
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Jason Hobbs (community) Approve
Review via email: mp+240680@code.launchpad.net

Commit message

Add power query capabilities to UCSM hardware.

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

Setting to Work in Progress because this needs to be tested on UCS Manager hardware and the only place that seems to be is in the OIL lab. Andres, any idea when we could get this tested in there?

Revision history for this message
Andres Rodriguez (andreserl) wrote :

Ok, so I have tested this in real environment and it seems to work fine other for the comment bellow. Please fix that and then it should be good to go.

However, also please have someone else look at this branch.

review: Needs Fixing
Revision history for this message
Jason Hobbs (jason-hobbs) wrote :

I think there are some improvements that should be made to the template, but the rest looks good. See comments inline below.

Revision history for this message
Jason Hobbs (jason-hobbs) :
review: Needs Fixing
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Jason,

Concerning the stack trace logging, pserv.log already gets the entire stack trace if there is an error within the try block of the power template (this was tested by raising an exception within the try block and monitoring the logs). Go ahead and have another look as I have changed the other items you commented on.

Revision history for this message
Jason Hobbs (jason-hobbs) wrote :

Getting closer, but the template can still be reduced more by moving the error handling introduced here into a method you can import instead of just being directly in the template.

review: Needs Fixing
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Ready for another look. Template was reduced and error handling was moved to Python code. Leaving the try/except block in the template due to copious amounts of code in provisioningserver.rpc.power/poweraction, which handle this well.

Revision history for this message
Jason Hobbs (jason-hobbs) wrote :

Looks much better, thanks. I don't follow what you mean about leaving the try/except in the template, but it's small enough I won't block on it. Approve, as long as you've tested this latest revision.

review: Approve
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-05 21:35:37 +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-05 21:35:37 +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-05 21:35:37 +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-11-24 22:38:15 +0000
241+++ src/provisioningserver/rpc/power.py 2014-12-05 21:35:37 +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,