Merge lp:~rvb/maas-test/power-type-support into lp:maas-test

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: 166
Merged at revision: 157
Proposed branch: lp:~rvb/maas-test/power-type-support
Merge into: lp:maas-test
Diff against target: 596 lines (+332/-70)
8 files modified
docs/man/maas-test.8.rst (+43/-9)
maastest/cases.py (+57/-36)
maastest/kvmfixture.py (+10/-5)
maastest/main.py (+10/-0)
maastest/parser.py (+79/-9)
maastest/tests/test_utils.py (+54/-1)
maastest/utils.py (+36/-1)
man/maas-test.8 (+43/-9)
To merge this branch: bzr merge lp:~rvb/maas-test/power-type-support
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+217350@code.launchpad.net

Commit message

Add two parameters, --power-type and --power-parameters, to pass the power details about a node. This allows maas-test to support any power mechanism MAAS itself support instead of supporting only IPMI.

Description of the change

Notes:

- The legacy --bmc-* parameters (used when the node is IPMI-based) are still supported: the value of the --bmc-* parameters is converted into power-type and power-parameters.

- the trick use to power up the node to get it enlistment can appear a bit fishy but the nice thing about it is that is relies exclusively on API methods that won't change any time soon; this, I think, is much better than call MAAS' code directly.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thanks for working on this. The timeout is no longer needed, I suppose, now that the API client is fixed.

Is there a race condition between the node booting and maas-test deleting it? I suppose it won't matter until we get magical ultra-fast speed-booting nodes.

I have a few syntactical questions about the documentation:

  $ sudo maas-test --maas-series trusty --architecture amd64
  --power-type=amt --power-parameters=\"mac_address=aa:bb:cc:dd:ee:ff
  power_pass='my password'"

What does it mean for the opening double-quote after --power-parameters to be escaped, but not the closing quote? And, do the single quotes inside the quoted dict really work the way you'd expect? Who parses them?

I would like to recommend a few style rules for technical writing, e.g:

                    "You need to provide all the details required to power "
                    "control the node under test. "
                    "You need to provide the --bmc-* parameters if the node "
                    "supports IPMI or use --power-type and --power-parameters "
                    "if you want to use any of the other power types "
                    "supported by MAAS (see %s for a list of the supported "
                    "power types)."

Try to keep elements of a sentence in an easy-to-understand order, so readers don't have to remember or re-read text that may only make sense later. Limit the nesting of phrases. That second sentence is structured like “A if B or C if D (E)” — that's quite hard to read if you're confused, annoyed, under pressure, or not very good at English.

Only use "[statement] if [condition]" if [statement] is very brief and very simple. Even then, keep an eye out for ambiguity around ‘and’/‘or’: is it part of the condition? Or does it start a whole new statement? These are parsing ambiguities. If the reader picks the wrong interpretation at first, they will hit a “parse error” later and have to re-read, which is annoying.

So: don't try to cram all relevant information into your sentence. See how _little_ information you can put in a sentence while still making a clear chain of steps towards full understanding. Good technical writing follows Antoine de Saint Exupéry's rule: “perfection is achieved, not when there is nothing more to add, but when there is nothing more to take away.”

The same things go for these messages:

                self.error(
                    "All the BMC details (MAC address or IP address, "
                    "username and password) must be provided."
                )

...

                self.error(
                    "Value for both --power-type and --power-parameters must "
                    "be provided."

Can you find a way to make these easier to read? Changing from passive voice to active voice is usually a good step.

But back to code. In convert_bmc_details_into_parameters you use an OrderedDict to produce consistent ordering — but the ordering isn't documented. Wouldn't it be just as easy to make the tests run parameters.split() and do an assertItemsEqual on the result? Or use sorted() inside convert_bmc_details_into_parameters?

review: Approve
lp:~rvb/maas-test/power-type-support updated
165. By Raphaël Badin

Review fixes.

166. By Raphaël Badin

Fix test.

Revision history for this message
Raphaël Badin (rvb) wrote :

> Thanks for working on this. The timeout is no longer needed, I suppose, now
> that the API client is fixed.
>
> Is there a race condition between the node booting and maas-test deleting it?
> I suppose it won't matter until we get magical ultra-fast speed-booting nodes.
>
> I have a few syntactical questions about the documentation:
>
> $ sudo maas-test --maas-series trusty --architecture amd64
> --power-type=amt --power-parameters=\"mac_address=aa:bb:cc:dd:ee:ff
> power_pass='my password'"
>
> What does it mean for the opening double-quote after --power-parameters to be
> escaped, but not the closing quote?

An oversight that had no consequence because of the way the man page is rendered. Fixed.

> And, do the single quotes inside the
> quoted dict really work the way you'd expect? Who parses them?

Yes, there is a test for this. All the parsing is done by utils.make_json_power_parameters().

>
> I would like to recommend a few style rules for technical writing, e.g:
> [...]

Done.

>
> The same things go for these messages:
> [...]

Done.

> But back to code. In convert_bmc_details_into_parameters you use an
> OrderedDict to produce consistent ordering — but the ordering isn't
> documented. Wouldn't it be just as easy to make the tests run
> parameters.split() and do an assertItemsEqual on the result? Or use sorted()
> inside convert_bmc_details_into_parameters?

Good idea, done.

Revision history for this message
Raphaël Badin (rvb) wrote :

> Thanks for working on this. The timeout is no longer needed, I suppose, now
> that the API client is fixed.

Well, I still want this to work with the package published in Trusty so I'll keep the workaround for now. When the fix gets SRUed, we will think about removing it.

> Is there a race condition between the node booting and maas-test deleting it?
> I suppose it won't matter until we get magical ultra-fast speed-booting nodes.

Yeah, I think this is safe :).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/man/maas-test.8.rst'
2--- docs/man/maas-test.8.rst 2014-04-23 14:47:13 +0000
3+++ docs/man/maas-test.8.rst 2014-04-30 06:58:59 +0000
4@@ -185,30 +185,64 @@
5 MAC address for the node's baseboard management controller. MAAS will
6 control the node's power and boot sequence through this controller.
7 It must be attached to the testing network. This is mutually
8- exclusive with **--bmc-ip**. This option is not needed in
9- interactive mode. In non-interactive mode, either **--bmc-mac** or
10- **--bmc-ip** is required.
11+ exclusive with **--bmc-ip**. In non-interactive mode, either **--bmc-mac**
12+ or **--bmc-ip** is required. Not needed in interactive mode or when using
13+ **--power-type** and **--power-parameters**.
14
15 --bmc-ip=IP
16 IP address of the node's baseboard management controller. Use this if
17 the BMC is not connected to the interface given as argument. Note
18 that the IP address must not change for the duration of the testing.
19- This is mutually exclusive with **--bmc-mac**. This option is not
20- needed in interactive mode. In non-interactive mode, either
21- **--bmc-mac** or **--bmc-ip** is required.
22+ This is mutually exclusive with **--bmc-mac**. In non-interactive mode,
23+ either **--bmc-mac** or **--bmc-ip** is required.
24+ Not needed in interactive mode or when using --power-type and
25+ --power-parameters.
26
27 --bmc-password=password
28 Password for IPMI authentication on the BMC. Use with **--bmc-user**.
29- Not needed in interactive mode.
30+ Not needed in interactive mode or when using **--power-type** and
31+ **--power-parameters**.
32
33 --bmc-user=user
34 Username for IPMI authentication. Use with **--bmc-password**.
35- Not needed in interactive mode.
36+ Not needed in interactive mode or when using **--power-type** and
37+ **--power-parameters**.
38
39 --ipmi-driver=driver
40 Specify IPMI driver version. Default is LAN_2_0 (IPMI v2.0), which
41 is what most modern BMCs support. Use the LAN option if your BMC only supports
42- IPMI version 1.5.
43+ IPMI version 1.5. Not needed in interactive mode or if using **--power-type** and
44+ **--power-parameters**.
45+
46+--power-type=power_type
47+ Power type of the node under test (e.g. 'ipmi', 'amt', etc.
48+ See http://maas.ubuntu.com/docs/api.html#power-types for a list of the supported
49+ power types and their related power parameters).
50+ Using **--power-type** and **--power-parameters** is an alternative to using
51+ the bmc-* arguments that allows to use any one of the power mechanisms
52+ MAAS supports.
53+
54+ # Example: testing a node with an Active Management Technology (AMT) interface:
55+
56+ $ sudo maas-test --maas-series trusty --architecture amd64
57+ --power-type=amt --power-parameters="mac_address=aa:bb:cc:dd:ee:ff
58+ power_pass='my password'"
59+
60+ # Example: testing a node with an Intelligent Platform Management Interface (IPMI):
61+
62+ $ sudo maas-test --maas-series trusty --architecture amd64
63+ --power-type=ipmi --power-parameters="power_address=192.168.2.3 power_user='my user'
64+ power_pass='my password' power_driver=LAN_2_0"
65+
66+--power-parameters=power_parameters
67+ Power parameters of the node under test (See
68+ http://maas.ubuntu.com/docs/api.html#power-types for a list of the supported power
69+ types and their related power parameters). This is a dict-like expression of the
70+ power parameters for the specified power type.
71+ Using **--power-type** and **--power-parameters** is an alternative to using
72+ the bmc-* arguments that allows to use any one of the power mechanisms
73+ MAAS supports.
74+ See the documentation for **--power-type** for examples.
75
76 --interactive
77 Interactive mode. Instead of powering up the node automatically through
78
79=== modified file 'maastest/cases.py'
80--- maastest/cases.py 2014-04-28 13:44:34 +0000
81+++ maastest/cases.py 2014-04-30 06:58:59 +0000
82@@ -14,15 +14,19 @@
83 "TestOneNode",
84 ]
85
86+from functools import partial
87 import httplib
88 import json
89+import socket
90 import sys
91 from time import sleep
92+import urllib2
93
94 from maastest import utils
95 from maastest.maas_enums import NODE_STATUS
96 import testresources
97 import testtools
98+from testtools.monkey import MonkeyPatcher
99 from testtools.testcase import gather_details
100
101
102@@ -112,42 +116,59 @@
103 def power_up_node_noninteractive(self):
104 """Power up the node using the provided power parameters."""
105 # Power-cycle the node.
106- # TODO: Move these into MAASFixture?
107- power_address = self.find_bmc_ip_address()
108- self.maas.kvm_fixture.run_command([
109- 'ipmipower', '-h', power_address,
110- '-W', 'opensesspriv',
111- '-D', self.args.ipmi_driver,
112- '-u', self.args.bmc_username,
113- '-p', self.args.bmc_password, '--off'],
114- check_call=True)
115- self.maas.kvm_fixture.run_command([
116- 'ipmipower', '-h', power_address,
117- '-W', 'opensesspriv',
118- '-D', self.args.ipmi_driver,
119- '-u', self.args.bmc_username,
120- '-p', self.args.bmc_password, '--on'],
121- check_call=True)
122-
123- def find_bmc_ip_address(self):
124- """Returns the BMC IP address.
125-
126- This is done by scanning the network it the BMC MAC address was given
127- or by simply returning the IP address of the BMC is it was passed.
128- """
129- if self.args.bmc_mac is not None:
130- # The BMC's MAC address was passed: scan the network to find the
131- # IP address of the node's BMC.
132- for retry in utils.retries(delay=20, timeout=5 * 60):
133- ip_scan = self.maas.kvm_fixture.get_ip_from_network_scan
134- power_address = ip_scan(self.args.bmc_mac)
135- if power_address is not None:
136- return power_address
137- else:
138- # The BMC IP address was passed: just return that.
139- return self.args.bmc_ip
140- if power_address is None:
141- self.fail("Failed to get the IP address of the BMC.")
142+ # We use a little trick here to be able to test enlistment while
143+ # leveraging MAAS' capabilities to boot a node for all the
144+ # supported power types. In order to get a node enlisted, this code:
145+ # - adds a node using the client library (passing the power type
146+ # and all the power parameters given on the command line). Since
147+ # the node is getting enlisted by an admin user, the node will be
148+ # started.
149+ # - deletes the node.
150+ # The node will now be unknown to MAAS and will thus be enlisted
151+ # as a new node.
152+ #
153+ # 0. Force a network scan: this populates the ARP cache used to lookup
154+ # IP addresses using MAC addresses.
155+ self.maas.kvm_fixture.get_network_scan()
156+
157+ # 1. Register the node.
158+ nodes_uri = utils.get_uri('nodes/')
159+ cluster_uuid = self.maas.get_master_ng_uuid()
160+ json_power_parameters = utils.make_json_power_parameters(
161+ self.args.power_parameters)
162+ response = self.maas.admin_maas_client.post(
163+ nodes_uri, op='new',
164+ architecture=self.args.architecture,
165+ nodegroup=cluster_uuid,
166+ power_type=self.args.power_type,
167+ power_parameters=json_power_parameters,
168+ # Use a bogus MAC address as one MAC address is required.
169+ # Its value will be overridden by the value passed in
170+ # the power parameters.
171+ mac_addresses=['aa:bb:cc:dd:ee:ff'],
172+ )
173+ if response.code != httplib.OK:
174+ raise self.fail(
175+ "Failed to power up node (registration): %s" % (
176+ response.content))
177+ node_dict = json.loads(response.read())
178+ node_uri = utils.get_uri('nodes/%s/' % node_dict['system_id'])
179+
180+ # 2. Delete the node.
181+ # XXX: rvb 2014-03-28 bug=https://bugs.launchpad.net/maas/+bug/1313556
182+ # Workaround a bug affecting the API client (deleting a resource
183+ # blocks) by forcing a timeout for the request.
184+ urlopen_with_timeout = partial(urllib2.urlopen, timeout=10)
185+ patcher = MonkeyPatcher()
186+ from apiclient import maas_client
187+ patcher.add_patch(maas_client.urllib2, "urlopen", urlopen_with_timeout)
188+
189+ def delete_and_survive_timeout():
190+ try:
191+ self.maas.admin_maas_client.delete(node_uri)
192+ except socket.timeout:
193+ pass
194+ response = patcher.run_with_patches(delete_and_survive_timeout)
195
196 def test_enlist_node(self):
197 """Enlist node."""
198
199=== modified file 'maastest/kvmfixture.py'
200--- maastest/kvmfixture.py 2014-04-26 08:38:08 +0000
201+++ maastest/kvmfixture.py 2014-04-30 06:58:59 +0000
202@@ -435,15 +435,20 @@
203 ['scp'] + self._get_base_ssh_options() + [source, remote_dest],
204 check_call=True)
205
206- def get_ip_from_network_scan(self, mac_address):
207- """Return the IP address associated with a MAC address.
208-
209- The IP address is found by scanning the direct network using nmap.
210- """
211+ def get_network_scan(self):
212+ """Return the result of scanning the 'direct' network using `nmap`."""
213 network_repr = "%s" % self.direct_network
214 nmap_scan_cmd = ['sudo', 'nmap', '-sP', network_repr, '-oX', '-']
215 _, output, _ = self.run_command(
216 nmap_scan_cmd, check_call=True)
217+ return output
218+
219+ def get_ip_from_network_scan(self, mac_address):
220+ """Return the IP address associated with a MAC address.
221+
222+ The IP address is found by scanning the 'direct' network using nmap.
223+ """
224+ output = self.get_network_scan()
225 mapping = extract_mac_ip_mapping(output)
226 ip = mapping.get(mac_address.upper())
227 if ip is not None:
228
229=== modified file 'maastest/main.py'
230--- maastest/main.py 2014-04-10 10:08:14 +0000
231+++ maastest/main.py 2014-04-30 06:58:59 +0000
232@@ -284,6 +284,16 @@
233 check_against_virtual_host()
234 check_against_dhcp_servers(args.interface)
235
236+ # Support legacy BMC details-passing:
237+ if args.bmc_username is not None:
238+ # Generate args.power_type and args.power_parameters from the
239+ # bmc_* arguments.
240+ args.power_type = 'ipmi'
241+ args.power_parameters = (
242+ utils.convert_bmc_details_into_parameters(
243+ args.bmc_mac, args.bmc_ip, args.bmc_username,
244+ args.bmc_password, args.ipmi_driver))
245+
246 proxy_url, proxy_fixture = set_up_proxy(args)
247 machine_fixture = create_vm(args, proxy_url)
248 machine_fixture.setUp()
249
250=== modified file 'maastest/parser.py'
251--- maastest/parser.py 2014-04-10 08:16:16 +0000
252+++ maastest/parser.py 2014-04-30 06:58:59 +0000
253@@ -21,6 +21,10 @@
254 from six import text_type
255
256
257+# Link to the documentation about power types and power parameters.
258+POWER_TYPES_DOC_URL = 'http://maas.ubuntu.com/docs/api.html#power-types'
259+
260+
261 class MAASTestArgumentParser(argparse.ArgumentParser):
262
263 def parse_args(self, *args, **kwargs):
264@@ -40,10 +44,41 @@
265 (args.bmc_mac is not None or args.bmc_ip is not None) and
266 args.bmc_username is not None and
267 args.bmc_password is not None)
268- if not bmc_details_all_set:
269- self.error(
270- "All the BMC details (MAC address or IP address, username "
271- "and password) must be provided.")
272+ one_bmc_param_set = (
273+ args.bmc_mac is not None or args.bmc_ip is not None or
274+ args.bmc_username is not None or
275+ args.bmc_password is not None)
276+
277+ power_details_all_set = (
278+ args.power_type is not None and
279+ args.power_parameters is not None)
280+ one_power_details_all_set = (
281+ args.power_type is not None or
282+ args.power_parameters is not None)
283+
284+ if not one_bmc_param_set and not one_power_details_all_set:
285+ self.error(
286+ "You need to provide all the details required to power "
287+ "control the node under test. "
288+ "If the node supports IPMI, you can use the legacy "
289+ "--bmc-* parameters. For any other power type supported "
290+ "by MAAS (see %s for a list of the supported "
291+ "power types), including IPMI, you can use --power-type "
292+ "and --power-parameters." % POWER_TYPES_DOC_URL
293+ )
294+
295+ if one_bmc_param_set and not bmc_details_all_set:
296+ self.error(
297+ "You must provide all the BMC details (MAC address "
298+ "or IP address, username and password)."
299+ )
300+
301+ if one_power_details_all_set and not power_details_all_set:
302+ self.error(
303+ "You must provide values for both --power-type and "
304+ "--power-parameters."
305+ )
306+
307 return args
308
309
310@@ -80,22 +115,26 @@
311 help="MAC address of the node's baseboard management controller. "
312 "Use this if the BMC is connected to the interface given as "
313 "argument. In non-interactive mode, either --bmc-mac or "
314- "--bmc-ip. Not needed in interactive mode.")
315+ "--bmc-ip. Not needed in interactive mode or if using "
316+ "--power-type and --power-parameters.")
317 parser.add_argument(
318 '--bmc-ip', type=text_type,
319 help="IP address of the node's baseboard management controller. "
320 "Use this if the BMC is not connected to the inteface given as "
321 "argument. Note that the IP address must not change for the "
322 "duration of the testing. In non-interactive mode, either "
323- "--bmc-mac or --bmc-ip. Not needed in interactive mode.")
324+ "--bmc-mac or --bmc-ip. Not needed in interactive mode or if "
325+ "using --power-type and --power-parameters.")
326 parser.add_argument(
327 '--bmc-username', type=text_type,
328 help="Username for authenticating to the node's BMC. "
329- "Not needed in interactive mode.")
330+ "Not needed in interactive mode or if "
331+ "using --power-type and --power-parameters.")
332 parser.add_argument(
333 '--bmc-password', type=text_type,
334 help="Password for authenticating to the node's BMC. "
335- "Not needed in interactive mode.")
336+ "Not needed in interactive mode or if "
337+ "using --power-type and --power-parameters.")
338 parser.add_argument(
339 '--ipmi-driver', type=text_type, default='LAN_2_0',
340 help="IPMI driver type for the node's BMC. Defaults to LAN_2_0 "
341@@ -103,7 +142,38 @@
342 "Other option is LAN (IPMI version 1.5). Many BMCs can work "
343 "with either IPMI 1.5 or 2.0, but, some BMCs such as HP's iLO "
344 "only use IPMI 2.0. "
345- "You may need to pass LAN if your BMC only supports IPMI 1.5.")
346+ "You may need to pass LAN if your BMC only supports IPMI 1.5. "
347+ "Not needed in interactive mode or if "
348+ "using --power-type and --power-parameters.")
349+
350+ # Power type and power parameters.
351+ parser.add_argument(
352+ '--power-type', type=text_type,
353+ help=(
354+ "Power type of the node under test (e.g. 'ipmi', 'amt', etc. "
355+ "See %s for a list of the supported "
356+ "power types and their related power parameters). "
357+ "Using --power-type and --power-parameters is an alternative to "
358+ "using the bmc-* arguments that allows to use any one of the "
359+ "power mechanisms MAAS supports. See the manpage for examples "
360+ "on how to use --power-type and --power-parameters."
361+ % POWER_TYPES_DOC_URL
362+ )
363+ )
364+ parser.add_argument(
365+ '--power-parameters', type=text_type,
366+ help=(
367+ "Power parameters of the node under test (See %s "
368+ "for a list of the supported power types and their related power "
369+ "parameters). This is a dict-like expression of the "
370+ "power parameters for the specified power type. "
371+ "Using --power-type and --power-parameters is an alternative to "
372+ "using the bmc-* arguments that allows to use any one of the "
373+ "power mechanisms MAAS supports. "
374+ "See the documentation for --power-type for examples."
375+ % POWER_TYPES_DOC_URL
376+ )
377+ )
378
379 # VM details.
380 parser.add_argument(
381
382=== modified file 'maastest/tests/test_utils.py'
383--- maastest/tests/test_utils.py 2014-04-25 15:46:59 +0000
384+++ maastest/tests/test_utils.py 2014-04-30 06:58:59 +0000
385@@ -16,10 +16,11 @@
386 import os.path
387 from pipes import quote
388 import platform
389+import re
390+import shlex
391 from subprocess import PIPE
392 from textwrap import dedent
393 import time
394-import re
395
396 import distro_info
397 from fixtures import TempDir
398@@ -470,3 +471,55 @@
399 self.assertEqual(
400 '%s~(x\\.y\\*)' % key,
401 utils.compose_filter(key, ['x.y*']))
402+
403+
404+class TestBMCDetailsConversion(testtools.TestCase):
405+
406+ def test_convert_parameters_with_power_address(self):
407+ ip = self.getUniqueString()
408+ username = self.getUniqueString()
409+ password = self.getUniqueString()
410+ driver = self.getUniqueString()
411+ parameters = utils.convert_bmc_details_into_parameters(
412+ None, ip, username, password, driver)
413+ self.assertItemsEqual(
414+ [
415+ "power_user=%s" % username,
416+ "power_pass=%s" % password,
417+ "power_driver=%s" % driver,
418+ "power_address=%s" % ip,
419+ ],
420+ shlex.split(parameters))
421+
422+ def test_convert_parameters_with_mac_address(self):
423+ mac = self.getUniqueString()
424+ username = self.getUniqueString()
425+ password = self.getUniqueString()
426+ driver = self.getUniqueString()
427+ parameters = utils.convert_bmc_details_into_parameters(
428+ mac, None, username, password, driver)
429+ self.assertItemsEqual(
430+ [
431+ "power_user=%s" % username,
432+ "power_pass=%s" % password,
433+ "power_driver=%s" % driver,
434+ "power_address=",
435+ "mac_address=%s" % mac,
436+ ],
437+ shlex.split(parameters))
438+
439+ def test_convert_parameters_that_need_quoting(self):
440+ ip = self.getUniqueString()
441+ username = self.getUniqueString() + "'+" + self.getUniqueString()
442+ password = "'" + self.getUniqueString() + "'"
443+ driver = self.getUniqueString()
444+ parameters = utils.convert_bmc_details_into_parameters(
445+ None, ip, username, password, driver)
446+ self.assertItemsEqual(
447+ [
448+ "power_user=%s" % username,
449+ "power_pass=%s" % password,
450+ "power_driver=%s" % driver,
451+ "power_address=%s" % ip,
452+ ],
453+ shlex.split(parameters))
454
455=== modified file 'maastest/utils.py'
456--- maastest/utils.py 2014-04-25 15:46:59 +0000
457+++ maastest/utils.py 2014-04-30 06:58:59 +0000
458@@ -26,12 +26,15 @@
459 ]
460
461
462+from collections import OrderedDict
463 import inspect
464 from io import BytesIO
465+import json
466 import logging
467 from pipes import quote
468 import platform
469 import re
470+import shlex
471 from subprocess import (
472 PIPE,
473 Popen,
474@@ -48,7 +51,6 @@
475 from testtools.content import Content
476 from testtools.content_type import ContentType
477
478-
479 # Default location for maas-test state, such as SSH keys for the virtual
480 # machine, and the http proxy cache.
481 DEFAULT_STATE_DIR = '/var/cache/maas-test'
482@@ -352,3 +354,36 @@
483 key,
484 '|'.join(re.escape(literal) for literal in values),
485 )
486+
487+
488+def make_json_power_parameters(raw_power_parameters):
489+ """Convert a command-line string with power parameters into json."""
490+ return json.dumps(
491+ dict(
492+ token.split('=')
493+ for token in shlex.split(raw_power_parameters)
494+ )
495+ )
496+
497+
498+def convert_bmc_details_into_parameters(mac, ip, username, password,
499+ driver):
500+ """Convert BMC details into MAAS-style power parameters."""
501+ # Use an OrderedDict to ease testing.
502+ power_parameters = OrderedDict([
503+ ('power_user', username),
504+ ('power_pass', password),
505+ ('power_driver', driver),
506+ ])
507+ if mac is not None:
508+ # XXX: rvb 2014-03-29 bug=https://bugs.launchpad.net/maas/+bug/1314174
509+ # Set power_address to the empty string to avoid the power_address to
510+ # be set to a wrong default.
511+ power_parameters['power_address'] = ''
512+ power_parameters['mac_address'] = mac
513+ else:
514+ power_parameters['power_address'] = ip
515+ return ' '.join(
516+ '%s=%s' % (key, quote(value))
517+ for key, value in power_parameters.items()
518+ )
519
520=== modified file 'man/maas-test.8'
521--- man/maas-test.8 2014-04-23 14:47:13 +0000
522+++ man/maas-test.8 2014-04-30 06:58:59 +0000
523@@ -210,30 +210,64 @@
524 MAC address for the node\(aqs baseboard management controller. MAAS will
525 control the node\(aqs power and boot sequence through this controller.
526 It must be attached to the testing network. This is mutually
527-exclusive with \fB\-\-bmc\-ip\fP\&. This option is not needed in
528-interactive mode. In non\-interactive mode, either \fB\-\-bmc\-mac\fP or
529-\fB\-\-bmc\-ip\fP is required.
530+exclusive with \fB\-\-bmc\-ip\fP\&. In non\-interactive mode, either \fB\-\-bmc\-mac\fP
531+or \fB\-\-bmc\-ip\fP is required. Not needed in interactive mode or when using
532+\fB\-\-power\-type\fP and \fB\-\-power\-parameters\fP\&.
533 .TP
534 .BI \-\-bmc\-ip\fB= IP
535 IP address of the node\(aqs baseboard management controller. Use this if
536 the BMC is not connected to the interface given as argument. Note
537 that the IP address must not change for the duration of the testing.
538-This is mutually exclusive with \fB\-\-bmc\-mac\fP\&. This option is not
539-needed in interactive mode. In non\-interactive mode, either
540-\fB\-\-bmc\-mac\fP or \fB\-\-bmc\-ip\fP is required.
541+This is mutually exclusive with \fB\-\-bmc\-mac\fP\&. In non\-interactive mode,
542+either \fB\-\-bmc\-mac\fP or \fB\-\-bmc\-ip\fP is required.
543+Not needed in interactive mode or when using \-\-power\-type and
544+\-\-power\-parameters.
545 .TP
546 .BI \-\-bmc\-password\fB= password
547 Password for IPMI authentication on the BMC. Use with \fB\-\-bmc\-user\fP\&.
548-Not needed in interactive mode.
549+Not needed in interactive mode or when using \fB\-\-power\-type\fP and
550+\fB\-\-power\-parameters\fP\&.
551 .TP
552 .BI \-\-bmc\-user\fB= user
553 Username for IPMI authentication. Use with \fB\-\-bmc\-password\fP\&.
554-Not needed in interactive mode.
555+Not needed in interactive mode or when using \fB\-\-power\-type\fP and
556+\fB\-\-power\-parameters\fP\&.
557 .TP
558 .BI \-\-ipmi\-driver\fB= driver
559 Specify IPMI driver version. Default is LAN_2_0 (IPMI v2.0), which
560 is what most modern BMCs support. Use the LAN option if your BMC only supports
561-IPMI version 1.5.
562+IPMI version 1.5. Not needed in interactive mode or if using \fB\-\-power\-type\fP and
563+\fB\-\-power\-parameters\fP\&.
564+.TP
565+.BI \-\-power\-type\fB= power_type
566+Power type of the node under test (e.g. \(aqipmi\(aq, \(aqamt\(aq, etc.
567+See \fI\%http://maas.ubuntu.com/docs/api.html#power\-types\fP for a list of the supported
568+power types and their related power parameters).
569+Using \fB\-\-power\-type\fP and \fB\-\-power\-parameters\fP is an alternative to using
570+the bmc\-* arguments that allows to use any one of the power mechanisms
571+MAAS supports.
572+.sp
573+# Example: testing a node with an Active Management Technology (AMT) interface:
574+.sp
575+$ sudo maas\-test \-\-maas\-series trusty \-\-architecture amd64
576+\-\-power\-type=amt \-\-power\-parameters="mac_address=aa:bb:cc:dd:ee:ff
577+power_pass=\(aqmy password\(aq"
578+.sp
579+# Example: testing a node with an Intelligent Platform Management Interface (IPMI):
580+.sp
581+$ sudo maas\-test \-\-maas\-series trusty \-\-architecture amd64
582+\-\-power\-type=ipmi \-\-power\-parameters="power_address=192.168.2.3 power_user=\(aqmy user\(aq
583+power_pass=\(aqmy password\(aq power_driver=LAN_2_0"
584+.TP
585+.BI \-\-power\-parameters\fB= power_parameters
586+Power parameters of the node under test (See
587+\fI\%http://maas.ubuntu.com/docs/api.html#power\-types\fP for a list of the supported power
588+types and their related power parameters). This is a dict\-like expression of the
589+power parameters for the specified power type.
590+Using \fB\-\-power\-type\fP and \fB\-\-power\-parameters\fP is an alternative to using
591+the bmc\-* arguments that allows to use any one of the power mechanisms
592+MAAS supports.
593+See the documentation for \fB\-\-power\-type\fP for examples.
594 .TP
595 .B \-\-interactive
596 Interactive mode. Instead of powering up the node automatically through

Subscribers

People subscribed via source and target branches