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
=== modified file 'etc/maas/templates/power/ucsm.template'
--- etc/maas/templates/power/ucsm.template 2014-07-18 17:05:57 +0000
+++ etc/maas/templates/power/ucsm.template 2014-12-05 21:35:37 +0000
@@ -2,13 +2,63 @@
2#2#
3# Control a system via Cisco UCS Manager XML API.3# Control a system via Cisco UCS Manager XML API.
44
5# Exit with failure message.
6# Parameters: exit code, and error message.
7fail() {
8 echo "$2" >&2
9 exit $1
10}
11
12issue_ucsm_command() {
5python - << END13python - << END
14import sys
6from provisioningserver.drivers.hardware.ucsm import power_control_ucsm15from provisioningserver.drivers.hardware.ucsm import power_control_ucsm
7power_control_ucsm(16try:
8 {{escape_py_literal(power_address) | safe}},17 power_control_ucsm(
9 {{escape_py_literal(power_user) | safe}},18 {{escape_py_literal(power_address) | safe}},
10 {{escape_py_literal(power_pass) | safe}},19 {{escape_py_literal(power_user) | safe}},
11 {{escape_py_literal(uuid) | safe}},20 {{escape_py_literal(power_pass) | safe}},
12 {{escape_py_literal(power_change) | safe}},21 {{escape_py_literal(uuid) | safe}},
13)22 {{escape_py_literal(power_change) | safe}},
14END23 )
24except Exception as e:
25 # This gets in the node event log: print the exception's message
26 # and not the stacktrace.
27 print(unicode(e))
28 sys.exit(1)
29END
30}
31
32query_state() {
33python - << END
34import sys
35from provisioningserver.drivers.hardware.ucsm import power_state_ucsm
36try:
37 print(power_state_ucsm(
38 {{escape_py_literal(power_address) | safe}},
39 {{escape_py_literal(power_user) | safe}},
40 {{escape_py_literal(power_pass) | safe}},
41 {{escape_py_literal(uuid) | safe}},
42 ))
43except Exception as e:
44 # This gets in the node event log: print the exception's message
45 # and not the stacktrace.
46 print(unicode(e))
47 sys.exit(1)
48END
49}
50
51main() {
52 case $1 in
53 'on'|'off')
54 issue_ucsm_command
55 ;;
56 'query')
57 query_state
58 ;;
59 *)
60 fail 2 "Unknown power command: '$1'"
61 esac
62}
63
64main "{{power_change}}"
1565
=== modified file 'src/provisioningserver/drivers/hardware/tests/test_ucsm.py'
--- src/provisioningserver/drivers/hardware/tests/test_ucsm.py 2014-07-18 17:05:57 +0000
+++ src/provisioningserver/drivers/hardware/tests/test_ucsm.py 2014-12-05 21:35:37 +0000
@@ -50,6 +50,7 @@
50 make_request_data,50 make_request_data,
51 parse_response,51 parse_response,
52 power_control_ucsm,52 power_control_ucsm,
53 power_state_ucsm,
53 probe_and_enlist_ucsm,54 probe_and_enlist_ucsm,
54 probe_servers,55 probe_servers,
55 RO_KEYS,56 RO_KEYS,
@@ -60,6 +61,7 @@
60 UCSM_XML_API_Error,61 UCSM_XML_API_Error,
61 )62 )
62import provisioningserver.utils as utils63import provisioningserver.utils as utils
64from testtools.matchers import Equals
6365
6466
65def make_api(url='http://url', user='u', password='p',67def make_api(url='http://url', user='u', password='p',
@@ -461,7 +463,7 @@
461463
462 def test_get_power_command_raises_assertion_error_on_bad_power_mode(self):464 def test_get_power_command_raises_assertion_error_on_bad_power_mode(self):
463 bad_power_mode = factory.make_name('unlikely')465 bad_power_mode = factory.make_name('unlikely')
464 error = self.assertRaises(AssertionError, get_power_command,466 error = self.assertRaises(UCSM_XML_API_Error, get_power_command,
465 bad_power_mode, None)467 bad_power_mode, None)
466 self.assertIn(bad_power_mode, error.args[0])468 self.assertIn(bad_power_mode, error.args[0])
467469
@@ -490,6 +492,76 @@
490 MockCalledOnceWith(api, power_control, state))492 MockCalledOnceWith(api, power_control, state))
491493
492494
495class TestUCSMPowerState(MAASTestCase):
496 """Tests for `power_state_ucsm`."""
497
498 def test_power_state_get_off(self):
499 url = factory.make_name('url')
500 username = factory.make_name('username')
501 password = factory.make_name('password')
502 uuid = factory.make_UUID()
503 api = Mock()
504 self.patch(ucsm, 'UCSM_XML_API').return_value = api
505 get_servers_mock = self.patch(ucsm, 'get_servers')
506 server = make_server()
507 current_state = 'down'
508 power_control = Element('lsPower', {'state': current_state})
509 get_servers_mock.return_value = [server]
510 get_server_power_control_mock = self.patch(
511 ucsm, 'get_server_power_control')
512 get_server_power_control_mock.return_value = power_control
513
514 power_state = power_state_ucsm(url, username, password, uuid)
515 self.expectThat(get_servers_mock, MockCalledOnceWith(api, uuid))
516 self.expectThat(
517 get_server_power_control_mock,
518 MockCalledOnceWith(api, server))
519 self.expectThat(power_state, Equals('off'))
520
521 def test_power_state_get_on(self):
522 url = factory.make_name('url')
523 username = factory.make_name('username')
524 password = factory.make_name('password')
525 uuid = factory.make_UUID()
526 api = Mock()
527 self.patch(ucsm, 'UCSM_XML_API').return_value = api
528 get_servers_mock = self.patch(ucsm, 'get_servers')
529 server = make_server()
530 current_state = 'up'
531 power_control = Element('lsPower', {'state': current_state})
532 get_servers_mock.return_value = [server]
533 get_server_power_control_mock = self.patch(
534 ucsm, 'get_server_power_control')
535 get_server_power_control_mock.return_value = power_control
536
537 power_state = power_state_ucsm(url, username, password, uuid)
538 self.expectThat(get_servers_mock, MockCalledOnceWith(api, uuid))
539 self.expectThat(
540 get_server_power_control_mock,
541 MockCalledOnceWith(api, server))
542 self.expectThat(power_state, Equals('on'))
543
544 def test_power_state_error_on_unknown_state(self):
545 url = factory.make_name('url')
546 username = factory.make_name('username')
547 password = factory.make_name('password')
548 uuid = factory.make_UUID()
549 api = Mock()
550 self.patch(ucsm, 'UCSM_XML_API').return_value = api
551 get_servers_mock = self.patch(ucsm, 'get_servers')
552 server = make_server()
553 current_state = factory.make_name('error')
554 power_control = Element('lsPower', {'state': current_state})
555 get_servers_mock.return_value = [server]
556 get_server_power_control_mock = self.patch(
557 ucsm, 'get_server_power_control')
558 get_server_power_control_mock.return_value = power_control
559
560 self.assertRaises(
561 UCSM_XML_API_Error, power_state_ucsm, url,
562 username, password, uuid)
563
564
493class TestProbeAndEnlistUCSM(MAASTestCase):565class TestProbeAndEnlistUCSM(MAASTestCase):
494 """Tests for ``probe_and_enlist_ucsm``."""566 """Tests for ``probe_and_enlist_ucsm``."""
495567
496568
=== modified file 'src/provisioningserver/drivers/hardware/ucsm.py'
--- src/provisioningserver/drivers/hardware/ucsm.py 2014-09-10 16:20:31 +0000
+++ src/provisioningserver/drivers/hardware/ucsm.py 2014-12-05 21:35:37 +0000
@@ -83,10 +83,16 @@
83__metaclass__ = type83__metaclass__ = type
84__all__ = [84__all__ = [
85 'power_control_ucsm',85 'power_control_ucsm',
86 'power_state_ucsm',
86 'probe_and_enlist_ucsm',87 'probe_and_enlist_ucsm',
87]88]
8889
8990
91class UCSMState:
92 DOWN = "down"
93 UP = "up"
94
95
90class UCSM_XML_API_Error(Exception):96class UCSM_XML_API_Error(Exception):
91 """Failure talking to a Cisco UCS Manager."""97 """Failure talking to a Cisco UCS Manager."""
9298
@@ -375,8 +381,8 @@
375 elif maas_power_mode == 'off':381 elif maas_power_mode == 'off':
376 return 'admin-down'382 return 'admin-down'
377 else:383 else:
378 message = 'Unexpected maas power mode: %s' % (maas_power_mode)384 raise UCSM_XML_API_Error(
379 raise AssertionError(message)385 'Unexpected maas power mode: %s' % (maas_power_mode), None)
380386
381387
382def power_control_ucsm(url, username, password, uuid, maas_power_mode):388def power_control_ucsm(url, username, password, uuid, maas_power_mode):
@@ -393,6 +399,23 @@
393 set_server_power_control(api, power_control, command)399 set_server_power_control(api, power_control, command)
394400
395401
402def power_state_ucsm(url, username, password, uuid):
403 """Return the power state for the ucsm machine."""
404 with logged_in(url, username, password) as api:
405 # UUIDs are unique per server, so we get either one or zero
406 # servers for a given UUID.
407 [server] = get_servers(api, uuid)
408 power_control = get_server_power_control(api, server)
409 power_state = power_control.get('state')
410
411 if power_state == UCSMState.DOWN:
412 return 'off'
413 elif power_state == UCSMState.UP:
414 return 'on'
415 raise UCSM_XML_API_Error(
416 'Unknown power state: %s' % power_state, None)
417
418
396def probe_and_enlist_ucsm(url, username, password):419def probe_and_enlist_ucsm(url, username, password):
397 """Probe a UCS Manager and enlist all its servers.420 """Probe a UCS Manager and enlist all its servers.
398421
399422
=== modified file 'src/provisioningserver/rpc/power.py'
--- src/provisioningserver/rpc/power.py 2014-11-24 22:38:15 +0000
+++ src/provisioningserver/rpc/power.py 2014-12-05 21:35:37 +0000
@@ -61,7 +61,7 @@
61# state for these power types.61# state for these power types.
62# This is meant to be temporary until all the power types support62# This is meant to be temporary until all the power types support
63# querying the power state of a node.63# querying the power state of a node.
64QUERY_POWER_TYPES = ['amt', 'ipmi', 'mscm', 'virsh']64QUERY_POWER_TYPES = ['amt', 'ipmi', 'mscm', 'ucsm', 'virsh']
6565
6666
67# Timeout for change_power_state(). We set it to 2 minutes by default,67# Timeout for change_power_state(). We set it to 2 minutes by default,