Merge lp:~mpontillo/maas/bug-1451852-remove-legacy-esxi-probe-and-enlist into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 3895
Proposed branch: lp:~mpontillo/maas/bug-1451852-remove-legacy-esxi-probe-and-enlist
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~mpontillo/maas/add-pycharm-project-files
Diff against target: 948 lines (+6/-767)
13 files modified
etc/maas/templates/power/esxi.template (+0/-65)
src/maasserver/api/nodegroups.py (+1/-26)
src/maasserver/api/tests/test_nodegroup.py (+0/-40)
src/maasserver/models/nodegroup.py (+0/-27)
src/maasserver/models/tests/test_nodegroup.py (+0/-54)
src/maasserver/static/js/angular/controllers/add_hardware.js (+1/-39)
src/maastesting/tests/test_scss.py (+1/-1)
src/provisioningserver/drivers/hardware/esxi.py (+0/-116)
src/provisioningserver/drivers/hardware/tests/test_esxi.py (+0/-315)
src/provisioningserver/power_schema.py (+1/-11)
src/provisioningserver/rpc/clusterservice.py (+1/-15)
src/provisioningserver/rpc/power.py (+0/-1)
src/provisioningserver/rpc/tests/test_clusterservice.py (+1/-57)
To merge this branch: bzr merge lp:~mpontillo/maas/bug-1451852-remove-legacy-esxi-probe-and-enlist
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+259075@code.launchpad.net

Commit message

Remove legacy (never shipped) VMware probe-and-enlist option.

Description of the change

Remove redundant VMware probe-and-enlist option.

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
=== removed file 'etc/maas/templates/power/esxi.template'
--- etc/maas/templates/power/esxi.template 2015-02-19 17:31:32 +0000
+++ etc/maas/templates/power/esxi.template 1970-01-01 00:00:00 +0000
@@ -1,65 +0,0 @@
1# -*- mode: shell-script -*-
2#
3# Control virtual system's "power" through virsh for ESXi
4#
5
6# Exit with failure message.
7# Parameters: exit code, and error message.
8fail() {
9 echo "$2" >&2
10 exit $1
11}
12
13issue_virsh_command() {
14python - << END
15import sys
16from provisioningserver.drivers.hardware.esxi import power_control_esxi
17try:
18 power_control_esxi(
19 {{escape_py_literal(power_address).decode("ascii") | safe}},
20 {{escape_py_literal(power_id).decode("ascii") | safe}},
21 {{escape_py_literal(power_change).decode("ascii") | safe}},
22 {{escape_py_literal(power_user).decode("ascii") | safe}},
23 {{escape_py_literal(power_pass).decode("ascii") | safe}},
24 )
25except Exception as e:
26 # This gets in the node event log: print the exception's message
27 # and not the stacktrace.
28 print(unicode(e))
29 sys.exit(1)
30END
31}
32
33query_state() {
34python - << END
35import sys
36from provisioningserver.drivers.hardware.esxi import power_state_esxi
37try:
38 print(power_state_esxi(
39 {{escape_py_literal(power_address).decode("ascii") | safe}},
40 {{escape_py_literal(power_id).decode("ascii") | safe}},
41 {{escape_py_literal(power_user).decode("ascii") | safe}},
42 {{escape_py_literal(power_pass).decode("ascii") | safe}},
43 ))
44except Exception as e:
45 # This gets in the node event log: print the exception's message
46 # and not the stacktrace.
47 print(unicode(e))
48 sys.exit(1)
49END
50}
51
52main() {
53 case $1 in
54 'on'|'off')
55 issue_virsh_command
56 ;;
57 'query')
58 query_state
59 ;;
60 *)
61 fail 2 "Unknown power command: '$1'"
62 esac
63}
64
65main "{{power_change}}"
660
=== modified file 'src/maasserver/api/nodegroups.py'
--- src/maasserver/api/nodegroups.py 2015-05-07 18:14:38 +0000
+++ src/maasserver/api/nodegroups.py 2015-05-14 16:27:22 +0000
@@ -372,11 +372,10 @@
372 """Add special hardware types.372 """Add special hardware types.
373373
374 :param model: The type of hardware. 'seamicro15k', 'virsh', 'vsphere',374 :param model: The type of hardware. 'seamicro15k', 'virsh', 'vsphere',
375 'esxi', 'powerkvm', 'mscm', 'msftocs' and 'ucsm' are supported.375 'powerkvm', 'mscm', 'msftocs' and 'ucsm' are supported.
376376
377 seamicro15k is the model for the Seamicro 1500 Chassis.377 seamicro15k is the model for the Seamicro 1500 Chassis.
378 virsh is the model for Virtual Machines managed by Virsh.378 virsh is the model for Virtual Machines managed by Virsh.
379 esxi is the model for Virtual Machines managed by ESXi.
380 powerkvm is the model for Virtual Machines on Power KVM,379 powerkvm is the model for Virtual Machines on Power KVM,
381 managed by Virsh.380 managed by Virsh.
382 mscm is the model for the Moonshot Chassis Manager.381 mscm is the model for the Moonshot Chassis Manager.
@@ -419,20 +418,6 @@
419 :param prefix_filter: Filter nodes with supplied prefix.418 :param prefix_filter: Filter nodes with supplied prefix.
420 :type prefix_filter: unicode419 :type prefix_filter: unicode
421420
422 The following are required if you are probing esxi:
423
424 :param address: The IP address of the esxi machine.
425 :type address: unicode
426 :param username: esxi username.
427 :type username: unicode
428 :param password: esxi password.
429 :type password: unicode
430
431 The following are optional if you are probing esxi:
432
433 :param prefix_filter: Filter nodes with supplied prefix.
434 :type prefix_filter: unicode
435
436 The following are required if you are probing mscm:421 The following are required if you are probing mscm:
437422
438 :param host: IP Address for the Moonshot Chassis Manager.423 :param host: IP Address for the Moonshot Chassis Manager.
@@ -514,16 +499,6 @@
514 user, host, username, password, port=port,499 user, host, username, password, port=port,
515 protocol=protocol, prefix_filter=prefix_filter,500 protocol=protocol, prefix_filter=prefix_filter,
516 accept_all=accept_all)501 accept_all=accept_all)
517 elif model == 'esxi':
518 poweraddr = get_mandatory_param(request.data, 'address')
519 poweruser = get_mandatory_param(request.data, 'username')
520 password = get_mandatory_param(request.data, 'password')
521 prefix_filter = get_optional_param(
522 request.data, 'prefix_filter', default=None)
523
524 nodegroup.add_esxi(
525 user, poweruser, poweraddr, password=password,
526 prefix_filter=prefix_filter, accept_all=accept_all)
527 else:502 else:
528 return HttpResponse(status=httplib.BAD_REQUEST)503 return HttpResponse(status=httplib.BAD_REQUEST)
529504
530505
=== modified file 'src/maasserver/api/tests/test_nodegroup.py'
--- src/maasserver/api/tests/test_nodegroup.py 2015-05-07 18:14:38 +0000
+++ src/maasserver/api/tests/test_nodegroup.py 2015-05-14 16:27:22 +0000
@@ -57,7 +57,6 @@
57)57)
58from mock import Mock58from mock import Mock
59from provisioningserver.rpc.cluster import (59from provisioningserver.rpc.cluster import (
60 AddESXi,
61 AddSeaMicro15k,60 AddSeaMicro15k,
62 AddVirsh,61 AddVirsh,
63 AddVsphere,62 AddVsphere,
@@ -447,45 +446,6 @@
447 port=None, prefix_filter=prefix_filter,446 port=None, prefix_filter=prefix_filter,
448 accept_all=True))447 accept_all=True))
449448
450 def test_probe_and_enlist_hardware_adds_esxi(self):
451 self.become_admin()
452 user = self.logged_in_user
453 nodegroup = factory.make_NodeGroup()
454 model = 'esxi'
455 poweraddr = factory.make_ipv4_address()
456 password = factory.make_name('password')
457 poweruser = factory.make_name('poweruser')
458 prefix_filter = factory.make_name('filter')
459 accept_all = True
460
461 getClientFor = self.patch(nodegroup_module, 'getClientFor')
462 client = getClientFor.return_value
463 nodegroup = factory.make_NodeGroup()
464
465 response = self.client.post(
466 reverse('nodegroup_handler', args=[nodegroup.uuid]),
467 {
468 'op': 'probe_and_enlist_hardware',
469 'model': model,
470 'user': user.username,
471 'address': poweraddr,
472 'username': poweruser,
473 'password': password,
474 'prefix_filter': prefix_filter,
475 'accept_all': accept_all,
476 })
477
478 self.assertEqual(
479 httplib.OK, response.status_code,
480 explain_unexpected_response(httplib.OK, response))
481
482 self.expectThat(
483 client,
484 MockCalledOnceWith(
485 AddESXi, user=user.username, poweruser=poweruser,
486 poweraddr=poweraddr, password=password,
487 prefix_filter=prefix_filter, accept_all=True))
488
489 def test_probe_and_enlist_hardware_adds_msftocs(self):449 def test_probe_and_enlist_hardware_adds_msftocs(self):
490 self.become_admin()450 self.become_admin()
491 user = self.logged_in_user451 user = self.logged_in_user
492452
=== modified file 'src/maasserver/models/nodegroup.py'
--- src/maasserver/models/nodegroup.py 2015-05-07 18:14:38 +0000
+++ src/maasserver/models/nodegroup.py 2015-05-14 16:27:22 +0000
@@ -50,7 +50,6 @@
50)50)
51from provisioningserver.dhcp.omshell import generate_omapi_key51from provisioningserver.dhcp.omshell import generate_omapi_key
52from provisioningserver.rpc.cluster import (52from provisioningserver.rpc.cluster import (
53 AddESXi,
54 AddSeaMicro15k,53 AddSeaMicro15k,
55 AddVirsh,54 AddVirsh,
56 AddVsphere,55 AddVsphere,
@@ -437,32 +436,6 @@
437 password=password, protocol=protocol, port=port,436 password=password, protocol=protocol, port=port,
438 prefix_filter=prefix_filter, accept_all=accept_all)437 prefix_filter=prefix_filter, accept_all=accept_all)
439438
440 def add_esxi(self, user, poweruser, poweraddr, password,
441 prefix_filter=None, accept_all=False):
442 """ Add all of the virtual machines inside a virsh controller.
443
444 :param user: user for the ESXi host.
445 :param poweraddr: IP address of esxi host string.
446 :param password: password.
447 :param prefix_filter: import based on prefix.
448 :param accept_all: commission enlisted nodes.
449
450 :raises NoConnectionsAvailable: If no connections to the cluster
451 are available.
452 """
453 try:
454 client = getClientFor(self.uuid, timeout=1)
455 except NoConnectionsAvailable:
456 # No connection to the cluster so we can't do anything. We
457 # let the caller handle the error, since we don't want to
458 # just drop it.
459 raise
460 else:
461 return client(
462 AddESXi, user=user, poweruser=poweruser, poweraddr=poweraddr,
463 password=password, prefix_filter=prefix_filter,
464 accept_all=accept_all)
465
466 def enlist_nodes_from_ucsm(self, user, url, username,439 def enlist_nodes_from_ucsm(self, user, url, username,
467 password, accept_all=False):440 password, accept_all=False):
468 """ Add the servers from a Cicso UCS Manager.441 """ Add the servers from a Cicso UCS Manager.
469442
=== modified file 'src/maasserver/models/tests/test_nodegroup.py'
--- src/maasserver/models/tests/test_nodegroup.py 2015-05-07 18:14:38 +0000
+++ src/maasserver/models/tests/test_nodegroup.py 2015-05-14 16:27:22 +0000
@@ -54,7 +54,6 @@
54)54)
55from provisioningserver.dhcp.omshell import generate_omapi_key55from provisioningserver.dhcp.omshell import generate_omapi_key
56from provisioningserver.rpc.cluster import (56from provisioningserver.rpc.cluster import (
57 AddESXi,
58 AddSeaMicro15k,57 AddSeaMicro15k,
59 AddVirsh,58 AddVirsh,
60 AddVsphere,59 AddVsphere,
@@ -683,59 +682,6 @@
683 username, password, protocol=None, port=None,682 username, password, protocol=None, port=None,
684 prefix_filter=None, accept_all=True)683 prefix_filter=None, accept_all=True)
685684
686 def test_add_esxi_end_to_end(self):
687 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ACCEPTED)
688
689 self.useFixture(RegionEventLoopFixture("rpc"))
690 self.useFixture(RunningEventLoopFixture())
691 fixture = self.useFixture(MockLiveRegionToClusterRPCFixture())
692 protocol = fixture.makeCluster(nodegroup, AddESXi)
693 protocol.AddESXi.return_value = defer.succeed(
694 {'system_id': factory.make_name('system-id')})
695
696 user = factory.make_name('user')
697 poweruser = factory.make_name('poweruser')
698 poweraddr = factory.make_name('poweraddr')
699 password = factory.make_name('password')
700 nodegroup.add_esxi(
701 user, poweruser, poweraddr, password, None, True).wait(10)
702
703 self.expectThat(
704 protocol.AddESXi,
705 MockCalledOnceWith(
706 ANY, user=user, poweruser=poweruser, poweraddr=poweraddr,
707 password=password, prefix_filter=None, accept_all=True))
708
709 def test_add_esxi_calls_client_with_resource_endpoint(self):
710 getClientFor = self.patch(nodegroup_module, 'getClientFor')
711 client = getClientFor.return_value
712 nodegroup = factory.make_NodeGroup()
713
714 user = factory.make_name('user')
715 poweruser = factory.make_name('poweruser')
716 poweraddr = factory.make_name('poweraddr')
717 password = factory.make_name('password')
718 nodegroup.add_esxi(user, poweruser, poweraddr, password, None, True)
719
720 self.expectThat(
721 client,
722 MockCalledOnceWith(
723 AddESXi, user=user, poweruser=poweruser, poweraddr=poweraddr,
724 password=password, prefix_filter=None, accept_all=True))
725
726 def test_add_esxi_raises_if_no_connection_to_cluster(self):
727 getClientFor = self.patch(nodegroup_module, 'getClientFor')
728 getClientFor.side_effect = NoConnectionsAvailable()
729 nodegroup = factory.make_NodeGroup()
730
731 user = factory.make_name('user')
732 poweraddr = factory.make_name('poweraddr')
733 password = factory.make_name('password')
734
735 self.assertRaises(
736 NoConnectionsAvailable, nodegroup.add_esxi, user,
737 poweraddr, password, True)
738
739 def test_add_seamicro15k_end_to_end(self):685 def test_add_seamicro15k_end_to_end(self):
740 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ACCEPTED)686 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ACCEPTED)
741687
742688
=== modified file 'src/maasserver/static/js/angular/controllers/add_hardware.js'
--- src/maasserver/static/js/angular/controllers/add_hardware.js 2015-05-07 15:00:36 +0000
+++ src/maasserver/static/js/angular/controllers/add_hardware.js 2015-05-14 16:27:22 +0000
@@ -172,46 +172,8 @@
172 fields: virshFields172 fields: virshFields
173 },173 },
174 {174 {
175 name: 'esxi',
176 description: 'VMWare ESXi (virsh)',
177 fields: [
178 {
179 name: 'address',
180 label: 'Address',
181 field_type: 'string',
182 "default": '',
183 choices: [],
184 required: true
185 },
186 {
187 name: 'username',
188 label: 'Username',
189 field_type: 'string',
190 "default": '',
191 choices: [],
192 required: true
193 },
194 {
195 name: 'password',
196 label: 'Password',
197 field_type: 'string',
198 "default": '',
199 choices: [],
200 required: true
201 },
202 {
203 name: 'prefix_filter',
204 label: 'Prefix filter',
205 field_type: 'string',
206 "default": '',
207 choices: [],
208 required: false
209 }
210 ]
211 },
212 {
213 name: 'vsphere',175 name: 'vsphere',
214 description: 'VMWare (python-vmomi)',176 description: 'VMWare',
215 fields: [177 fields: [
216 {178 {
217 name: 'host',179 name: 'host',
218180
=== modified file 'src/maastesting/tests/test_scss.py'
--- src/maastesting/tests/test_scss.py 2015-05-12 12:38:12 +0000
+++ src/maastesting/tests/test_scss.py 2015-05-14 16:27:22 +0000
@@ -70,4 +70,4 @@
70 tmp_css = self.read_content(70 tmp_css = self.read_content(
71 os.path.join(output_dir, "maas-styles.css"))71 os.path.join(output_dir, "maas-styles.css"))
72 if in_tree_css != tmp_css:72 if in_tree_css != tmp_css:
73 self.fail("maas-styles.css is out-of-date.")73 self.fail("maas-styles.css is out-of-date. (run 'make styles')")
7474
=== removed file 'src/provisioningserver/drivers/hardware/esxi.py'
--- src/provisioningserver/drivers/hardware/esxi.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/drivers/hardware/esxi.py 1970-01-01 00:00:00 +0000
@@ -1,116 +0,0 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4from __future__ import (
5 absolute_import,
6 print_function,
7 unicode_literals,
8 )
9
10str = None
11
12__metaclass__ = type
13__all__ = [
14 'probe_esxi_and_enlist',
15 ]
16
17from provisioningserver.drivers.hardware.virsh import (
18 VirshSSH,
19 VirshVMState,
20 VM_STATE_TO_POWER_STATE,
21)
22from provisioningserver.utils import (
23 commission_node,
24 create_node,
25)
26from provisioningserver.utils.twisted import synchronous
27
28
29class ESXiError(Exception):
30 """Failure communicating to ESXi. """
31
32
33def compose_esxi_url(poweruser, poweraddr):
34 return "esx://%s@%s/?no_verify=1" % (poweruser, poweraddr)
35
36
37@synchronous
38def probe_esxi_and_enlist(
39 user, poweruser, poweraddr, password,
40 prefix_filter=None, accept_all=False):
41 """Extracts all of the virtual machines from virsh and enlists them
42 into MAAS.
43
44 :param user: user for the nodes.
45 :param poweraddr: IP Address of ESXi host.
46 :param password: password connection string.
47 :param prefix_filter: only enlist nodes that have the prefix.
48 :param accept_all: if True, commission enlisted nodes.
49 """
50 conn = VirshSSH(dom_prefix=prefix_filter)
51 virsh_poweraddr = compose_esxi_url(poweruser, poweraddr)
52 if not conn.login(virsh_poweraddr, password):
53 raise ESXiError('Failed to login to virsh console.')
54
55 for machine in conn.list():
56 arch = conn.get_arch(machine)
57 state = conn.get_state(machine)
58 macs = conn.get_mac_addresses(machine)
59
60 # Force the machine off, as MAAS will control the machine
61 # and it needs to be in a known state of off.
62 if state == VirshVMState.ON:
63 conn.poweroff(machine)
64
65 params = {
66 'power_address': poweraddr,
67 'power_user': poweruser,
68 'power_id': machine,
69 'power_pass': password,
70 }
71 system_id = create_node(macs, arch, 'esxi', params).wait(30)
72
73 if accept_all:
74 commission_node(system_id, user).wait(30)
75
76 conn.logout()
77
78
79def power_control_esxi(poweraddr, machine, power_change, poweruser, password):
80 """Powers controls a virtual machine using virsh."""
81
82 conn = VirshSSH()
83 virsh_poweraddr = compose_esxi_url(poweruser, poweraddr)
84 if not conn.login(virsh_poweraddr, password):
85 raise ESXiError('Failed to login to virsh console.')
86
87 state = conn.get_state(machine)
88 if state is None:
89 raise ESXiError('Failed to get virtual domain: %s' % machine)
90
91 if state == VirshVMState.OFF:
92 if power_change == 'on':
93 if not conn.poweron(machine):
94 raise ESXiError('Failed to power on domain: %s' % machine)
95 elif state == VirshVMState.ON:
96 if power_change == 'off':
97 if not conn.poweroff(machine):
98 raise ESXiError('Failed to power off domain: %s' % machine)
99
100
101def power_state_esxi(poweraddr, machine, poweruser, password):
102 """Return the power state for the virtual machine using virsh."""
103
104 conn = VirshSSH()
105 virsh_poweraddr = compose_esxi_url(poweruser, poweraddr)
106 if not conn.login(virsh_poweraddr, password):
107 raise ESXiError('Failed to login to virsh console.')
108
109 state = conn.get_state(machine)
110 if state is None:
111 raise ESXiError('Failed to get domain: %s' % machine)
112
113 try:
114 return VM_STATE_TO_POWER_STATE[state]
115 except KeyError:
116 raise ESXiError('Unknown power state: %s' % state)
1170
=== removed file 'src/provisioningserver/drivers/hardware/tests/test_esxi.py'
--- src/provisioningserver/drivers/hardware/tests/test_esxi.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/drivers/hardware/tests/test_esxi.py 1970-01-01 00:00:00 +0000
@@ -1,315 +0,0 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for `provisioningserver.drivers.hardware.esxi`.
5"""
6
7from __future__ import (
8 absolute_import,
9 print_function,
10 unicode_literals,
11 )
12
13str = None
14
15__metaclass__ = type
16__all__ = []
17
18from textwrap import dedent
19
20from maastesting.factory import factory
21from maastesting.matchers import (
22 MockCalledOnceWith,
23 MockCalledWith,
24 MockCallsMatch,
25)
26from maastesting.testcase import (
27 MAASTestCase,
28 MAASTwistedRunTest,
29)
30from mock import call
31from provisioningserver.drivers.hardware import esxi as virsh
32from provisioningserver.utils.twisted import asynchronous
33from testtools.testcase import ExpectedException
34from twisted.internet.defer import inlineCallbacks
35from twisted.internet.threads import deferToThread
36
37
38SAMPLE_IFLIST = dedent("""
39 Interface Type Source Model MAC
40 -------------------------------------------------------
41 - bridge br0 e1000 %s
42 - bridge br1 e1000 %s
43 """)
44
45SAMPLE_DUMPXML = dedent("""
46 <domain type='kvm'>
47 <name>test</name>
48 <memory unit='KiB'>4096576</memory>
49 <currentMemory unit='KiB'>4096576</currentMemory>
50 <vcpu placement='static'>1</vcpu>
51 <os>
52 <type arch='%s'>hvm</type>
53 <boot dev='network'/>
54 </os>
55 </domain>
56 """)
57
58
59class TestESXi(MAASTestCase):
60 """Tests for `probe_esxi_and_enlist`."""
61
62 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
63
64 @inlineCallbacks
65 def test_probe_and_enlist(self):
66 # Patch VirshSSH list so that some machines are returned
67 # with some fake architectures.
68 user = factory.make_name('user')
69 system_id = factory.make_name('system_id')
70 machines = [factory.make_name('machine') for _ in range(3)]
71 self.patch(virsh.VirshSSH, 'list').return_value = machines
72 fake_arch = factory.make_name('arch')
73 mock_arch = self.patch(virsh.VirshSSH, 'get_arch')
74 mock_arch.return_value = fake_arch
75
76 # Patch get_state so that one of the machines is on, so we
77 # can check that it will be forced off.
78 fake_states = [
79 virsh.VirshVMState.ON,
80 virsh.VirshVMState.OFF,
81 virsh.VirshVMState.OFF
82 ]
83 mock_state = self.patch(virsh.VirshSSH, 'get_state')
84 mock_state.side_effect = fake_states
85
86 # Setup the power parameters that we should expect to be
87 # the output of the probe_and_enlist
88 fake_password = factory.make_string()
89 poweruser = factory.make_name('poweruser')
90 poweraddr = factory.make_name('poweraddr')
91 esx_poweraddr = "esx://%s@%s/?no_verify=1" % (user, poweraddr)
92 called_params = []
93 fake_macs = []
94 for machine in machines:
95 macs = [factory.make_mac_address() for _ in range(3)]
96 fake_macs.append(macs)
97 called_params.append({
98 'power_address': poweraddr,
99 'power_id': machine,
100 'power_pass': fake_password,
101 'power_user': poweruser,
102 })
103
104 # Patch the get_mac_addresses so we get a known list of
105 # mac addresses for each machine.
106 mock_macs = self.patch(virsh.VirshSSH, 'get_mac_addresses')
107 mock_macs.side_effect = fake_macs
108
109 # Patch the poweroff and create as we really don't want these
110 # actions to occur, but want to also check that they are called.
111 mock_poweroff = self.patch(virsh.VirshSSH, 'poweroff')
112 mock_create_node = self.patch(virsh, 'create_node')
113 mock_create_node.side_effect = asynchronous(lambda *args: system_id)
114 mock_commission_node = self.patch(virsh, 'commission_node')
115
116 # Patch login and logout so that we don't really contact
117 # a server at the fake poweraddr
118 mock_login = self.patch(virsh.VirshSSH, 'login')
119 mock_login.return_value = True
120 mock_logout = self.patch(virsh.VirshSSH, 'logout')
121
122 # Perform the probe and enlist
123 yield deferToThread(
124 virsh.probe_esxi_and_enlist, user, poweruser, poweraddr,
125 password=fake_password, accept_all=True)
126
127 # Check that login was called with the provided poweraddr and
128 # password.
129 self.expectThat(
130 mock_login, MockCalledOnceWith(esx_poweraddr, fake_password))
131
132 # The first machine should have poweroff called on it, as it
133 # was initial in the on state.
134 self.expectThat(
135 mock_poweroff, MockCalledOnceWith(machines[0]))
136
137 # Check that the create command had the correct parameters for
138 # each machine.
139 self.expectThat(
140 mock_create_node, MockCallsMatch(
141 call(
142 fake_macs[0], fake_arch, 'esxi', called_params[0]),
143 call(
144 fake_macs[1], fake_arch, 'esxi', called_params[1]),
145 call(
146 fake_macs[2], fake_arch,
147 'esxi', called_params[2])))
148 mock_logout.assert_called()
149 self.expectThat(
150 mock_commission_node,
151 MockCalledWith(system_id, user))
152
153 @inlineCallbacks
154 def test_probe_and_enlist_login_failure(self):
155 user = factory.make_name('user')
156 poweruser = factory.make_name('poweruser')
157 poweraddr = factory.make_name('poweraddr')
158 mock_login = self.patch(virsh.VirshSSH, 'login')
159 mock_login.return_value = False
160 with ExpectedException(virsh.ESXiError):
161 yield deferToThread(
162 virsh.probe_esxi_and_enlist,
163 user, poweruser, poweraddr, password=factory.make_string())
164
165
166class TestESXiPowerControl(MAASTestCase):
167 """Tests for `power_control_esxi`."""
168
169 def test_power_control_login_failure(self):
170 mock_login = self.patch(virsh.VirshSSH, 'login')
171 mock_login.return_value = False
172 self.assertRaises(
173 virsh.ESXiError, virsh.power_control_esxi,
174 factory.make_name('poweraddr'), factory.make_name('machine'), 'on',
175 factory.make_name('username'), password=factory.make_string())
176
177 def test_power_control_on(self):
178 mock_login = self.patch(virsh.VirshSSH, 'login')
179 mock_login.return_value = True
180 mock_state = self.patch(virsh.VirshSSH, 'get_state')
181 mock_state.return_value = virsh.VirshVMState.OFF
182 mock_poweron = self.patch(virsh.VirshSSH, 'poweron')
183
184 poweraddr = factory.make_name('poweraddr')
185 machine = factory.make_name('machine')
186 username = factory.make_name('user')
187 password = factory.make_string()
188 esx_poweraddr = "esx://%s@%s/?no_verify=1" % (username, poweraddr)
189 virsh.power_control_esxi(poweraddr, machine, 'on', username, password)
190
191 self.assertThat(
192 mock_login, MockCalledOnceWith(esx_poweraddr, password))
193 self.assertThat(
194 mock_state, MockCalledOnceWith(machine))
195 self.assertThat(
196 mock_poweron, MockCalledOnceWith(machine))
197
198 def test_power_control_off(self):
199 mock_login = self.patch(virsh.VirshSSH, 'login')
200 mock_login.return_value = True
201 mock_state = self.patch(virsh.VirshSSH, 'get_state')
202 mock_state.return_value = virsh.VirshVMState.ON
203 mock_poweroff = self.patch(virsh.VirshSSH, 'poweroff')
204
205 poweraddr = factory.make_name('poweraddr')
206 machine = factory.make_name('machine')
207 username = factory.make_name('user')
208 password = factory.make_string()
209 esx_poweraddr = "esx://%s@%s/?no_verify=1" % (username, poweraddr)
210 virsh.power_control_esxi(poweraddr, machine, 'off', username, password)
211
212 self.assertThat(
213 mock_login, MockCalledOnceWith(esx_poweraddr, password))
214 self.assertThat(
215 mock_state, MockCalledOnceWith(machine))
216 self.assertThat(
217 mock_poweroff, MockCalledOnceWith(machine))
218
219 def test_power_control_bad_domain(self):
220 mock_login = self.patch(virsh.VirshSSH, 'login')
221 mock_login.return_value = True
222 mock_state = self.patch(virsh.VirshSSH, 'get_state')
223 mock_state.return_value = None
224
225 poweraddr = factory.make_name('poweraddr')
226 machine = factory.make_name('machine')
227 username = factory.make_name('user')
228 password = factory.make_string()
229 self.assertRaises(
230 virsh.ESXiError, virsh.power_control_esxi,
231 poweraddr, machine, 'on', username, password)
232
233 def test_power_control_power_failure(self):
234 mock_login = self.patch(virsh.VirshSSH, 'login')
235 mock_login.return_value = True
236 mock_state = self.patch(virsh.VirshSSH, 'get_state')
237 mock_state.return_value = virsh.VirshVMState.ON
238 mock_poweroff = self.patch(virsh.VirshSSH, 'poweroff')
239 mock_poweroff.return_value = False
240
241 poweraddr = factory.make_name('poweraddr')
242 machine = factory.make_name('machine')
243 username = factory.make_name('user')
244 password = factory.make_string()
245 self.assertRaises(
246 virsh.ESXiError, virsh.power_control_esxi,
247 poweraddr, machine, 'off', username, password)
248
249
250class TestESXiPowerState(MAASTestCase):
251 """Tests for `power_state_esxi`."""
252
253 def test_power_state_login_failure(self):
254 mock_login = self.patch(virsh.VirshSSH, 'login')
255 mock_login.return_value = False
256 self.assertRaises(
257 virsh.ESXiError, virsh.power_state_esxi,
258 factory.make_name('poweraddr'), factory.make_name('machine'),
259 factory.make_name('user'), password=factory.make_string())
260
261 def test_power_state_get_on(self):
262 mock_login = self.patch(virsh.VirshSSH, 'login')
263 mock_login.return_value = True
264 mock_state = self.patch(virsh.VirshSSH, 'get_state')
265 mock_state.return_value = virsh.VirshVMState.ON
266
267 poweraddr = factory.make_name('poweraddr')
268 machine = factory.make_name('machine')
269 username = factory.make_name('user')
270 password = factory.make_string()
271 self.assertEqual(
272 'on', virsh.power_state_esxi(poweraddr, machine,
273 username, password))
274
275 def test_power_state_get_off(self):
276 mock_login = self.patch(virsh.VirshSSH, 'login')
277 mock_login.return_value = True
278 mock_state = self.patch(virsh.VirshSSH, 'get_state')
279 mock_state.return_value = virsh.VirshVMState.OFF
280
281 poweraddr = factory.make_name('poweraddr')
282 machine = factory.make_name('machine')
283 username = factory.make_name('user')
284 password = factory.make_string()
285 self.assertEqual(
286 'off', virsh.power_state_esxi(poweraddr, machine,
287 username, password))
288
289 def test_power_control_bad_domain(self):
290 mock_login = self.patch(virsh.VirshSSH, 'login')
291 mock_login.return_value = True
292 mock_state = self.patch(virsh.VirshSSH, 'get_state')
293 mock_state.return_value = None
294
295 poweraddr = factory.make_name('poweraddr')
296 machine = factory.make_name('machine')
297 username = factory.make_name('user')
298 password = factory.make_string()
299 self.assertRaises(
300 virsh.ESXiError, virsh.power_state_esxi,
301 poweraddr, machine, username, password)
302
303 def test_power_state_error_on_unknown_state(self):
304 mock_login = self.patch(virsh.VirshSSH, 'login')
305 mock_login.return_value = True
306 mock_state = self.patch(virsh.VirshSSH, 'get_state')
307 mock_state.return_value = 'unknown'
308
309 poweraddr = factory.make_name('poweraddr')
310 machine = factory.make_name('machine')
311 username = factory.make_name('user')
312 password = factory.make_string()
313 self.assertRaises(
314 virsh.ESXiError, virsh.power_state_esxi,
315 poweraddr, machine, username, password)
3160
=== modified file 'src/provisioningserver/power_schema.py'
--- src/provisioningserver/power_schema.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/power_schema.py 2015-05-14 16:27:22 +0000
@@ -174,18 +174,8 @@
174 ],174 ],
175 },175 },
176 {176 {
177 'name': 'esxi',
178 'description': 'VMWare ESXi (virsh)',
179 'fields': [
180 make_json_field('power_address', "Power address"),
181 make_json_field('power_id', "Power ID"),
182 make_json_field('power_user', "Power user"),
183 make_json_field('power_pass', "Power password"),
184 ],
185 },
186 {
187 'name': 'vsphere',177 'name': 'vsphere',
188 'description': 'VMWare (python-pyvmomi)',178 'description': 'VMWare',
189 'fields': [179 'fields': [
190 make_json_field(180 make_json_field(
191 'power_vm_name', "VM Name (if UUID unknown)", required=False),181 'power_vm_name', "VM Name (if UUID unknown)", required=False),
192182
=== modified file 'src/provisioningserver/rpc/clusterservice.py'
--- src/provisioningserver/rpc/clusterservice.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/rpc/clusterservice.py 2015-05-14 16:27:22 +0000
@@ -34,7 +34,6 @@
34 ArchitectureRegistry,34 ArchitectureRegistry,
35 PowerTypeRegistry,35 PowerTypeRegistry,
36)36)
37from provisioningserver.drivers.hardware.esxi import probe_esxi_and_enlist
38from provisioningserver.drivers.hardware.mscm import probe_and_enlist_mscm37from provisioningserver.drivers.hardware.mscm import probe_and_enlist_mscm
39from provisioningserver.drivers.hardware.msftocs import (38from provisioningserver.drivers.hardware.msftocs import (
40 probe_and_enlist_msftocs,39 probe_and_enlist_msftocs,
@@ -368,19 +367,6 @@
368 d.addErrback(partial(catch_probe_and_enlist_error, "virsh"))367 d.addErrback(partial(catch_probe_and_enlist_error, "virsh"))
369 return {}368 return {}
370369
371 @cluster.AddESXi.responder
372 def add_esxi(self, user, poweruser, poweraddr,
373 password, prefix_filter, accept_all):
374 """add_esxi()
375
376 Implementation of :py:class:`~provisioningserver.rpc.cluster.AddESXi`.
377 """
378 d = deferToThread(
379 probe_esxi_and_enlist,
380 user, poweruser, poweraddr, password, prefix_filter, accept_all)
381 d.addErrback(partial(catch_probe_and_enlist_error, "esxi"))
382 return {}
383
384 @cluster.AddSeaMicro15k.responder370 @cluster.AddSeaMicro15k.responder
385 def add_seamicro15k(self, user, mac, username,371 def add_seamicro15k(self, user, mac, username,
386 password, power_control, accept_all):372 password, power_control, accept_all):
@@ -417,7 +403,7 @@
417 port=port, protocol=protocol, prefix_filter=prefix_filter,403 port=port, protocol=protocol, prefix_filter=prefix_filter,
418 accept_all=accept_all)404 accept_all=accept_all)
419 d.addErrback(405 d.addErrback(
420 partial(catch_probe_and_enlist_error, "vSphere"))406 partial(catch_probe_and_enlist_error, "VMware"))
421 return {}407 return {}
422408
423 @cluster.EnlistNodesFromMSCM.responder409 @cluster.EnlistNodesFromMSCM.responder
424410
=== modified file 'src/provisioningserver/rpc/power.py'
--- src/provisioningserver/rpc/power.py 2015-05-12 16:12:15 +0000
+++ src/provisioningserver/rpc/power.py 2015-05-14 16:27:22 +0000
@@ -67,7 +67,6 @@
67QUERY_POWER_TYPES = [67QUERY_POWER_TYPES = [
68 'amt',68 'amt',
69 'dli',69 'dli',
70 'esxi',
71 'ipmi',70 'ipmi',
72 'mscm',71 'mscm',
73 'msftocs',72 'msftocs',
7473
=== modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py'
--- src/provisioningserver/rpc/tests/test_clusterservice.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/rpc/tests/test_clusterservice.py 2015-05-14 16:27:22 +0000
@@ -2082,63 +2082,7 @@
2082 clusterservice.maaslog.error,2082 clusterservice.maaslog.error,
2083 MockAnyCall(2083 MockAnyCall(
2084 "Failed to probe and enlist %s nodes: %s",2084 "Failed to probe and enlist %s nodes: %s",
2085 "vSphere", fake_error))2085 "VMware", fake_error))
2086
2087
2088class TestClusterProtocol_AddESXi(MAASTestCase):
2089
2090 def test__is_registered(self):
2091 protocol = Cluster()
2092 responder = protocol.locateResponder(
2093 cluster.AddESXi.commandName)
2094 self.assertIsNotNone(responder)
2095
2096 def test__calls_deferToThread_with_probe_esxi_and_enlist(self):
2097 mock_deferToThread = self.patch_autospec(
2098 clusterservice, 'deferToThread')
2099 user = factory.make_name('user')
2100 poweruser = factory.make_name('poweruser')
2101 poweraddr = factory.make_name('poweraddr')
2102 password = factory.make_name('password')
2103 prefix_filter = factory.make_name('prefix_filter')
2104 call_responder(Cluster(), cluster.AddESXi, {
2105 "user": user,
2106 "poweruser": poweruser,
2107 "poweraddr": poweraddr,
2108 "password": password,
2109 "prefix_filter": prefix_filter,
2110 "accept_all": True,
2111 })
2112 self.assertThat(
2113 mock_deferToThread, MockCalledOnceWith(
2114 clusterservice.probe_esxi_and_enlist,
2115 user, poweruser, poweraddr, password,
2116 prefix_filter, True))
2117
2118 def test__logs_error_to_maaslog(self):
2119 fake_error = factory.make_name('error')
2120 self.patch(clusterservice, 'maaslog')
2121 mock_deferToThread = self.patch_autospec(
2122 clusterservice, 'deferToThread')
2123 mock_deferToThread.return_value = fail(Exception(fake_error))
2124 user = factory.make_name('user')
2125 poweruser = factory.make_name('poweruser')
2126 poweraddr = factory.make_name('poweraddr')
2127 password = factory.make_name('password')
2128 prefix_filter = factory.make_name('prefix_filter')
2129 call_responder(Cluster(), cluster.AddESXi, {
2130 "user": user,
2131 "poweruser": poweruser,
2132 "poweraddr": poweraddr,
2133 "password": password,
2134 "prefix_filter": prefix_filter,
2135 "accept_all": True,
2136 })
2137 self.assertThat(
2138 clusterservice.maaslog.error,
2139 MockAnyCall(
2140 "Failed to probe and enlist %s nodes: %s",
2141 "esxi", fake_error))
21422086
21432087
2144class TestClusterProtocol_AddSeaMicro15k(MAASTestCase):2088class TestClusterProtocol_AddSeaMicro15k(MAASTestCase):