Merge lp:~rvb/maas-test/power-type-support into lp:maas-test
- power-type-support
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
- 165. By Raphaël Badin
-
Review fixes.
- 166. By Raphaël Badin
-
Fix test.
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-
> 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_
>
> 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_
> 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_
Good idea, done.
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
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 |
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 parameters= \"mac_address= aa:bb:cc: dd:ee:ff
--power-type=amt --power-
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:
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:
)
...
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 ?