Merge lp:~newell-jensen/maas/arm64-backport-1.5 into lp:maas/1.5

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: no longer in the source branch.
Merged at revision: 2292
Proposed branch: lp:~newell-jensen/maas/arm64-backport-1.5
Merge into: lp:maas/1.5
Diff against target: 725 lines (+582/-1)
12 files modified
etc/maas/templates/power/mscm.template (+15/-0)
src/maasserver/api.py (+24/-0)
src/maasserver/models/nodegroup.py (+11/-0)
src/maasserver/tests/test_api_nodegroup.py (+26/-0)
src/maastesting/factory.py (+9/-0)
src/provisioningserver/driver/__init__.py (+6/-0)
src/provisioningserver/drivers/hardware/mscm.py (+187/-0)
src/provisioningserver/drivers/hardware/tests/test_mscm.py (+259/-0)
src/provisioningserver/power/tests/test_poweraction.py (+11/-0)
src/provisioningserver/power_schema.py (+13/-0)
src/provisioningserver/tasks.py (+9/-1)
src/provisioningserver/tests/test_tasks.py (+12/-0)
To merge this branch: bzr merge lp:~newell-jensen/maas/arm64-backport-1.5
Reviewer Review Type Date Requested Status
Newell Jensen (community) Approve
Review via email: mp+228041@code.launchpad.net

Commit message

arm64 enablement backported to MaaS 1.5

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

Self review, backport.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'etc/maas/templates/power/mscm.template'
--- etc/maas/templates/power/mscm.template 1970-01-01 00:00:00 +0000
+++ etc/maas/templates/power/mscm.template 2014-07-24 05:13:03 +0000
@@ -0,0 +1,15 @@
1# -*- mode: shell-script -*-
2#
3# Control a system via Moonshot HP iLO Chassis Manager (MSCM).
4
5{{py: from provisioningserver.utils import escape_py_literal}}
6python - << END
7from provisioningserver.drivers.hardware.mscm import power_control_mscm
8power_control_mscm(
9 {{escape_py_literal(power_address) | safe}},
10 {{escape_py_literal(power_user) | safe}},
11 {{escape_py_literal(power_pass) | safe}},
12 {{escape_py_literal(node_id) | safe}},
13 {{escape_py_literal(power_change) | safe}},
14)
15END
016
=== added symlink 'etc/maas/templates/pxe/config.commissioning.arm64.template'
=== target is u'config.commissioning.armhf.template'
=== added symlink 'etc/maas/templates/pxe/config.install.arm64.template'
=== target is u'config.install.armhf.template'
=== added symlink 'etc/maas/templates/pxe/config.xinstall.arm64.template'
=== target is u'config.xinstall.armhf.template'
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2014-06-19 11:25:08 +0000
+++ src/maasserver/api.py 2014-07-24 05:13:03 +0000
@@ -1763,6 +1763,30 @@
17631763
1764 return HttpResponse(status=httplib.OK)1764 return HttpResponse(status=httplib.OK)
17651765
1766 @admin_method
1767 @operation(idempotent=False)
1768 def probe_and_enlist_mscm(self, request, uuid):
1769 """Add the nodes from a Moonshot HP iLO Chassis Manager (MSCM).
1770
1771 :param host: IP Address for the MSCM.
1772 :type host: unicode
1773 :param username: The username for the MSCM.
1774 :type username: unicode
1775 :param password: The password for the MSCM.
1776 :type password: unicode
1777
1778 """
1779 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
1780
1781 host = get_mandatory_param(request.data, 'host')
1782 username = get_mandatory_param(request.data, 'username')
1783 password = get_mandatory_param(request.data, 'password')
1784
1785 nodegroup.enlist_nodes_from_mscm(host, username, password)
1786
1787 return HttpResponse(status=httplib.OK)
1788
1789
1766DISPLAYED_NODEGROUPINTERFACE_FIELDS = (1790DISPLAYED_NODEGROUPINTERFACE_FIELDS = (
1767 'ip', 'management', 'interface', 'subnet_mask',1791 'ip', 'management', 'interface', 'subnet_mask',
1768 'broadcast_ip', 'ip_range_low', 'ip_range_high')1792 'broadcast_ip', 'ip_range_low', 'ip_range_high')
17691793
=== modified file 'src/maasserver/models/nodegroup.py'
--- src/maasserver/models/nodegroup.py 2014-05-21 17:30:26 +0000
+++ src/maasserver/models/nodegroup.py 2014-07-24 05:13:03 +0000
@@ -42,6 +42,7 @@
42 add_new_dhcp_host_map,42 add_new_dhcp_host_map,
43 add_seamicro15k,43 add_seamicro15k,
44 add_virsh,44 add_virsh,
45 enlist_nodes_from_mscm,
45 enlist_nodes_from_ucsm,46 enlist_nodes_from_ucsm,
46 import_boot_images,47 import_boot_images,
47 report_boot_images,48 report_boot_images,
@@ -307,6 +308,16 @@
307 args = (url, username, password)308 args = (url, username, password)
308 enlist_nodes_from_ucsm.apply_async(queue=self.uuid, args=args)309 enlist_nodes_from_ucsm.apply_async(queue=self.uuid, args=args)
309310
311 def enlist_nodes_from_mscm(self, host, username, password):
312 """ Add the servers from a Moonshot HP iLO Chassis Manager.
313
314 :param host: IP address for the MSCM.
315 :param username: username for MSCM.
316 :param password: password for MSCM.
317 """
318 args = (host, username, password)
319 enlist_nodes_from_mscm.apply_async(queue=self.uuid, args=args)
320
310 def add_dhcp_host_maps(self, new_leases):321 def add_dhcp_host_maps(self, new_leases):
311 if len(new_leases) > 0 and len(self.get_managed_interfaces()) > 0:322 if len(new_leases) > 0 and len(self.get_managed_interfaces()) > 0:
312 # XXX JeroenVermeulen 2012-08-21, bug=1039362: the DHCP323 # XXX JeroenVermeulen 2012-08-21, bug=1039362: the DHCP
313324
=== modified file 'src/maasserver/tests/test_api_nodegroup.py'
--- src/maasserver/tests/test_api_nodegroup.py 2014-05-06 21:18:17 +0000
+++ src/maasserver/tests/test_api_nodegroup.py 2014-07-24 05:13:03 +0000
@@ -460,6 +460,32 @@
460 matcher = MockCalledOnceWith(queue=nodegroup.uuid, args=args)460 matcher = MockCalledOnceWith(queue=nodegroup.uuid, args=args)
461 self.assertThat(mock.apply_async, matcher)461 self.assertThat(mock.apply_async, matcher)
462462
463 def test_probe_and_enlist_mscm_adds_mscm(self):
464 nodegroup = factory.make_node_group()
465 host = 'http://host'
466 username = factory.make_name('user')
467 password = factory.make_name('password')
468 self.become_admin()
469
470 mock = self.patch(nodegroup_module, 'enlist_nodes_from_mscm')
471
472 response = self.client.post(
473 reverse('nodegroup_handler', args=[nodegroup.uuid]),
474 {
475 'op': 'probe_and_enlist_mscm',
476 'host': host,
477 'username': username,
478 'password': password,
479 })
480
481 self.assertEqual(
482 httplib.OK, response.status_code,
483 explain_unexpected_response(httplib.OK, response))
484
485 args = (host, username, password)
486 matcher = MockCalledOnceWith(queue=nodegroup.uuid, args=args)
487 self.assertThat(mock.apply_async, matcher)
488
463489
464class TestNodeGroupAPIAuth(MAASServerTestCase):490class TestNodeGroupAPIAuth(MAASServerTestCase):
465 """Authorization tests for nodegroup API."""491 """Authorization tests for nodegroup API."""
466492
=== modified file 'src/maastesting/factory.py'
--- src/maastesting/factory.py 2014-03-13 02:46:10 +0000
+++ src/maastesting/factory.py 2014-07-24 05:13:03 +0000
@@ -35,6 +35,7 @@
35from uuid import uuid135from uuid import uuid1
3636
37from maastesting.fixtures import TempDirectory37from maastesting.fixtures import TempDirectory
38import mock
38from netaddr import (39from netaddr import (
39 IPAddress,40 IPAddress,
40 IPNetwork,41 IPNetwork,
@@ -264,6 +265,14 @@
264265
265 return tarball266 return tarball
266267
268 def make_streams(self, stdin=None, stdout=None, stderr=None):
269 """Make a fake return value for a SSHClient.exec_command."""
270 # stdout.read() is called so stdout can't be None.
271 if stdout is None:
272 stdout = mock.Mock()
273
274 return (stdin, stdout, stderr)
275
267276
268# Create factory singleton.277# Create factory singleton.
269factory = Factory()278factory = Factory()
270279
=== modified file 'src/provisioningserver/driver/__init__.py'
--- src/provisioningserver/driver/__init__.py 2014-06-02 20:14:18 +0000
+++ src/provisioningserver/driver/__init__.py 2014-07-24 05:13:03 +0000
@@ -134,6 +134,12 @@
134 Architecture(name="i386/generic", description="i386"),134 Architecture(name="i386/generic", description="i386"),
135 Architecture(name="amd64/generic", description="amd64"),135 Architecture(name="amd64/generic", description="amd64"),
136 Architecture(136 Architecture(
137 name="arm64/generic", description="arm64/generic",
138 pxealiases=["arm"]),
139 Architecture(
140 name="arm64/xgene-uboot", description="arm64/xgene-uboot",
141 pxealiases=["arm"]),
142 Architecture(
137 name="armhf/highbank", description="armhf/highbank",143 name="armhf/highbank", description="armhf/highbank",
138 pxealiases=["arm"], kernel_options=["console=ttyAMA0"]),144 pxealiases=["arm"], kernel_options=["console=ttyAMA0"]),
139 Architecture(145 Architecture(
140146
=== added directory 'src/provisioningserver/drivers'
=== added file 'src/provisioningserver/drivers/__init__.py'
=== added directory 'src/provisioningserver/drivers/hardware'
=== added file 'src/provisioningserver/drivers/hardware/__init__.py'
=== added file 'src/provisioningserver/drivers/hardware/mscm.py'
--- src/provisioningserver/drivers/hardware/mscm.py 1970-01-01 00:00:00 +0000
+++ src/provisioningserver/drivers/hardware/mscm.py 2014-07-24 05:13:03 +0000
@@ -0,0 +1,187 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Support for managing nodes via the Moonshot HP iLO Chassis Manager CLI.
5
6This module provides support for interacting with HP Moonshot iLO Chassis
7Management (MSCM) CLI via SSH, and for using that support to allow MAAS to
8manage systems via iLO.
9"""
10
11from __future__ import (
12 absolute_import,
13 print_function,
14 unicode_literals,
15 )
16str = None
17
18__metaclass__ = type
19__all__ = [
20 'power_control_mscm',
21 'probe_and_enlist_mscm',
22]
23
24import re
25
26from paramiko import (
27 AutoAddPolicy,
28 SSHClient,
29 )
30import provisioningserver.custom_hardware.utils as utils
31
32
33cartridge_mapping = {
34 'ProLiant Moonshot Cartridge': 'amd64/generic',
35 'ProLiant m300 Server Cartridge': 'amd64/generic',
36 'ProLiant m350 Server Cartridge': 'amd64/generic',
37 'ProLiant m400 Server Cartridge': 'arm64/xgene-uboot',
38 'ProLiant m500 Server Cartridge': 'amd64/generic',
39 'ProLiant m710 Server Cartridge': 'amd64/generic',
40 'ProLiant m800 Server Cartridge': 'armhf/keystone',
41 'Default': 'arm64/generic',
42}
43
44
45class MSCM_CLI_API(object):
46 """An API for interacting with the Moonshot iLO CM CLI."""
47
48 def __init__(self, host, username, password):
49 """MSCM_CLI_API Constructor."""
50 self.host = host
51 self.username = username
52 self.password = password
53 self._ssh = SSHClient()
54 self._ssh.set_missing_host_key_policy(AutoAddPolicy())
55
56 def _run_cli_command(self, command):
57 """Run a single command and return unparsed text from stdout."""
58 self._ssh.connect(
59 self.host, username=self.username, password=self.password)
60 try:
61 _, stdout, _ = self._ssh.exec_command(command)
62 output = stdout.read()
63 finally:
64 self._ssh.close()
65
66 return output
67
68 def discover_nodes(self):
69 """Discover all available nodes.
70
71 Example of stdout from running "show node list":
72
73 'show node list\r\r\nSlot ID Proc Manufacturer
74 Architecture Memory Power Health\r\n----
75 ----- ---------------------- --------------------
76 ------ ----- ------\r\n 01 c1n1 Intel Corporation
77 x86 Architecture 32 GB On OK \r\n 02 c2n1
78 N/A No Asset Information \r\n\r\n'
79
80 The regex 'c\d+n\d' is finding the node_id's c1-45n1-8
81 """
82 node_list = self._run_cli_command("show node list")
83 return re.findall(r'c\d+n\d', node_list)
84
85 def get_node_macaddr(self, node_id):
86 """Get node MAC address(es).
87
88 Example of stdout from running "show node macaddr <node_id>":
89
90 'show node macaddr c1n1\r\r\nSlot ID NIC 1 (Switch A)
91 NIC 2 (Switch B) NIC 3 (Switch A) NIC 4 (Switch B)\r\n
92 ---- ----- ----------------- ----------------- -----------------
93 -----------------\r\n 1 c1n1 a0:1d:48:b5:04:34 a0:1d:48:b5:04:35
94 a0:1d:48:b5:04:36 a0:1d:48:b5:04:37\r\n\r\n\r\n'
95
96 The regex '[\:]'.join(['[0-9A-F]{1,2}'] * 6) is finding
97 the MAC Addresses for the given node_id.
98 """
99 macs = self._run_cli_command("show node macaddr %s" % node_id)
100 return re.findall(r':'.join(['[0-9a-f]{2}'] * 6), macs)
101
102 def get_node_arch(self, node_id):
103 """Get node architecture.
104
105 Example of stdout from running "show node info <node_id>":
106
107 'show node info c1n1\r\r\n\r\nCartridge #1 \r\n Type: Compute\r\n
108 Manufacturer: HP\r\n Product Name: ProLiant m500 Server Cartridge\r\n'
109
110 Parsing this retrieves 'ProLiant m500 Server Cartridge'
111 """
112 node_detail = self._run_cli_command("show node info %s" % node_id)
113 cartridge = node_detail.split('Product Name: ')[1].splitlines()[0]
114 if cartridge in cartridge_mapping:
115 return cartridge_mapping[cartridge]
116 else:
117 return cartridge_mapping['Default']
118
119 def get_node_power_status(self, node_id):
120 """Get power state of node (on/off).
121
122 Example of stdout from running "show node power <node_id>":
123
124 'show node power c1n1\r\r\n\r\nCartridge #1\r\n Node #1\r\n
125 Power State: On\r\n'
126
127 Parsing this retrieves 'On'
128 """
129 power_state = self._run_cli_command("show node power %s" % node_id)
130 return power_state.split('Power State: ')[1].splitlines()[0]
131
132 def power_node_on(self, node_id):
133 """Power node on."""
134 return self._run_cli_command("set node power on %s" % node_id)
135
136 def power_node_off(self, node_id):
137 """Power node off."""
138 return self._run_cli_command("set node power off force %s" % node_id)
139
140 def configure_node_boot_m2(self, node_id):
141 """Configure HDD boot for node."""
142 return self._run_cli_command("set node boot M.2 %s" % node_id)
143
144 def configure_node_bootonce_pxe(self, node_id):
145 """Configure PXE boot for node once."""
146 return self._run_cli_command("set node bootonce pxe %s" % node_id)
147
148
149def power_control_mscm(host, username, password, node_id, power_change):
150 """Handle calls from the power template for nodes with a power type
151 of 'mscm'.
152 """
153 mscm = MSCM_CLI_API(host, username, password)
154 power_status = mscm.get_node_power_status(node_id)
155
156 if power_change == 'off':
157 mscm.power_node_off(node_id)
158 return
159
160 if power_change != 'on':
161 raise AssertionError('Unexpected maas power mode.')
162
163 if power_status == 'On':
164 mscm.power_node_off(node_id)
165
166 mscm.configure_node_bootonce_pxe(node_id)
167 mscm.power_node_on(node_id)
168
169
170def probe_and_enlist_mscm(host, username, password):
171 """ Extracts all of nodes from mscm, sets all of them to boot via HDD by,
172 default, sets them to bootonce via PXE, and then enlists them into MAAS.
173 """
174 mscm = MSCM_CLI_API(host, username, password)
175 nodes = mscm.discover_nodes()
176 for node_id in nodes:
177 # Set default boot to HDD
178 mscm.configure_node_boot_m2(node_id)
179 params = {
180 'power_address': host,
181 'power_user': username,
182 'power_pass': password,
183 'node_id': node_id,
184 }
185 arch = mscm.get_node_arch(node_id)
186 macs = mscm.get_node_macaddr(node_id)
187 utils.create_node(macs, arch, 'mscm', params)
0188
=== added directory 'src/provisioningserver/drivers/hardware/tests'
=== added file 'src/provisioningserver/drivers/hardware/tests/test_mscm.py'
--- src/provisioningserver/drivers/hardware/tests/test_mscm.py 1970-01-01 00:00:00 +0000
+++ src/provisioningserver/drivers/hardware/tests/test_mscm.py 2014-07-24 05:13:03 +0000
@@ -0,0 +1,259 @@
1# Copyright 2014 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.mscm``."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17from random import randint
18import re
19from StringIO import StringIO
20
21from maastesting.factory import factory
22from maastesting.matchers import MockCalledOnceWith
23from maastesting.testcase import MAASTestCase
24from mock import Mock
25from provisioningserver.drivers.hardware.mscm import (
26 cartridge_mapping,
27 MSCM_CLI_API,
28 power_control_mscm,
29 probe_and_enlist_mscm,
30 )
31import provisioningserver.custom_hardware.utils as utils
32
33
34def make_mscm_api():
35 """Make a MSCM_CLI_API object with randomized parameters."""
36 host = factory.make_hostname('mscm')
37 username = factory.make_name('user')
38 password = factory.make_name('password')
39 return MSCM_CLI_API(host, username, password)
40
41
42def make_node_id():
43 """Make a node_id."""
44 return 'c%sn%s' % (randint(1, 45), randint(1, 8))
45
46
47def make_show_node_list(length=10):
48 """Make a fake return value for discover_nodes."""
49 return re.findall(r'c\d+n\d', ''.join(make_node_id()
50 for _ in xrange(length)))
51
52
53def make_show_node_macaddr(length=10):
54 """Make a fake return value for get_node_macaddr."""
55 return ''.join((factory.getRandomMACAddress() + ' ')
56 for _ in xrange(length))
57
58
59class TestRunCliCommand(MAASTestCase):
60 """Tests for ``MSCM_CLI_API.run_cli_command``."""
61
62 def test_returns_output(self):
63 api = make_mscm_api()
64 ssh_mock = self.patch(api, '_ssh')
65 expected = factory.make_name('output')
66 stdout = StringIO(expected)
67 streams = factory.make_streams(stdout=stdout)
68 ssh_mock.exec_command = Mock(return_value=streams)
69 output = api._run_cli_command(factory.make_name('command'))
70 self.assertEqual(expected, output)
71
72 def test_connects_and_closes_ssh_client(self):
73 api = make_mscm_api()
74 ssh_mock = self.patch(api, '_ssh')
75 ssh_mock.exec_command = Mock(return_value=factory.make_streams())
76 api._run_cli_command(factory.make_name('command'))
77 self.assertThat(
78 ssh_mock.connect,
79 MockCalledOnceWith(
80 api.host, username=api.username, password=api.password))
81 self.assertThat(ssh_mock.close, MockCalledOnceWith())
82
83 def test_closes_when_exception_raised(self):
84 api = make_mscm_api()
85 ssh_mock = self.patch(api, '_ssh')
86
87 def fail():
88 raise Exception('fail')
89
90 ssh_mock.exec_command = Mock(side_effect=fail)
91 command = factory.make_name('command')
92 self.assertRaises(Exception, api._run_cli_command, command)
93 self.assertThat(ssh_mock.close, MockCalledOnceWith())
94
95
96class TestDiscoverNodes(MAASTestCase):
97 """Tests for ``MSCM_CLI_API.discover_nodes``."""
98
99 def test_discover_nodes(self):
100 api = make_mscm_api()
101 ssh_mock = self.patch(api, '_ssh')
102 expected = make_show_node_list()
103 stdout = StringIO(expected)
104 streams = factory.make_streams(stdout=stdout)
105 ssh_mock.exec_command = Mock(return_value=streams)
106 output = api.discover_nodes()
107 self.assertEqual(expected, output)
108
109
110class TestNodeMACAddress(MAASTestCase):
111 """Tests for ``MSCM_CLI_API.get_node_macaddr``."""
112
113 def test_get_node_macaddr(self):
114 api = make_mscm_api()
115 expected = make_show_node_macaddr()
116 cli_mock = self.patch(api, '_run_cli_command')
117 cli_mock.return_value = expected
118 node_id = make_node_id()
119 output = api.get_node_macaddr(node_id)
120 self.assertEqual(re.findall(r':'.join(['[0-9a-f]{2}'] * 6),
121 expected), output)
122
123
124class TestNodeArch(MAASTestCase):
125 """Tests for ``MSCM_CLI_API.get_node_arch``."""
126
127 def test_get_node_arch(self):
128 api = make_mscm_api()
129 expected = '\r\n Product Name: ProLiant Moonshot Cartridge\r\n'
130 cli_mock = self.patch(api, '_run_cli_command')
131 cli_mock.return_value = expected
132 node_id = make_node_id()
133 output = api.get_node_arch(node_id)
134 key = expected.split('Product Name: ')[1].splitlines()[0]
135 self.assertEqual(cartridge_mapping[key], output)
136
137
138class TestGetNodePowerStatus(MAASTestCase):
139 """Tests for ``MSCM_CLI_API.get_node_power_status``."""
140
141 def test_get_node_power_status(self):
142 api = make_mscm_api()
143 expected = '\r\n Node #1\r\n Power State: On\r\n'
144 cli_mock = self.patch(api, '_run_cli_command')
145 cli_mock.return_value = expected
146 node_id = make_node_id()
147 output = api.get_node_power_status(node_id)
148 self.assertEqual(expected.split('Power State: ')[1].splitlines()[0],
149 output)
150
151
152class TestPowerAndConfigureNode(MAASTestCase):
153 """Tests for ``MSCM_CLI_API.configure_node_bootonce_pxe,
154 MSCM_CLI_API.power_node_on, and MSCM_CLI_API.power_node_off``.
155 """
156
157 scenarios = [
158 ('power_node_on()',
159 dict(method='power_node_on')),
160 ('power_node_off()',
161 dict(method='power_node_off')),
162 ('configure_node_bootonce_pxe()',
163 dict(method='configure_node_bootonce_pxe')),
164 ]
165
166 def test_returns_expected_outout(self):
167 api = make_mscm_api()
168 ssh_mock = self.patch(api, '_ssh')
169 expected = factory.make_name('output')
170 stdout = StringIO(expected)
171 streams = factory.make_streams(stdout=stdout)
172 ssh_mock.exec_command = Mock(return_value=streams)
173 output = getattr(api, self.method)(make_node_id())
174 self.assertEqual(expected, output)
175
176
177class TestPowerControlMSCM(MAASTestCase):
178 """Tests for ``power_control_ucsm``."""
179
180 def test_power_control_mscm_on_on(self):
181 # power_change and power_status are both 'on'
182 host = factory.make_hostname('mscm')
183 username = factory.make_name('user')
184 password = factory.make_name('password')
185 node_id = make_node_id()
186 bootonce_mock = self.patch(MSCM_CLI_API, 'configure_node_bootonce_pxe')
187 power_status_mock = self.patch(MSCM_CLI_API, 'get_node_power_status')
188 power_status_mock.return_value = 'On'
189 power_node_on_mock = self.patch(MSCM_CLI_API, 'power_node_on')
190 power_node_off_mock = self.patch(MSCM_CLI_API, 'power_node_off')
191
192 power_control_mscm(host, username, password, node_id,
193 power_change='on')
194 self.assertThat(bootonce_mock, MockCalledOnceWith(node_id))
195 self.assertThat(power_node_off_mock, MockCalledOnceWith(node_id))
196 self.assertThat(power_node_on_mock, MockCalledOnceWith(node_id))
197
198 def test_power_control_mscm_on_off(self):
199 # power_change is 'on' and power_status is 'off'
200 host = factory.make_hostname('mscm')
201 username = factory.make_name('user')
202 password = factory.make_name('password')
203 node_id = make_node_id()
204 bootonce_mock = self.patch(MSCM_CLI_API, 'configure_node_bootonce_pxe')
205 power_status_mock = self.patch(MSCM_CLI_API, 'get_node_power_status')
206 power_status_mock.return_value = 'Off'
207 power_node_on_mock = self.patch(MSCM_CLI_API, 'power_node_on')
208
209 power_control_mscm(host, username, password, node_id,
210 power_change='on')
211 self.assertThat(bootonce_mock, MockCalledOnceWith(node_id))
212 self.assertThat(power_node_on_mock, MockCalledOnceWith(node_id))
213
214 def test_power_control_mscm_off_on(self):
215 # power_change is 'off' and power_status is 'on'
216 host = factory.make_hostname('mscm')
217 username = factory.make_name('user')
218 password = factory.make_name('password')
219 node_id = make_node_id()
220 power_status_mock = self.patch(MSCM_CLI_API, 'get_node_power_status')
221 power_status_mock.return_value = 'On'
222 power_node_off_mock = self.patch(MSCM_CLI_API, 'power_node_off')
223
224 power_control_mscm(host, username, password, node_id,
225 power_change='off')
226 self.assertThat(power_node_off_mock, MockCalledOnceWith(node_id))
227
228
229class TestProbeAndEnlistMSCM(MAASTestCase):
230 """Tests for ``probe_and_enlist_mscm``."""
231
232 def test_probe_and_enlist(self):
233 host = factory.make_hostname('mscm')
234 username = factory.make_name('user')
235 password = factory.make_name('password')
236 node_id = make_node_id()
237 macs = make_show_node_macaddr(4)
238 arch = 'arm64/xgene-uboot'
239 discover_nodes_mock = self.patch(MSCM_CLI_API, 'discover_nodes')
240 discover_nodes_mock.return_value = [node_id]
241 boot_m2_mock = self.patch(MSCM_CLI_API, 'configure_node_boot_m2')
242 node_arch_mock = self.patch(MSCM_CLI_API, 'get_node_arch')
243 node_arch_mock.return_value = arch
244 node_macs_mock = self.patch(MSCM_CLI_API, 'get_node_macaddr')
245 node_macs_mock.return_value = macs
246 create_node_mock = self.patch(utils, 'create_node')
247 probe_and_enlist_mscm(host, username, password)
248 self.assertThat(discover_nodes_mock, MockCalledOnceWith())
249 self.assertThat(boot_m2_mock, MockCalledOnceWith(node_id))
250 self.assertThat(node_arch_mock, MockCalledOnceWith(node_id))
251 self.assertThat(node_macs_mock, MockCalledOnceWith(node_id))
252 params = {
253 'power_address': host,
254 'power_user': username,
255 'power_pass': password,
256 'node_id': node_id,
257 }
258 self.assertThat(create_node_mock,
259 MockCalledOnceWith(macs, arch, 'mscm', params))
0260
=== modified file 'src/provisioningserver/power/tests/test_poweraction.py'
--- src/provisioningserver/power/tests/test_poweraction.py 2014-05-21 17:30:26 +0000
+++ src/provisioningserver/power/tests/test_poweraction.py 2014-07-24 05:13:03 +0000
@@ -220,3 +220,14 @@
220 power_user='bar', power_pass='baz',220 power_user='bar', power_pass='baz',
221 uuid=factory.getRandomUUID(), power_change='on')221 uuid=factory.getRandomUUID(), power_change='on')
222 self.assertIn('power_control_ucsm', script)222 self.assertIn('power_control_ucsm', script)
223
224 def test_mscm_renders_template(self):
225 # I'd like to assert that escape_py_literal is being used here,
226 # but it's not obvious how to mock things in the template
227 # rendering namespace so I passed on that.
228 action = PowerAction('mscm')
229 script = action.render_template(
230 action.get_template(), power_address='foo',
231 power_user='bar', power_pass='baz',
232 node_id='c1n1', power_change='on')
233 self.assertIn('power_control_mscm', script)
223234
=== modified file 'src/provisioningserver/power_schema.py'
--- src/provisioningserver/power_schema.py 2014-05-21 17:30:26 +0000
+++ src/provisioningserver/power_schema.py 2014-07-24 05:13:03 +0000
@@ -255,4 +255,17 @@
255 make_json_field('power_pass', "API password"),255 make_json_field('power_pass', "API password"),
256 ],256 ],
257 },257 },
258 {
259 'name': 'mscm',
260 'description': "Moonshot HP iLO Chassis Manager",
261 'fields': [
262 make_json_field('power_address', "IP for MSCM CLI API"),
263 make_json_field('power_user', "MSCM CLI API user"),
264 make_json_field('power_pass', "MSCM CLI API password"),
265 make_json_field(
266 'node_id',
267 "Node ID - Must adhere to cXnY format "
268 "(X=cartridge number, Y=node number)."),
269 ],
270 },
258]271]
259272
=== modified file 'src/provisioningserver/tasks.py'
--- src/provisioningserver/tasks.py 2014-06-02 07:48:17 +0000
+++ src/provisioningserver/tasks.py 2014-07-24 05:13:03 +0000
@@ -58,6 +58,7 @@
58 set_up_options_conf,58 set_up_options_conf,
59 setup_rndc,59 setup_rndc,
60 )60 )
61from provisioningserver.drivers.hardware.mscm import probe_and_enlist_mscm
61from provisioningserver.omshell import Omshell62from provisioningserver.omshell import Omshell
62from provisioningserver.power.poweraction import (63from provisioningserver.power.poweraction import (
63 PowerAction,64 PowerAction,
@@ -493,5 +494,12 @@
493@task494@task
494@log_exception_text495@log_exception_text
495def enlist_nodes_from_ucsm(url, username, password):496def enlist_nodes_from_ucsm(url, username, password):
496 """ See `maasserver.api.NodeGroupsHandler.enlist_nodes_from_ucsm`. """497 """ See `maasserver.api.NodeGroupHandler.enlist_nodes_from_ucsm`. """
497 probe_and_enlist_ucsm(url, username, password)498 probe_and_enlist_ucsm(url, username, password)
499
500
501@task
502@log_exception_text
503def enlist_nodes_from_mscm(host, username, password):
504 """ See `maasserver.api.NodeGroupHandler.enlist_nodes_from_mscm`. """
505 probe_and_enlist_mscm(host, username, password)
498506
=== modified file 'src/provisioningserver/tests/test_tasks.py'
--- src/provisioningserver/tests/test_tasks.py 2014-06-02 07:48:17 +0000
+++ src/provisioningserver/tests/test_tasks.py 2014-07-24 05:13:03 +0000
@@ -73,6 +73,7 @@
73from provisioningserver.tags import MissingCredentials73from provisioningserver.tags import MissingCredentials
74from provisioningserver.tasks import (74from provisioningserver.tasks import (
75 add_new_dhcp_host_map,75 add_new_dhcp_host_map,
76 enlist_nodes_from_mscm,
76 enlist_nodes_from_ucsm,77 enlist_nodes_from_ucsm,
77 import_boot_images,78 import_boot_images,
78 Omshell,79 Omshell,
@@ -661,3 +662,14 @@
661 mock = self.patch(tasks, 'probe_and_enlist_ucsm')662 mock = self.patch(tasks, 'probe_and_enlist_ucsm')
662 enlist_nodes_from_ucsm(url, username, password)663 enlist_nodes_from_ucsm(url, username, password)
663 self.assertThat(mock, MockCalledOnceWith(url, username, password))664 self.assertThat(mock, MockCalledOnceWith(url, username, password))
665
666
667class TestAddMSCM(PservTestCase):
668
669 def test_enlist_nodes_from_mscm(self):
670 host = 'host'
671 username = 'username'
672 password = 'password'
673 mock = self.patch(tasks, 'probe_and_enlist_mscm')
674 enlist_nodes_from_mscm(host, username, password)
675 self.assertThat(mock, MockCalledOnceWith(host, username, password))

Subscribers

People subscribed via source and target branches

to all changes: