Merge ~jsseidel/maas:jsseidel-annotation-w-examples into maas:master

Proposed by Spencer Seidel
Status: Merged
Approved by: Newell Jensen
Approved revision: 79dfbe262404f9a8e39cc723e6f00cd430b96dd6
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~jsseidel/maas:jsseidel-annotation-w-examples
Merge into: maas:master
Diff against target: 3360 lines (+2977/-93)
6 files modified
src/maasserver/api/annotations.py (+141/-10)
src/maasserver/api/examples/for-tests.json (+5/-0)
src/maasserver/api/examples/tags.json (+2513/-0)
src/maasserver/api/tags.py (+227/-69)
src/maasserver/api/tests/test_annotations.py (+83/-11)
src/maasserver/api/tmpl-apidoc.rst (+8/-3)
Reviewer Review Type Date Requested Status
Newell Jensen (community) Approve
MAAS Lander Approve
Review via email: mp+358389@code.launchpad.net

Commit message

Added a JSON 'examples' database lookup for storing API success examples. Updated tags.py to use the new lookup mechanism. Added tags.json in examples for lookups. Also updated the inline documentation to reflect this new feature.

Description of the change

'example' tags, e.g. @success-example, can reference example lookup keys (exkey) in their opts sections:

@success-example "someid" [exkey=mykey] placeholder

The code tries to find a json file to load in the examples directory that corresponds to the operation in the URI. E.g. /MAAS/api/2.0/zone/{name}/ => examples/zones.json. The code tries to manage the plural and singular so only 1 examples file is necessary (i.e. zone == zones). It then creates a dictionary and uses the object value for key "mykey" as example text in the API docs.

If a matching example is found, placeholder is ignored and replaced with a formatted version of the JSON object found that matches the given key (here, mykey) in the API docs (CLI help is not relevant here currently).

To post a comment you must log in.
4f79b60... by Spencer Seidel

Fixed typo in tags docstring

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b jsseidel-annotation-w-examples lp:~jsseidel/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 78baad5412292fa85158f0f8a383b630f730368b

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b jsseidel-annotation-w-examples lp:~jsseidel/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 4f79b605f28bd204cd4e99d481d41a06964fcc55

review: Approve
Revision history for this message
Newell Jensen (newell-jensen) wrote :

As we discussed in hangout (putting here for documentation purposes, pun intended :):

1. Change your usage of "somename" to something that is more reader friendly like "some-name".

2. Change the tags.json file to a readable version.

I will give a more thorough review in a bit.

review: Needs Fixing
4f1e525... by Spencer Seidel

Formatted json example files. Changed example variable names in tags.py to use hyphens to make them more readable.

1fbeb9d... by Spencer Seidel

Updated comment to reflect why indent=4 is used when loading examples from example json file.

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b jsseidel-annotation-w-examples lp:~jsseidel/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 1fbeb9dd220dafb36eb2c1337e2cecf240c9ef48

review: Approve
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Looks good for the most but have a few things that I think should be changed. See inline.

review: Needs Fixing
79dfbe2... by Spencer Seidel

Cleaned up exkey logic and did some renaming of variables to make things cleaner.

Revision history for this message
Newell Jensen (newell-jensen) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/annotations.py b/src/maasserver/api/annotations.py
2index 8d02791..010d512 100644
3--- a/src/maasserver/api/annotations.py
4+++ b/src/maasserver/api/annotations.py
5@@ -44,10 +44,7 @@ In most cases, formatting is preserved when presenting output.
6 With the exception of the description tag, docstrings can
7 contain as many tags as necessary to describe an API call.
8
9-The [required=true|false] option is a requirement for the
10-@param tag.
11-
12-Notes:
13+Descriptions:
14
15 Description annotations can be interpreted in a couple of ways:
16
17@@ -58,7 +55,10 @@ Description annotations can be interpreted in a couple of ways:
18
19 Explicit title:
20 The APIDocstringParser also contains support for an explicit
21- title, which the CLI-help code should honor if present.
22+ title (@description-title), which the CLI-help code should
23+ honor if present.
24+
25+Example tags:
26
27 For an example tag to be successfully associated with
28 a tag, the name or ID fields of the annotations must match:
29@@ -71,8 +71,42 @@ a tag, the name or ID fields of the annotations must match:
30 In this case, param "p1" and param-example "p1" will be
31 paired together in the output dictionary.
32
33+Options:
34+
35+The [required=true|false] option is a requirement for the
36+@param tag.
37+
38+Example tags can either include formatted examples inline:
39+
40+@success-example "some-name"
41+ {
42+ "name": "value"
43+ }
44+
45+Or reference an example key for lookup in a JSON database
46+in the examples directory based on the URI. For example:
47+
48+ /MAAS/api/2.0/zone/{name}/ -> examples/zones.json
49+ /MAAS/api/2.0/zones/{name}/ -> examples/zones.json
50+
51+Use the "exkey" to do this:
52+
53+@success-example "some-name" [exkey=unique-key] ignored text
54+
55+Where:
56+
57+examples/zones.json contains:
58+
59+{
60+ ...
61+ "unique-key": { ...some JSON object...},
62+ ...
63+}
64+
65+Notes:
66+
67 This class is typically used in conjunction with the
68-APITemplateRenderer class.
69+APITemplateRenderer class (templates.py).
70
71 OUTPUT:
72
73@@ -81,7 +115,7 @@ OUTPUT:
74
75 {
76 "http_method": "GET",
77- "uri": "/myuri/{foo}/",
78+ "uri": "/MAAS/api/2.0/myuri/{foo}/",
79 "operation": "arg1=foo&arg2=bar",
80 "description-title": "A brief, title-like description",
81 "description": "Some longer description.",
82@@ -133,6 +167,8 @@ __all__ = [
83 'APIDocstringParser',
84 ]
85
86+import json
87+import os
88 import re
89 from textwrap import indent
90
91@@ -311,10 +347,44 @@ class APIDocstringParser:
92 """
93 return_list = []
94 for tag in tags:
95- e = self._get_named_example_for_named_tag(
96+ example = self._get_named_example_for_named_tag(
97 tag_cat, tag['name'], examples)
98- if e:
99- tag['example'] = e['description']
100+ if example:
101+ # We default to the description for this example given in the
102+ # docstring.
103+ example_desc = example['description']
104+
105+ # If the user has given options, check for the presence of
106+ # 'exkey'.
107+ example_options = example['options']
108+ if example_options is not None and 'exkey' in example_options:
109+ example_options_exkey = example_options['exkey']
110+ # If the examples db is empty, since we have an exkey,
111+ # we need to warn.
112+ if self.examples_db is None:
113+ self._warn(
114+ "Found 'exkey'='%s' in example named '%s', but "
115+ "the examples database is empty." %
116+ (example_options_exkey, example['name']))
117+ elif example_options_exkey in self.examples_db:
118+ # Use indent=4 in order to tell json to format
119+ # the outgoing string as opposed to keeping it on
120+ # one line.
121+ example_desc = json.dumps(
122+ self.examples_db[example['options']['exkey']],
123+ indent=4)
124+ else:
125+ self._warn(
126+ "Found 'exkey'='%s' in example named '%s', "
127+ "but found no corresponding entry in the the "
128+ "examples database." %
129+ (example['options']['exkey'], example['name']))
130+
131+ # example will contain either the description provided in the
132+ # docstring as is, or it will have been replaced with an
133+ # entry from the examples_db
134+ tag['example'] = example_desc
135+
136 else:
137 tag['example'] = ""
138
139@@ -345,6 +415,8 @@ class APIDocstringParser:
140 self.error_examples = []
141 # Clear out any collected warnings
142 self.warnings = ""
143+ # Clear the examples database
144+ self.examples_db = None
145
146 # Strips multiple inline spaces, all newlines, and
147 # leading and trailing spaces
148@@ -503,6 +575,61 @@ class APIDocstringParser:
149
150 return ""
151
152+ def _get_operation_from_uri(self, uri):
153+ """Parses out an operation name from a given URI.
154+
155+ Given, for example, /MAAS/api/2.0/resourcepool/{id}/, this
156+ function returns "resourcepool".
157+ """
158+ m = re.search("/MAAS/api/[0-9]+\.[0-9]+/([a-z\-]+)/", uri)
159+ if m:
160+ return m.group(1)
161+
162+ # Note that *not* finding an operation in a URI is normal
163+ # and acceptable because sometimes we're parsing a docstring
164+ # that the user hasn't associated with a URI.
165+
166+ return ""
167+
168+ def _get_examples_dict(self, uri):
169+ """Returns a dictionary containing examples data or None
170+
171+ Given an operation string like "zone" or "zones", this function tries
172+ to open and slurp in the JSON of a matching file in the api/examples
173+ directory (e.g. tags.json). Example objects look like this:
174+
175+ "uniquekey": {
176+ ... any JSON object ...
177+ }
178+
179+ If no database is associated with the operation, the function
180+ returns None.
181+ """
182+ examples = {}
183+
184+ operation = self._get_operation_from_uri(uri)
185+
186+ if operation == "":
187+ return None
188+
189+ # First, try the operation string as is:
190+ json_file = ("%s/examples/%s.json" %
191+ (os.path.dirname(__file__), operation))
192+
193+ if not os.path.isfile(json_file):
194+ # Not available, so try adding an 's' to make it plural
195+ json_file = ("%s/examples/%ss.json" %
196+ (os.path.dirname(__file__), operation))
197+
198+ if not os.path.isfile(json_file):
199+ # Give up
200+ return None
201+
202+ with open(json_file, "r") as ex_db_file:
203+ examples = json.load(ex_db_file)
204+
205+ return examples
206+
207 def parse(self, docstring, http_method='', uri='', operation=''):
208 """State machine that parses annotated API docstrings.
209
210@@ -542,6 +669,10 @@ class APIDocstringParser:
211 self.operation = operation
212 self._clear_docstring_vars()
213
214+ # Fetch and build a dictionary containing examples associated with
215+ # the given URI (if any)
216+ self.examples_db = self._get_examples_dict(uri)
217+
218 # Init parse state
219 ps = ParseState.TAG
220
221diff --git a/src/maasserver/api/examples/for-tests.json b/src/maasserver/api/examples/for-tests.json
222new file mode 100644
223index 0000000..5e259b9
224--- /dev/null
225+++ b/src/maasserver/api/examples/for-tests.json
226@@ -0,0 +1,5 @@
227+{
228+ "key1": {
229+ "name": "value"
230+ }
231+}
232diff --git a/src/maasserver/api/examples/tags.json b/src/maasserver/api/examples/tags.json
233new file mode 100644
234index 0000000..0c18c25
235--- /dev/null
236+++ b/src/maasserver/api/examples/tags.json
237@@ -0,0 +1,2513 @@
238+{
239+ "get-tags": [
240+ {
241+ "name": "virtual",
242+ "definition": "",
243+ "comment": "",
244+ "kernel_opts": null,
245+ "resource_uri": "/MAAS/api/2.0/tags/virtual/"
246+ }
247+ ],
248+ "get-tag-by-name": {
249+ "name": "virtual",
250+ "definition": "",
251+ "comment": "",
252+ "kernel_opts": null,
253+ "resource_uri": "/MAAS/api/2.0/tags/virtual/"
254+ },
255+ "get-devices-by-tag": [
256+ {
257+ "tag_names": [
258+ "virtual"
259+ ],
260+ "interface_set": [
261+ {
262+ "id": 130,
263+ "name": "eth-tNqklu",
264+ "tags": [
265+ "tag-YYQK5S",
266+ "tag-MUmKRU",
267+ "tag-dh0kc2"
268+ ],
269+ "links": [],
270+ "firmware_version": null,
271+ "effective_mtu": 1500,
272+ "product": null,
273+ "mac_address": "bb:f7:55:fd:0f:34",
274+ "system_id": "nqg6dg",
275+ "parents": [],
276+ "children": [],
277+ "params": "",
278+ "enabled": true,
279+ "discovered": null,
280+ "vlan": {
281+ "vid": 0,
282+ "mtu": 1500,
283+ "dhcp_on": false,
284+ "external_dhcp": null,
285+ "relay_vlan": null,
286+ "id": 5013,
287+ "name": "untagged",
288+ "fabric": "fabric-9",
289+ "primary_rack": null,
290+ "fabric_id": 9,
291+ "secondary_rack": null,
292+ "space": "undefined",
293+ "resource_uri": "/MAAS/api/2.0/vlans/5013/"
294+ },
295+ "vendor": null,
296+ "type": "physical",
297+ "resource_uri": "/MAAS/api/2.0/nodes/nqg6dg/interfaces/130/"
298+ }
299+ ],
300+ "zone": {
301+ "name": "default",
302+ "description": "",
303+ "id": 1,
304+ "resource_uri": "/MAAS/api/2.0/zones/default/"
305+ },
306+ "node_type": 1,
307+ "hostname": "calm-bobcat",
308+ "system_id": "nqg6dg",
309+ "owner_data": {},
310+ "parent": null,
311+ "address_ttl": null,
312+ "owner": null,
313+ "ip_addresses": [],
314+ "node_type_name": "Device",
315+ "fqdn": "calm-bobcat.maas",
316+ "domain": {
317+ "authoritative": true,
318+ "ttl": null,
319+ "id": 0,
320+ "name": "maas",
321+ "resource_record_count": 0,
322+ "is_default": true,
323+ "resource_uri": "/MAAS/api/2.0/domains/0/"
324+ },
325+ "resource_uri": "/MAAS/api/2.0/devices/nqg6dg/"
326+ }
327+ ],
328+ "get-machines-by-tag": [
329+ {
330+ "pool": {
331+ "name": "default",
332+ "description": "Default pool",
333+ "id": 0,
334+ "resource_uri": "/MAAS/api/2.0/resourcepool/0/"
335+ },
336+ "storage": 3865.490432,
337+ "min_hwe_kernel": "",
338+ "interface_set": [
339+ {
340+ "id": 94,
341+ "name": "eth-x2dFvx",
342+ "tags": [
343+ "tag-fAzmsZ",
344+ "tag-doKrVe",
345+ "tag-ejryXL"
346+ ],
347+ "links": [
348+ {
349+ "id": 39,
350+ "mode": "auto",
351+ "subnet": {
352+ "name": "name-rLI3eq",
353+ "vlan": {
354+ "vid": 0,
355+ "mtu": 1500,
356+ "dhcp_on": false,
357+ "external_dhcp": null,
358+ "relay_vlan": null,
359+ "id": 5001,
360+ "name": "untagged",
361+ "fabric": "fabric-0",
362+ "primary_rack": null,
363+ "fabric_id": 0,
364+ "secondary_rack": null,
365+ "space": "management",
366+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
367+ },
368+ "cidr": "172.16.1.0/24",
369+ "rdns_mode": 2,
370+ "gateway_ip": "172.16.1.1",
371+ "dns_servers": [
372+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
373+ ],
374+ "allow_dns": true,
375+ "allow_proxy": true,
376+ "active_discovery": false,
377+ "managed": true,
378+ "id": 1,
379+ "space": "management",
380+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
381+ }
382+ }
383+ ],
384+ "firmware_version": null,
385+ "effective_mtu": 1500,
386+ "product": null,
387+ "mac_address": "dd:c6:80:1a:c7:80",
388+ "system_id": "ssqcgt",
389+ "parents": [],
390+ "children": [
391+ "eth-x2dFvx.10"
392+ ],
393+ "params": "",
394+ "enabled": true,
395+ "discovered": null,
396+ "vlan": {
397+ "vid": 0,
398+ "mtu": 1500,
399+ "dhcp_on": false,
400+ "external_dhcp": null,
401+ "relay_vlan": null,
402+ "id": 5001,
403+ "name": "untagged",
404+ "fabric": "fabric-0",
405+ "primary_rack": null,
406+ "fabric_id": 0,
407+ "secondary_rack": null,
408+ "space": "management",
409+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
410+ },
411+ "vendor": null,
412+ "type": "physical",
413+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/94/"
414+ },
415+ {
416+ "id": 95,
417+ "name": "eth-gtMGIr",
418+ "tags": [
419+ "tag-rW3Lmf",
420+ "tag-DdASuZ",
421+ "tag-MS8EYC"
422+ ],
423+ "links": [
424+ {
425+ "id": 40,
426+ "mode": "auto",
427+ "subnet": {
428+ "name": "name-rLI3eq",
429+ "vlan": {
430+ "vid": 0,
431+ "mtu": 1500,
432+ "dhcp_on": false,
433+ "external_dhcp": null,
434+ "relay_vlan": null,
435+ "id": 5001,
436+ "name": "untagged",
437+ "fabric": "fabric-0",
438+ "primary_rack": null,
439+ "fabric_id": 0,
440+ "secondary_rack": null,
441+ "space": "management",
442+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
443+ },
444+ "cidr": "172.16.1.0/24",
445+ "rdns_mode": 2,
446+ "gateway_ip": "172.16.1.1",
447+ "dns_servers": [
448+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
449+ ],
450+ "allow_dns": true,
451+ "allow_proxy": true,
452+ "active_discovery": false,
453+ "managed": true,
454+ "id": 1,
455+ "space": "management",
456+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
457+ }
458+ }
459+ ],
460+ "firmware_version": null,
461+ "effective_mtu": 1500,
462+ "product": null,
463+ "mac_address": "52:db:98:ef:1e:b5",
464+ "system_id": "ssqcgt",
465+ "parents": [],
466+ "children": [
467+ "eth-gtMGIr.10"
468+ ],
469+ "params": "",
470+ "enabled": true,
471+ "discovered": null,
472+ "vlan": {
473+ "vid": 0,
474+ "mtu": 1500,
475+ "dhcp_on": false,
476+ "external_dhcp": null,
477+ "relay_vlan": null,
478+ "id": 5001,
479+ "name": "untagged",
480+ "fabric": "fabric-0",
481+ "primary_rack": null,
482+ "fabric_id": 0,
483+ "secondary_rack": null,
484+ "space": "management",
485+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
486+ },
487+ "vendor": null,
488+ "type": "physical",
489+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/95/"
490+ },
491+ {
492+ "id": 96,
493+ "name": "eth-3cXhBN",
494+ "tags": [
495+ "tag-QisnXF",
496+ "tag-duMUg2",
497+ "tag-06ARMg"
498+ ],
499+ "links": [
500+ {
501+ "id": 41,
502+ "mode": "static",
503+ "ip_address": "172.16.1.22",
504+ "subnet": {
505+ "name": "name-rLI3eq",
506+ "vlan": {
507+ "vid": 0,
508+ "mtu": 1500,
509+ "dhcp_on": false,
510+ "external_dhcp": null,
511+ "relay_vlan": null,
512+ "id": 5001,
513+ "name": "untagged",
514+ "fabric": "fabric-0",
515+ "primary_rack": null,
516+ "fabric_id": 0,
517+ "secondary_rack": null,
518+ "space": "management",
519+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
520+ },
521+ "cidr": "172.16.1.0/24",
522+ "rdns_mode": 2,
523+ "gateway_ip": "172.16.1.1",
524+ "dns_servers": [
525+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
526+ ],
527+ "allow_dns": true,
528+ "allow_proxy": true,
529+ "active_discovery": false,
530+ "managed": true,
531+ "id": 1,
532+ "space": "management",
533+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
534+ }
535+ }
536+ ],
537+ "firmware_version": null,
538+ "effective_mtu": 1500,
539+ "product": null,
540+ "mac_address": "e3:72:73:60:9a:b9",
541+ "system_id": "ssqcgt",
542+ "parents": [],
543+ "children": [
544+ "eth-3cXhBN.10"
545+ ],
546+ "params": "",
547+ "enabled": true,
548+ "discovered": null,
549+ "vlan": {
550+ "vid": 0,
551+ "mtu": 1500,
552+ "dhcp_on": false,
553+ "external_dhcp": null,
554+ "relay_vlan": null,
555+ "id": 5001,
556+ "name": "untagged",
557+ "fabric": "fabric-0",
558+ "primary_rack": null,
559+ "fabric_id": 0,
560+ "secondary_rack": null,
561+ "space": "management",
562+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
563+ },
564+ "vendor": null,
565+ "type": "physical",
566+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/96/"
567+ },
568+ {
569+ "id": 97,
570+ "name": "eth-x2dFvx.10",
571+ "tags": [
572+ "tag-Y5zzv5",
573+ "tag-w5vECc",
574+ "tag-o4znE9"
575+ ],
576+ "links": [
577+ {
578+ "id": 42,
579+ "mode": "static",
580+ "ip_address": "172.16.4.56",
581+ "subnet": {
582+ "name": "name-c2ULe1",
583+ "vlan": {
584+ "vid": 10,
585+ "mtu": 1500,
586+ "dhcp_on": false,
587+ "external_dhcp": null,
588+ "relay_vlan": null,
589+ "id": 5002,
590+ "name": "10",
591+ "fabric": "fabric-0",
592+ "primary_rack": null,
593+ "fabric_id": 0,
594+ "secondary_rack": null,
595+ "space": "internal",
596+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
597+ },
598+ "cidr": "172.16.4.0/24",
599+ "rdns_mode": 2,
600+ "gateway_ip": "172.16.4.1",
601+ "dns_servers": [
602+ "fd08:fef7:5c1f:a2e6:3d8e:6c3b:89f9:80cb",
603+ "fc67:ad6a:88fe:9192:62f9:e882:8bcc:339e",
604+ "255.59.162.158"
605+ ],
606+ "allow_dns": true,
607+ "allow_proxy": true,
608+ "active_discovery": false,
609+ "managed": true,
610+ "id": 4,
611+ "space": "internal",
612+ "resource_uri": "/MAAS/api/2.0/subnets/4/"
613+ }
614+ }
615+ ],
616+ "firmware_version": null,
617+ "effective_mtu": 1500,
618+ "product": null,
619+ "mac_address": "dd:c6:80:1a:c7:80",
620+ "system_id": "ssqcgt",
621+ "parents": [
622+ "eth-x2dFvx"
623+ ],
624+ "children": [],
625+ "params": "",
626+ "enabled": true,
627+ "discovered": null,
628+ "vlan": {
629+ "vid": 10,
630+ "mtu": 1500,
631+ "dhcp_on": false,
632+ "external_dhcp": null,
633+ "relay_vlan": null,
634+ "id": 5002,
635+ "name": "10",
636+ "fabric": "fabric-0",
637+ "primary_rack": null,
638+ "fabric_id": 0,
639+ "secondary_rack": null,
640+ "space": "internal",
641+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
642+ },
643+ "vendor": null,
644+ "type": "vlan",
645+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/97/"
646+ },
647+ {
648+ "id": 98,
649+ "name": "eth-gtMGIr.10",
650+ "tags": [
651+ "tag-wS4OXL",
652+ "tag-Wm3I55",
653+ "tag-LE5UYY"
654+ ],
655+ "links": [
656+ {
657+ "id": 43,
658+ "mode": "static",
659+ "ip_address": "172.16.3.102",
660+ "subnet": {
661+ "name": "name-zznp45",
662+ "vlan": {
663+ "vid": 10,
664+ "mtu": 1500,
665+ "dhcp_on": false,
666+ "external_dhcp": null,
667+ "relay_vlan": null,
668+ "id": 5002,
669+ "name": "10",
670+ "fabric": "fabric-0",
671+ "primary_rack": null,
672+ "fabric_id": 0,
673+ "secondary_rack": null,
674+ "space": "internal",
675+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
676+ },
677+ "cidr": "172.16.3.0/24",
678+ "rdns_mode": 2,
679+ "gateway_ip": "172.16.3.1",
680+ "dns_servers": [
681+ "fd98:8601:90d0:c8c:dd2e:ba51:fa5a:dcfa",
682+ "11.209.150.208",
683+ "fde6:f9ef:3ee9:c5de:2a66:1582:cc83:abaf"
684+ ],
685+ "allow_dns": true,
686+ "allow_proxy": true,
687+ "active_discovery": false,
688+ "managed": true,
689+ "id": 3,
690+ "space": "internal",
691+ "resource_uri": "/MAAS/api/2.0/subnets/3/"
692+ }
693+ }
694+ ],
695+ "firmware_version": null,
696+ "effective_mtu": 1500,
697+ "product": null,
698+ "mac_address": "52:db:98:ef:1e:b5",
699+ "system_id": "ssqcgt",
700+ "parents": [
701+ "eth-gtMGIr"
702+ ],
703+ "children": [],
704+ "params": "",
705+ "enabled": true,
706+ "discovered": null,
707+ "vlan": {
708+ "vid": 10,
709+ "mtu": 1500,
710+ "dhcp_on": false,
711+ "external_dhcp": null,
712+ "relay_vlan": null,
713+ "id": 5002,
714+ "name": "10",
715+ "fabric": "fabric-0",
716+ "primary_rack": null,
717+ "fabric_id": 0,
718+ "secondary_rack": null,
719+ "space": "internal",
720+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
721+ },
722+ "vendor": null,
723+ "type": "vlan",
724+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/98/"
725+ },
726+ {
727+ "id": 99,
728+ "name": "eth-3cXhBN.10",
729+ "tags": [
730+ "tag-Zp22Uh",
731+ "tag-PhIZjP",
732+ "tag-QtYg8x"
733+ ],
734+ "links": [
735+ {
736+ "id": 44,
737+ "mode": "static",
738+ "ip_address": "172.16.3.23",
739+ "subnet": {
740+ "name": "name-zznp45",
741+ "vlan": {
742+ "vid": 10,
743+ "mtu": 1500,
744+ "dhcp_on": false,
745+ "external_dhcp": null,
746+ "relay_vlan": null,
747+ "id": 5002,
748+ "name": "10",
749+ "fabric": "fabric-0",
750+ "primary_rack": null,
751+ "fabric_id": 0,
752+ "secondary_rack": null,
753+ "space": "internal",
754+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
755+ },
756+ "cidr": "172.16.3.0/24",
757+ "rdns_mode": 2,
758+ "gateway_ip": "172.16.3.1",
759+ "dns_servers": [
760+ "fd98:8601:90d0:c8c:dd2e:ba51:fa5a:dcfa",
761+ "11.209.150.208",
762+ "fde6:f9ef:3ee9:c5de:2a66:1582:cc83:abaf"
763+ ],
764+ "allow_dns": true,
765+ "allow_proxy": true,
766+ "active_discovery": false,
767+ "managed": true,
768+ "id": 3,
769+ "space": "internal",
770+ "resource_uri": "/MAAS/api/2.0/subnets/3/"
771+ }
772+ }
773+ ],
774+ "firmware_version": null,
775+ "effective_mtu": 1500,
776+ "product": null,
777+ "mac_address": "e3:72:73:60:9a:b9",
778+ "system_id": "ssqcgt",
779+ "parents": [
780+ "eth-3cXhBN"
781+ ],
782+ "children": [],
783+ "params": "",
784+ "enabled": true,
785+ "discovered": null,
786+ "vlan": {
787+ "vid": 10,
788+ "mtu": 1500,
789+ "dhcp_on": false,
790+ "external_dhcp": null,
791+ "relay_vlan": null,
792+ "id": 5002,
793+ "name": "10",
794+ "fabric": "fabric-0",
795+ "primary_rack": null,
796+ "fabric_id": 0,
797+ "secondary_rack": null,
798+ "space": "internal",
799+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
800+ },
801+ "vendor": null,
802+ "type": "vlan",
803+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/99/"
804+ }
805+ ],
806+ "iscsiblockdevice_set": [],
807+ "node_type": 0,
808+ "memory_test_status_name": "Failed",
809+ "bcaches": [],
810+ "memory_test_status": 3,
811+ "ip_addresses": [
812+ "172.16.1.22",
813+ "172.16.4.56",
814+ "172.16.3.102",
815+ "172.16.3.23"
816+ ],
817+ "swap_size": null,
818+ "volume_groups": [],
819+ "testing_status": 3,
820+ "node_type_name": "Machine",
821+ "current_testing_result_id": 153,
822+ "power_type": "virsh",
823+ "fqdn": "chief-hippo.ubnt",
824+ "storage_test_status_name": "Failed",
825+ "storage_test_status": 3,
826+ "status_name": "Rescue mode",
827+ "locked": false,
828+ "netboot": true,
829+ "tag_names": [
830+ "virtual"
831+ ],
832+ "zone": {
833+ "name": "zone-north",
834+ "description": "xsMaq90fRE",
835+ "id": 2,
836+ "resource_uri": "/MAAS/api/2.0/zones/zone-north/"
837+ },
838+ "boot_disk": {
839+ "firmware_version": "firmware_version-rszebt",
840+ "uuid": null,
841+ "id": 65,
842+ "model": "model-u038bu",
843+ "name": "name-2V9gLL",
844+ "tags": [
845+ "tag-wSvQ4O",
846+ "tag-52MbPv",
847+ "tag-JKilHY"
848+ ],
849+ "partitions": [],
850+ "partition_table_type": null,
851+ "used_size": 0,
852+ "storage_pool": "pool_id-aMRZUu",
853+ "id_path": null,
854+ "system_id": "ssqcgt",
855+ "block_size": 512,
856+ "filesystem": null,
857+ "used_for": "Unused",
858+ "serial": "serial-Lh6Yv9",
859+ "path": "/dev/disk/by-dname/name-2V9gLL",
860+ "type": "physical",
861+ "size": 3865490432,
862+ "available_size": 3865490432,
863+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/blockdevices/65/"
864+ },
865+ "commissioning_status": 2,
866+ "cpu_test_status": 3,
867+ "current_installation_result_id": null,
868+ "owner_data": {},
869+ "power_state": "on",
870+ "current_commissioning_result_id": 152,
871+ "status": 16,
872+ "hwe_kernel": null,
873+ "boot_interface": {
874+ "id": 94,
875+ "name": "eth-x2dFvx",
876+ "tags": [
877+ "tag-fAzmsZ",
878+ "tag-doKrVe",
879+ "tag-ejryXL"
880+ ],
881+ "links": [
882+ {
883+ "id": 39,
884+ "mode": "auto",
885+ "subnet": {
886+ "name": "name-rLI3eq",
887+ "vlan": {
888+ "vid": 0,
889+ "mtu": 1500,
890+ "dhcp_on": false,
891+ "external_dhcp": null,
892+ "relay_vlan": null,
893+ "id": 5001,
894+ "name": "untagged",
895+ "fabric": "fabric-0",
896+ "primary_rack": null,
897+ "fabric_id": 0,
898+ "secondary_rack": null,
899+ "space": "management",
900+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
901+ },
902+ "cidr": "172.16.1.0/24",
903+ "rdns_mode": 2,
904+ "gateway_ip": "172.16.1.1",
905+ "dns_servers": [
906+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
907+ ],
908+ "allow_dns": true,
909+ "allow_proxy": true,
910+ "active_discovery": false,
911+ "managed": true,
912+ "id": 1,
913+ "space": "management",
914+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
915+ }
916+ }
917+ ],
918+ "firmware_version": null,
919+ "effective_mtu": 1500,
920+ "product": null,
921+ "mac_address": "dd:c6:80:1a:c7:80",
922+ "system_id": "ssqcgt",
923+ "parents": [],
924+ "children": [
925+ "eth-x2dFvx.10"
926+ ],
927+ "params": "",
928+ "enabled": true,
929+ "discovered": null,
930+ "vlan": {
931+ "vid": 0,
932+ "mtu": 1500,
933+ "dhcp_on": false,
934+ "external_dhcp": null,
935+ "relay_vlan": null,
936+ "id": 5001,
937+ "name": "untagged",
938+ "fabric": "fabric-0",
939+ "primary_rack": null,
940+ "fabric_id": 0,
941+ "secondary_rack": null,
942+ "space": "management",
943+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
944+ },
945+ "vendor": null,
946+ "type": "physical",
947+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/94/"
948+ },
949+ "special_filesystems": [],
950+ "cpu_speed": 0,
951+ "architecture": "amd64/generic",
952+ "blockdevice_set": [
953+ {
954+ "id_path": null,
955+ "size": 3865490432,
956+ "block_size": 512,
957+ "tags": [
958+ "tag-wSvQ4O",
959+ "tag-52MbPv",
960+ "tag-JKilHY"
961+ ],
962+ "uuid": null,
963+ "id": 65,
964+ "model": "model-u038bu",
965+ "name": "name-2V9gLL",
966+ "partitions": [],
967+ "partition_table_type": null,
968+ "used_size": 0,
969+ "storage_pool": "pool_id-aMRZUu",
970+ "system_id": "ssqcgt",
971+ "filesystem": null,
972+ "used_for": "Unused",
973+ "serial": "serial-Lh6Yv9",
974+ "path": "/dev/disk/by-dname/name-2V9gLL",
975+ "type": "physical",
976+ "available_size": 3865490432,
977+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/blockdevices/65/"
978+ }
979+ ],
980+ "cpu_test_status_name": "Failed",
981+ "other_test_status": 2,
982+ "hostname": "chief-hippo",
983+ "memory": 4096,
984+ "default_gateways": {
985+ "ipv4": {
986+ "gateway_ip": "172.16.1.1",
987+ "link_id": null
988+ },
989+ "ipv6": {
990+ "gateway_ip": null,
991+ "link_id": null
992+ }
993+ },
994+ "distro_series": "",
995+ "status_action": "action-ft28IH",
996+ "owner": "user1",
997+ "commissioning_status_name": "Passed",
998+ "hardware_info": {
999+ "system_vendor": "Unknown",
1000+ "system_product": "Unknown",
1001+ "system_version": "Unknown",
1002+ "system_serial": "Unknown",
1003+ "cpu_model": "Unknown",
1004+ "mainboard_vendor": "Unknown",
1005+ "mainboard_product": "Unknown",
1006+ "mainboard_firmware_version": "Unknown",
1007+ "mainboard_firmware_date": "Unknown"
1008+ },
1009+ "pod": {
1010+ "id": 5,
1011+ "name": "normal-trout",
1012+ "resource_uri": "/MAAS/api/2.0/pods/5/"
1013+ },
1014+ "cache_sets": [],
1015+ "testing_status_name": "Failed",
1016+ "disable_ipv4": false,
1017+ "physicalblockdevice_set": [
1018+ {
1019+ "firmware_version": "firmware_version-rszebt",
1020+ "uuid": null,
1021+ "id": 65,
1022+ "model": "model-u038bu",
1023+ "name": "name-2V9gLL",
1024+ "tags": [
1025+ "tag-wSvQ4O",
1026+ "tag-52MbPv",
1027+ "tag-JKilHY"
1028+ ],
1029+ "partitions": [],
1030+ "partition_table_type": null,
1031+ "used_size": 0,
1032+ "storage_pool": "pool_id-aMRZUu",
1033+ "id_path": null,
1034+ "system_id": "ssqcgt",
1035+ "block_size": 512,
1036+ "filesystem": null,
1037+ "used_for": "Unused",
1038+ "serial": "serial-Lh6Yv9",
1039+ "path": "/dev/disk/by-dname/name-2V9gLL",
1040+ "type": "physical",
1041+ "size": 3865490432,
1042+ "available_size": 3865490432,
1043+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/blockdevices/65/"
1044+ }
1045+ ],
1046+ "osystem": "",
1047+ "system_id": "ssqcgt",
1048+ "address_ttl": null,
1049+ "raids": [],
1050+ "cpu_count": 5,
1051+ "virtualblockdevice_set": [],
1052+ "other_test_status_name": "Passed",
1053+ "status_message": "desc-SfeEsi",
1054+ "domain": {
1055+ "authoritative": true,
1056+ "ttl": null,
1057+ "id": 2,
1058+ "name": "ubnt",
1059+ "resource_record_count": 0,
1060+ "is_default": false,
1061+ "resource_uri": "/MAAS/api/2.0/domains/2/"
1062+ },
1063+ "resource_uri": "/MAAS/api/2.0/machines/ssqcgt/"
1064+ }
1065+ ],
1066+ "get-nodes-by-tag": [
1067+ {
1068+ "interface_set": [
1069+ {
1070+ "id": 1,
1071+ "name": "ens3",
1072+ "tags": [
1073+ "tag-0wj45r",
1074+ "tag-ylwPaA",
1075+ "tag-s8HCFS"
1076+ ],
1077+ "links": [
1078+ {
1079+ "id": 59,
1080+ "mode": "static",
1081+ "ip_address": "10.55.32.42",
1082+ "subnet": {
1083+ "name": "10.55.32.0/20",
1084+ "vlan": {
1085+ "vid": 0,
1086+ "mtu": 1500,
1087+ "dhcp_on": false,
1088+ "external_dhcp": null,
1089+ "relay_vlan": null,
1090+ "id": 5001,
1091+ "name": "untagged",
1092+ "fabric": "fabric-0",
1093+ "primary_rack": null,
1094+ "fabric_id": 0,
1095+ "secondary_rack": null,
1096+ "space": "management",
1097+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1098+ },
1099+ "cidr": "10.55.32.0/20",
1100+ "rdns_mode": 2,
1101+ "gateway_ip": "10.55.32.1",
1102+ "dns_servers": [],
1103+ "allow_dns": true,
1104+ "allow_proxy": true,
1105+ "active_discovery": false,
1106+ "managed": true,
1107+ "id": 6,
1108+ "space": "management",
1109+ "resource_uri": "/MAAS/api/2.0/subnets/6/"
1110+ }
1111+ }
1112+ ],
1113+ "firmware_version": "N/A",
1114+ "effective_mtu": 1500,
1115+ "product": "OpenStack Nova",
1116+ "mac_address": "fa:16:3e:3d:03:02",
1117+ "system_id": "7xtf67",
1118+ "parents": [],
1119+ "children": [],
1120+ "params": "",
1121+ "enabled": true,
1122+ "discovered": null,
1123+ "vlan": {
1124+ "vid": 0,
1125+ "mtu": 1500,
1126+ "dhcp_on": false,
1127+ "external_dhcp": null,
1128+ "relay_vlan": null,
1129+ "id": 5001,
1130+ "name": "untagged",
1131+ "fabric": "fabric-0",
1132+ "primary_rack": null,
1133+ "fabric_id": 0,
1134+ "secondary_rack": null,
1135+ "space": "management",
1136+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1137+ },
1138+ "vendor": "OpenStack Foundation",
1139+ "type": "physical",
1140+ "resource_uri": "/MAAS/api/2.0/nodes/7xtf67/interfaces/1/"
1141+ },
1142+ {
1143+ "id": 134,
1144+ "name": "virbr0",
1145+ "tags": [],
1146+ "links": [
1147+ {
1148+ "id": 60,
1149+ "mode": "static",
1150+ "ip_address": "192.168.122.1",
1151+ "subnet": {
1152+ "name": "192.168.122.0/24",
1153+ "vlan": {
1154+ "vid": 0,
1155+ "mtu": 1500,
1156+ "dhcp_on": false,
1157+ "external_dhcp": null,
1158+ "relay_vlan": null,
1159+ "id": 5017,
1160+ "name": "untagged",
1161+ "fabric": "fabric-13",
1162+ "primary_rack": null,
1163+ "fabric_id": 13,
1164+ "secondary_rack": null,
1165+ "space": "undefined",
1166+ "resource_uri": "/MAAS/api/2.0/vlans/5017/"
1167+ },
1168+ "cidr": "192.168.122.0/24",
1169+ "rdns_mode": 2,
1170+ "gateway_ip": null,
1171+ "dns_servers": [],
1172+ "allow_dns": true,
1173+ "allow_proxy": true,
1174+ "active_discovery": false,
1175+ "managed": true,
1176+ "id": 7,
1177+ "space": "undefined",
1178+ "resource_uri": "/MAAS/api/2.0/subnets/7/"
1179+ }
1180+ }
1181+ ],
1182+ "firmware_version": "N/A",
1183+ "effective_mtu": 1500,
1184+ "product": "OpenStack Nova",
1185+ "mac_address": "52:54:00:95:f3:f3",
1186+ "system_id": "7xtf67",
1187+ "parents": [],
1188+ "children": [],
1189+ "params": "",
1190+ "enabled": true,
1191+ "discovered": null,
1192+ "vlan": {
1193+ "vid": 0,
1194+ "mtu": 1500,
1195+ "dhcp_on": false,
1196+ "external_dhcp": null,
1197+ "relay_vlan": null,
1198+ "id": 5017,
1199+ "name": "untagged",
1200+ "fabric": "fabric-13",
1201+ "primary_rack": null,
1202+ "fabric_id": 13,
1203+ "secondary_rack": null,
1204+ "space": "undefined",
1205+ "resource_uri": "/MAAS/api/2.0/vlans/5017/"
1206+ },
1207+ "vendor": "OpenStack Foundation",
1208+ "type": "bridge",
1209+ "resource_uri": "/MAAS/api/2.0/nodes/7xtf67/interfaces/134/"
1210+ }
1211+ ],
1212+ "node_type": 4,
1213+ "memory_test_status_name": "Unknown",
1214+ "memory_test_status": -1,
1215+ "ip_addresses": [
1216+ "10.55.32.42",
1217+ "192.168.122.1"
1218+ ],
1219+ "swap_size": null,
1220+ "testing_status": -1,
1221+ "node_type_name": "Region and rack controller",
1222+ "current_testing_result_id": null,
1223+ "power_type": "virsh",
1224+ "fqdn": "spence-devmaas.maas",
1225+ "storage_test_status_name": "Unknown",
1226+ "storage_test_status": -1,
1227+ "tag_names": [
1228+ "virtual"
1229+ ],
1230+ "zone": {
1231+ "name": "zone-obryVj",
1232+ "description": "qRwvQuFIpM",
1233+ "id": 4,
1234+ "resource_uri": "/MAAS/api/2.0/zones/zone-obryVj/"
1235+ },
1236+ "commissioning_status": 2,
1237+ "cpu_test_status": -1,
1238+ "current_installation_result_id": null,
1239+ "power_state": "on",
1240+ "current_commissioning_result_id": 228,
1241+ "cpu_speed": 2500,
1242+ "architecture": "amd64/generic",
1243+ "cpu_test_status_name": "Unknown",
1244+ "other_test_status": -1,
1245+ "hostname": "spence-devmaas",
1246+ "memory": 8192,
1247+ "commissioning_status_name": "Passed",
1248+ "distro_series": "bionic",
1249+ "status_action": "",
1250+ "hardware_info": {
1251+ "system_vendor": "OpenStack Foundation",
1252+ "system_product": "OpenStack Nova",
1253+ "system_version": "2013.2.3",
1254+ "system_serial": "30353036-3837-5a43-3331-353234584a58",
1255+ "cpu_model": "Westmere E56xx/L56xx/X56xx (Nehalem-C)",
1256+ "mainboard_vendor": "Unknown",
1257+ "mainboard_product": "Unknown",
1258+ "mainboard_firmware_version": "Bochs",
1259+ "mainboard_firmware_date": "01/01/2011"
1260+ },
1261+ "service_set": [
1262+ {
1263+ "name": "proxy",
1264+ "status": "unknown",
1265+ "status_info": ""
1266+ },
1267+ {
1268+ "name": "proxy_rack",
1269+ "status": "unknown",
1270+ "status_info": ""
1271+ },
1272+ {
1273+ "name": "dhcpd6",
1274+ "status": "dead",
1275+ "status_info": ""
1276+ },
1277+ {
1278+ "name": "ntp_rack",
1279+ "status": "dead",
1280+ "status_info": ""
1281+ },
1282+ {
1283+ "name": "syslog_region",
1284+ "status": "unknown",
1285+ "status_info": ""
1286+ },
1287+ {
1288+ "name": "http",
1289+ "status": "unknown",
1290+ "status_info": ""
1291+ },
1292+ {
1293+ "name": "tftp",
1294+ "status": "dead",
1295+ "status_info": ""
1296+ },
1297+ {
1298+ "name": "bind9",
1299+ "status": "unknown",
1300+ "status_info": ""
1301+ },
1302+ {
1303+ "name": "rackd",
1304+ "status": "dead",
1305+ "status_info": ""
1306+ },
1307+ {
1308+ "name": "ntp_region",
1309+ "status": "unknown",
1310+ "status_info": ""
1311+ },
1312+ {
1313+ "name": "regiond",
1314+ "status": "running",
1315+ "status_info": ""
1316+ },
1317+ {
1318+ "name": "dns_rack",
1319+ "status": "unknown",
1320+ "status_info": ""
1321+ },
1322+ {
1323+ "name": "syslog_rack",
1324+ "status": "unknown",
1325+ "status_info": ""
1326+ },
1327+ {
1328+ "name": "dhcpd",
1329+ "status": "dead",
1330+ "status_info": ""
1331+ }
1332+ ],
1333+ "testing_status_name": "Unknown",
1334+ "osystem": "ubuntu",
1335+ "version": "",
1336+ "system_id": "7xtf67",
1337+ "cpu_count": 4,
1338+ "other_test_status_name": "Unknown",
1339+ "domain": {
1340+ "authoritative": true,
1341+ "ttl": null,
1342+ "id": 0,
1343+ "name": "maas",
1344+ "resource_record_count": 0,
1345+ "is_default": true,
1346+ "resource_uri": "/MAAS/api/2.0/domains/0/"
1347+ },
1348+ "resource_uri": "/MAAS/api/2.0/rackcontrollers/7xtf67/"
1349+ },
1350+ {
1351+ "pool": {
1352+ "name": "default",
1353+ "description": "Default pool",
1354+ "id": 0,
1355+ "resource_uri": "/MAAS/api/2.0/resourcepool/0/"
1356+ },
1357+ "storage": 3865.490432,
1358+ "min_hwe_kernel": "",
1359+ "interface_set": [
1360+ {
1361+ "id": 94,
1362+ "name": "eth-x2dFvx",
1363+ "tags": [
1364+ "tag-fAzmsZ",
1365+ "tag-doKrVe",
1366+ "tag-ejryXL"
1367+ ],
1368+ "links": [
1369+ {
1370+ "id": 39,
1371+ "mode": "auto",
1372+ "subnet": {
1373+ "name": "name-rLI3eq",
1374+ "vlan": {
1375+ "vid": 0,
1376+ "mtu": 1500,
1377+ "dhcp_on": false,
1378+ "external_dhcp": null,
1379+ "relay_vlan": null,
1380+ "id": 5001,
1381+ "name": "untagged",
1382+ "fabric": "fabric-0",
1383+ "primary_rack": null,
1384+ "fabric_id": 0,
1385+ "secondary_rack": null,
1386+ "space": "management",
1387+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1388+ },
1389+ "cidr": "172.16.1.0/24",
1390+ "rdns_mode": 2,
1391+ "gateway_ip": "172.16.1.1",
1392+ "dns_servers": [
1393+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
1394+ ],
1395+ "allow_dns": true,
1396+ "allow_proxy": true,
1397+ "active_discovery": false,
1398+ "managed": true,
1399+ "id": 1,
1400+ "space": "management",
1401+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
1402+ }
1403+ }
1404+ ],
1405+ "firmware_version": null,
1406+ "effective_mtu": 1500,
1407+ "product": null,
1408+ "mac_address": "dd:c6:80:1a:c7:80",
1409+ "system_id": "ssqcgt",
1410+ "parents": [],
1411+ "children": [
1412+ "eth-x2dFvx.10"
1413+ ],
1414+ "params": "",
1415+ "enabled": true,
1416+ "discovered": null,
1417+ "vlan": {
1418+ "vid": 0,
1419+ "mtu": 1500,
1420+ "dhcp_on": false,
1421+ "external_dhcp": null,
1422+ "relay_vlan": null,
1423+ "id": 5001,
1424+ "name": "untagged",
1425+ "fabric": "fabric-0",
1426+ "primary_rack": null,
1427+ "fabric_id": 0,
1428+ "secondary_rack": null,
1429+ "space": "management",
1430+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1431+ },
1432+ "vendor": null,
1433+ "type": "physical",
1434+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/94/"
1435+ },
1436+ {
1437+ "id": 95,
1438+ "name": "eth-gtMGIr",
1439+ "tags": [
1440+ "tag-rW3Lmf",
1441+ "tag-DdASuZ",
1442+ "tag-MS8EYC"
1443+ ],
1444+ "links": [
1445+ {
1446+ "id": 40,
1447+ "mode": "auto",
1448+ "subnet": {
1449+ "name": "name-rLI3eq",
1450+ "vlan": {
1451+ "vid": 0,
1452+ "mtu": 1500,
1453+ "dhcp_on": false,
1454+ "external_dhcp": null,
1455+ "relay_vlan": null,
1456+ "id": 5001,
1457+ "name": "untagged",
1458+ "fabric": "fabric-0",
1459+ "primary_rack": null,
1460+ "fabric_id": 0,
1461+ "secondary_rack": null,
1462+ "space": "management",
1463+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1464+ },
1465+ "cidr": "172.16.1.0/24",
1466+ "rdns_mode": 2,
1467+ "gateway_ip": "172.16.1.1",
1468+ "dns_servers": [
1469+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
1470+ ],
1471+ "allow_dns": true,
1472+ "allow_proxy": true,
1473+ "active_discovery": false,
1474+ "managed": true,
1475+ "id": 1,
1476+ "space": "management",
1477+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
1478+ }
1479+ }
1480+ ],
1481+ "firmware_version": null,
1482+ "effective_mtu": 1500,
1483+ "product": null,
1484+ "mac_address": "52:db:98:ef:1e:b5",
1485+ "system_id": "ssqcgt",
1486+ "parents": [],
1487+ "children": [
1488+ "eth-gtMGIr.10"
1489+ ],
1490+ "params": "",
1491+ "enabled": true,
1492+ "discovered": null,
1493+ "vlan": {
1494+ "vid": 0,
1495+ "mtu": 1500,
1496+ "dhcp_on": false,
1497+ "external_dhcp": null,
1498+ "relay_vlan": null,
1499+ "id": 5001,
1500+ "name": "untagged",
1501+ "fabric": "fabric-0",
1502+ "primary_rack": null,
1503+ "fabric_id": 0,
1504+ "secondary_rack": null,
1505+ "space": "management",
1506+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1507+ },
1508+ "vendor": null,
1509+ "type": "physical",
1510+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/95/"
1511+ },
1512+ {
1513+ "id": 96,
1514+ "name": "eth-3cXhBN",
1515+ "tags": [
1516+ "tag-QisnXF",
1517+ "tag-duMUg2",
1518+ "tag-06ARMg"
1519+ ],
1520+ "links": [
1521+ {
1522+ "id": 41,
1523+ "mode": "static",
1524+ "ip_address": "172.16.1.22",
1525+ "subnet": {
1526+ "name": "name-rLI3eq",
1527+ "vlan": {
1528+ "vid": 0,
1529+ "mtu": 1500,
1530+ "dhcp_on": false,
1531+ "external_dhcp": null,
1532+ "relay_vlan": null,
1533+ "id": 5001,
1534+ "name": "untagged",
1535+ "fabric": "fabric-0",
1536+ "primary_rack": null,
1537+ "fabric_id": 0,
1538+ "secondary_rack": null,
1539+ "space": "management",
1540+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1541+ },
1542+ "cidr": "172.16.1.0/24",
1543+ "rdns_mode": 2,
1544+ "gateway_ip": "172.16.1.1",
1545+ "dns_servers": [
1546+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
1547+ ],
1548+ "allow_dns": true,
1549+ "allow_proxy": true,
1550+ "active_discovery": false,
1551+ "managed": true,
1552+ "id": 1,
1553+ "space": "management",
1554+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
1555+ }
1556+ }
1557+ ],
1558+ "firmware_version": null,
1559+ "effective_mtu": 1500,
1560+ "product": null,
1561+ "mac_address": "e3:72:73:60:9a:b9",
1562+ "system_id": "ssqcgt",
1563+ "parents": [],
1564+ "children": [
1565+ "eth-3cXhBN.10"
1566+ ],
1567+ "params": "",
1568+ "enabled": true,
1569+ "discovered": null,
1570+ "vlan": {
1571+ "vid": 0,
1572+ "mtu": 1500,
1573+ "dhcp_on": false,
1574+ "external_dhcp": null,
1575+ "relay_vlan": null,
1576+ "id": 5001,
1577+ "name": "untagged",
1578+ "fabric": "fabric-0",
1579+ "primary_rack": null,
1580+ "fabric_id": 0,
1581+ "secondary_rack": null,
1582+ "space": "management",
1583+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1584+ },
1585+ "vendor": null,
1586+ "type": "physical",
1587+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/96/"
1588+ },
1589+ {
1590+ "id": 97,
1591+ "name": "eth-x2dFvx.10",
1592+ "tags": [
1593+ "tag-Y5zzv5",
1594+ "tag-w5vECc",
1595+ "tag-o4znE9"
1596+ ],
1597+ "links": [
1598+ {
1599+ "id": 42,
1600+ "mode": "static",
1601+ "ip_address": "172.16.4.56",
1602+ "subnet": {
1603+ "name": "name-c2ULe1",
1604+ "vlan": {
1605+ "vid": 10,
1606+ "mtu": 1500,
1607+ "dhcp_on": false,
1608+ "external_dhcp": null,
1609+ "relay_vlan": null,
1610+ "id": 5002,
1611+ "name": "10",
1612+ "fabric": "fabric-0",
1613+ "primary_rack": null,
1614+ "fabric_id": 0,
1615+ "secondary_rack": null,
1616+ "space": "internal",
1617+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
1618+ },
1619+ "cidr": "172.16.4.0/24",
1620+ "rdns_mode": 2,
1621+ "gateway_ip": "172.16.4.1",
1622+ "dns_servers": [
1623+ "fd08:fef7:5c1f:a2e6:3d8e:6c3b:89f9:80cb",
1624+ "fc67:ad6a:88fe:9192:62f9:e882:8bcc:339e",
1625+ "255.59.162.158"
1626+ ],
1627+ "allow_dns": true,
1628+ "allow_proxy": true,
1629+ "active_discovery": false,
1630+ "managed": true,
1631+ "id": 4,
1632+ "space": "internal",
1633+ "resource_uri": "/MAAS/api/2.0/subnets/4/"
1634+ }
1635+ }
1636+ ],
1637+ "firmware_version": null,
1638+ "effective_mtu": 1500,
1639+ "product": null,
1640+ "mac_address": "dd:c6:80:1a:c7:80",
1641+ "system_id": "ssqcgt",
1642+ "parents": [
1643+ "eth-x2dFvx"
1644+ ],
1645+ "children": [],
1646+ "params": "",
1647+ "enabled": true,
1648+ "discovered": null,
1649+ "vlan": {
1650+ "vid": 10,
1651+ "mtu": 1500,
1652+ "dhcp_on": false,
1653+ "external_dhcp": null,
1654+ "relay_vlan": null,
1655+ "id": 5002,
1656+ "name": "10",
1657+ "fabric": "fabric-0",
1658+ "primary_rack": null,
1659+ "fabric_id": 0,
1660+ "secondary_rack": null,
1661+ "space": "internal",
1662+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
1663+ },
1664+ "vendor": null,
1665+ "type": "vlan",
1666+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/97/"
1667+ },
1668+ {
1669+ "id": 98,
1670+ "name": "eth-gtMGIr.10",
1671+ "tags": [
1672+ "tag-wS4OXL",
1673+ "tag-Wm3I55",
1674+ "tag-LE5UYY"
1675+ ],
1676+ "links": [
1677+ {
1678+ "id": 43,
1679+ "mode": "static",
1680+ "ip_address": "172.16.3.102",
1681+ "subnet": {
1682+ "name": "name-zznp45",
1683+ "vlan": {
1684+ "vid": 10,
1685+ "mtu": 1500,
1686+ "dhcp_on": false,
1687+ "external_dhcp": null,
1688+ "relay_vlan": null,
1689+ "id": 5002,
1690+ "name": "10",
1691+ "fabric": "fabric-0",
1692+ "primary_rack": null,
1693+ "fabric_id": 0,
1694+ "secondary_rack": null,
1695+ "space": "internal",
1696+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
1697+ },
1698+ "cidr": "172.16.3.0/24",
1699+ "rdns_mode": 2,
1700+ "gateway_ip": "172.16.3.1",
1701+ "dns_servers": [
1702+ "fd98:8601:90d0:c8c:dd2e:ba51:fa5a:dcfa",
1703+ "11.209.150.208",
1704+ "fde6:f9ef:3ee9:c5de:2a66:1582:cc83:abaf"
1705+ ],
1706+ "allow_dns": true,
1707+ "allow_proxy": true,
1708+ "active_discovery": false,
1709+ "managed": true,
1710+ "id": 3,
1711+ "space": "internal",
1712+ "resource_uri": "/MAAS/api/2.0/subnets/3/"
1713+ }
1714+ }
1715+ ],
1716+ "firmware_version": null,
1717+ "effective_mtu": 1500,
1718+ "product": null,
1719+ "mac_address": "52:db:98:ef:1e:b5",
1720+ "system_id": "ssqcgt",
1721+ "parents": [
1722+ "eth-gtMGIr"
1723+ ],
1724+ "children": [],
1725+ "params": "",
1726+ "enabled": true,
1727+ "discovered": null,
1728+ "vlan": {
1729+ "vid": 10,
1730+ "mtu": 1500,
1731+ "dhcp_on": false,
1732+ "external_dhcp": null,
1733+ "relay_vlan": null,
1734+ "id": 5002,
1735+ "name": "10",
1736+ "fabric": "fabric-0",
1737+ "primary_rack": null,
1738+ "fabric_id": 0,
1739+ "secondary_rack": null,
1740+ "space": "internal",
1741+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
1742+ },
1743+ "vendor": null,
1744+ "type": "vlan",
1745+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/98/"
1746+ },
1747+ {
1748+ "id": 99,
1749+ "name": "eth-3cXhBN.10",
1750+ "tags": [
1751+ "tag-Zp22Uh",
1752+ "tag-PhIZjP",
1753+ "tag-QtYg8x"
1754+ ],
1755+ "links": [
1756+ {
1757+ "id": 44,
1758+ "mode": "static",
1759+ "ip_address": "172.16.3.23",
1760+ "subnet": {
1761+ "name": "name-zznp45",
1762+ "vlan": {
1763+ "vid": 10,
1764+ "mtu": 1500,
1765+ "dhcp_on": false,
1766+ "external_dhcp": null,
1767+ "relay_vlan": null,
1768+ "id": 5002,
1769+ "name": "10",
1770+ "fabric": "fabric-0",
1771+ "primary_rack": null,
1772+ "fabric_id": 0,
1773+ "secondary_rack": null,
1774+ "space": "internal",
1775+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
1776+ },
1777+ "cidr": "172.16.3.0/24",
1778+ "rdns_mode": 2,
1779+ "gateway_ip": "172.16.3.1",
1780+ "dns_servers": [
1781+ "fd98:8601:90d0:c8c:dd2e:ba51:fa5a:dcfa",
1782+ "11.209.150.208",
1783+ "fde6:f9ef:3ee9:c5de:2a66:1582:cc83:abaf"
1784+ ],
1785+ "allow_dns": true,
1786+ "allow_proxy": true,
1787+ "active_discovery": false,
1788+ "managed": true,
1789+ "id": 3,
1790+ "space": "internal",
1791+ "resource_uri": "/MAAS/api/2.0/subnets/3/"
1792+ }
1793+ }
1794+ ],
1795+ "firmware_version": null,
1796+ "effective_mtu": 1500,
1797+ "product": null,
1798+ "mac_address": "e3:72:73:60:9a:b9",
1799+ "system_id": "ssqcgt",
1800+ "parents": [
1801+ "eth-3cXhBN"
1802+ ],
1803+ "children": [],
1804+ "params": "",
1805+ "enabled": true,
1806+ "discovered": null,
1807+ "vlan": {
1808+ "vid": 10,
1809+ "mtu": 1500,
1810+ "dhcp_on": false,
1811+ "external_dhcp": null,
1812+ "relay_vlan": null,
1813+ "id": 5002,
1814+ "name": "10",
1815+ "fabric": "fabric-0",
1816+ "primary_rack": null,
1817+ "fabric_id": 0,
1818+ "secondary_rack": null,
1819+ "space": "internal",
1820+ "resource_uri": "/MAAS/api/2.0/vlans/5002/"
1821+ },
1822+ "vendor": null,
1823+ "type": "vlan",
1824+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/99/"
1825+ }
1826+ ],
1827+ "iscsiblockdevice_set": [],
1828+ "node_type": 0,
1829+ "memory_test_status_name": "Failed",
1830+ "bcaches": [],
1831+ "memory_test_status": 3,
1832+ "ip_addresses": [
1833+ "172.16.1.22",
1834+ "172.16.4.56",
1835+ "172.16.3.102",
1836+ "172.16.3.23"
1837+ ],
1838+ "swap_size": null,
1839+ "volume_groups": [],
1840+ "testing_status": 3,
1841+ "node_type_name": "Machine",
1842+ "current_testing_result_id": 153,
1843+ "power_type": "virsh",
1844+ "fqdn": "chief-hippo.ubnt",
1845+ "storage_test_status_name": "Failed",
1846+ "storage_test_status": 3,
1847+ "status_name": "Rescue mode",
1848+ "locked": false,
1849+ "netboot": true,
1850+ "tag_names": [
1851+ "virtual"
1852+ ],
1853+ "zone": {
1854+ "name": "zone-north",
1855+ "description": "xsMaq90fRE",
1856+ "id": 2,
1857+ "resource_uri": "/MAAS/api/2.0/zones/zone-north/"
1858+ },
1859+ "boot_disk": {
1860+ "firmware_version": "firmware_version-rszebt",
1861+ "uuid": null,
1862+ "id": 65,
1863+ "model": "model-u038bu",
1864+ "name": "name-2V9gLL",
1865+ "tags": [
1866+ "tag-wSvQ4O",
1867+ "tag-52MbPv",
1868+ "tag-JKilHY"
1869+ ],
1870+ "partitions": [],
1871+ "partition_table_type": null,
1872+ "used_size": 0,
1873+ "storage_pool": "pool_id-aMRZUu",
1874+ "id_path": null,
1875+ "system_id": "ssqcgt",
1876+ "block_size": 512,
1877+ "filesystem": null,
1878+ "used_for": "Unused",
1879+ "serial": "serial-Lh6Yv9",
1880+ "path": "/dev/disk/by-dname/name-2V9gLL",
1881+ "type": "physical",
1882+ "size": 3865490432,
1883+ "available_size": 3865490432,
1884+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/blockdevices/65/"
1885+ },
1886+ "commissioning_status": 2,
1887+ "cpu_test_status": 3,
1888+ "current_installation_result_id": null,
1889+ "owner_data": {},
1890+ "power_state": "on",
1891+ "current_commissioning_result_id": 152,
1892+ "status": 16,
1893+ "hwe_kernel": null,
1894+ "boot_interface": {
1895+ "id": 94,
1896+ "name": "eth-x2dFvx",
1897+ "tags": [
1898+ "tag-fAzmsZ",
1899+ "tag-doKrVe",
1900+ "tag-ejryXL"
1901+ ],
1902+ "links": [
1903+ {
1904+ "id": 39,
1905+ "mode": "auto",
1906+ "subnet": {
1907+ "name": "name-rLI3eq",
1908+ "vlan": {
1909+ "vid": 0,
1910+ "mtu": 1500,
1911+ "dhcp_on": false,
1912+ "external_dhcp": null,
1913+ "relay_vlan": null,
1914+ "id": 5001,
1915+ "name": "untagged",
1916+ "fabric": "fabric-0",
1917+ "primary_rack": null,
1918+ "fabric_id": 0,
1919+ "secondary_rack": null,
1920+ "space": "management",
1921+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1922+ },
1923+ "cidr": "172.16.1.0/24",
1924+ "rdns_mode": 2,
1925+ "gateway_ip": "172.16.1.1",
1926+ "dns_servers": [
1927+ "fd89:8724:81f1:5512:557f:99c3:6967:8d63"
1928+ ],
1929+ "allow_dns": true,
1930+ "allow_proxy": true,
1931+ "active_discovery": false,
1932+ "managed": true,
1933+ "id": 1,
1934+ "space": "management",
1935+ "resource_uri": "/MAAS/api/2.0/subnets/1/"
1936+ }
1937+ }
1938+ ],
1939+ "firmware_version": null,
1940+ "effective_mtu": 1500,
1941+ "product": null,
1942+ "mac_address": "dd:c6:80:1a:c7:80",
1943+ "system_id": "ssqcgt",
1944+ "parents": [],
1945+ "children": [
1946+ "eth-x2dFvx.10"
1947+ ],
1948+ "params": "",
1949+ "enabled": true,
1950+ "discovered": null,
1951+ "vlan": {
1952+ "vid": 0,
1953+ "mtu": 1500,
1954+ "dhcp_on": false,
1955+ "external_dhcp": null,
1956+ "relay_vlan": null,
1957+ "id": 5001,
1958+ "name": "untagged",
1959+ "fabric": "fabric-0",
1960+ "primary_rack": null,
1961+ "fabric_id": 0,
1962+ "secondary_rack": null,
1963+ "space": "management",
1964+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
1965+ },
1966+ "vendor": null,
1967+ "type": "physical",
1968+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/interfaces/94/"
1969+ },
1970+ "special_filesystems": [],
1971+ "cpu_speed": 0,
1972+ "architecture": "amd64/generic",
1973+ "blockdevice_set": [
1974+ {
1975+ "id_path": null,
1976+ "size": 3865490432,
1977+ "block_size": 512,
1978+ "tags": [
1979+ "tag-wSvQ4O",
1980+ "tag-52MbPv",
1981+ "tag-JKilHY"
1982+ ],
1983+ "uuid": null,
1984+ "id": 65,
1985+ "model": "model-u038bu",
1986+ "name": "name-2V9gLL",
1987+ "partitions": [],
1988+ "partition_table_type": null,
1989+ "used_size": 0,
1990+ "storage_pool": "pool_id-aMRZUu",
1991+ "system_id": "ssqcgt",
1992+ "filesystem": null,
1993+ "used_for": "Unused",
1994+ "serial": "serial-Lh6Yv9",
1995+ "path": "/dev/disk/by-dname/name-2V9gLL",
1996+ "type": "physical",
1997+ "available_size": 3865490432,
1998+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/blockdevices/65/"
1999+ }
2000+ ],
2001+ "cpu_test_status_name": "Failed",
2002+ "other_test_status": 2,
2003+ "hostname": "chief-hippo",
2004+ "memory": 4096,
2005+ "default_gateways": {
2006+ "ipv4": {
2007+ "gateway_ip": "172.16.1.1",
2008+ "link_id": null
2009+ },
2010+ "ipv6": {
2011+ "gateway_ip": null,
2012+ "link_id": null
2013+ }
2014+ },
2015+ "distro_series": "",
2016+ "status_action": "action-ft28IH",
2017+ "owner": "user1",
2018+ "commissioning_status_name": "Passed",
2019+ "hardware_info": {
2020+ "system_vendor": "Unknown",
2021+ "system_product": "Unknown",
2022+ "system_version": "Unknown",
2023+ "system_serial": "Unknown",
2024+ "cpu_model": "Unknown",
2025+ "mainboard_vendor": "Unknown",
2026+ "mainboard_product": "Unknown",
2027+ "mainboard_firmware_version": "Unknown",
2028+ "mainboard_firmware_date": "Unknown"
2029+ },
2030+ "pod": {
2031+ "id": 5,
2032+ "name": "normal-trout",
2033+ "resource_uri": "/MAAS/api/2.0/pods/5/"
2034+ },
2035+ "cache_sets": [],
2036+ "testing_status_name": "Failed",
2037+ "disable_ipv4": false,
2038+ "physicalblockdevice_set": [
2039+ {
2040+ "firmware_version": "firmware_version-rszebt",
2041+ "uuid": null,
2042+ "id": 65,
2043+ "model": "model-u038bu",
2044+ "name": "name-2V9gLL",
2045+ "tags": [
2046+ "tag-wSvQ4O",
2047+ "tag-52MbPv",
2048+ "tag-JKilHY"
2049+ ],
2050+ "partitions": [],
2051+ "partition_table_type": null,
2052+ "used_size": 0,
2053+ "storage_pool": "pool_id-aMRZUu",
2054+ "id_path": null,
2055+ "system_id": "ssqcgt",
2056+ "block_size": 512,
2057+ "filesystem": null,
2058+ "used_for": "Unused",
2059+ "serial": "serial-Lh6Yv9",
2060+ "path": "/dev/disk/by-dname/name-2V9gLL",
2061+ "type": "physical",
2062+ "size": 3865490432,
2063+ "available_size": 3865490432,
2064+ "resource_uri": "/MAAS/api/2.0/nodes/ssqcgt/blockdevices/65/"
2065+ }
2066+ ],
2067+ "osystem": "",
2068+ "system_id": "ssqcgt",
2069+ "address_ttl": null,
2070+ "raids": [],
2071+ "cpu_count": 5,
2072+ "virtualblockdevice_set": [],
2073+ "other_test_status_name": "Passed",
2074+ "status_message": "desc-SfeEsi",
2075+ "domain": {
2076+ "authoritative": true,
2077+ "ttl": null,
2078+ "id": 2,
2079+ "name": "ubnt",
2080+ "resource_record_count": 0,
2081+ "is_default": false,
2082+ "resource_uri": "/MAAS/api/2.0/domains/2/"
2083+ },
2084+ "resource_uri": "/MAAS/api/2.0/machines/ssqcgt/"
2085+ },
2086+ {
2087+ "tag_names": [
2088+ "virtual"
2089+ ],
2090+ "interface_set": [
2091+ {
2092+ "id": 130,
2093+ "name": "eth-tNqklu",
2094+ "tags": [
2095+ "tag-YYQK5S",
2096+ "tag-MUmKRU",
2097+ "tag-dh0kc2"
2098+ ],
2099+ "links": [],
2100+ "firmware_version": null,
2101+ "effective_mtu": 1500,
2102+ "product": null,
2103+ "mac_address": "bb:f7:55:fd:0f:34",
2104+ "system_id": "nqg6dg",
2105+ "parents": [],
2106+ "children": [],
2107+ "params": "",
2108+ "enabled": true,
2109+ "discovered": null,
2110+ "vlan": {
2111+ "vid": 0,
2112+ "mtu": 1500,
2113+ "dhcp_on": false,
2114+ "external_dhcp": null,
2115+ "relay_vlan": null,
2116+ "id": 5013,
2117+ "name": "untagged",
2118+ "fabric": "fabric-9",
2119+ "primary_rack": null,
2120+ "fabric_id": 9,
2121+ "secondary_rack": null,
2122+ "space": "undefined",
2123+ "resource_uri": "/MAAS/api/2.0/vlans/5013/"
2124+ },
2125+ "vendor": null,
2126+ "type": "physical",
2127+ "resource_uri": "/MAAS/api/2.0/nodes/nqg6dg/interfaces/130/"
2128+ }
2129+ ],
2130+ "zone": {
2131+ "name": "default",
2132+ "description": "",
2133+ "id": 1,
2134+ "resource_uri": "/MAAS/api/2.0/zones/default/"
2135+ },
2136+ "node_type": 1,
2137+ "hostname": "calm-bobcat",
2138+ "system_id": "nqg6dg",
2139+ "owner_data": {},
2140+ "parent": null,
2141+ "address_ttl": null,
2142+ "owner": null,
2143+ "ip_addresses": [],
2144+ "node_type_name": "Device",
2145+ "fqdn": "calm-bobcat.maas",
2146+ "domain": {
2147+ "authoritative": true,
2148+ "ttl": null,
2149+ "id": 0,
2150+ "name": "maas",
2151+ "resource_record_count": 0,
2152+ "is_default": true,
2153+ "resource_uri": "/MAAS/api/2.0/domains/0/"
2154+ },
2155+ "resource_uri": "/MAAS/api/2.0/devices/nqg6dg/"
2156+ }
2157+ ],
2158+ "get-rackc-by-tag": [
2159+ {
2160+ "interface_set": [
2161+ {
2162+ "id": 1,
2163+ "name": "ens3",
2164+ "tags": [
2165+ "tag-0wj45r",
2166+ "tag-ylwPaA",
2167+ "tag-s8HCFS"
2168+ ],
2169+ "links": [
2170+ {
2171+ "id": 59,
2172+ "mode": "static",
2173+ "ip_address": "10.55.32.42",
2174+ "subnet": {
2175+ "name": "10.55.32.0/20",
2176+ "vlan": {
2177+ "vid": 0,
2178+ "mtu": 1500,
2179+ "dhcp_on": false,
2180+ "external_dhcp": null,
2181+ "relay_vlan": null,
2182+ "id": 5001,
2183+ "name": "untagged",
2184+ "fabric": "fabric-0",
2185+ "primary_rack": null,
2186+ "fabric_id": 0,
2187+ "secondary_rack": null,
2188+ "space": "management",
2189+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
2190+ },
2191+ "cidr": "10.55.32.0/20",
2192+ "rdns_mode": 2,
2193+ "gateway_ip": "10.55.32.1",
2194+ "dns_servers": [],
2195+ "allow_dns": true,
2196+ "allow_proxy": true,
2197+ "active_discovery": false,
2198+ "managed": true,
2199+ "id": 6,
2200+ "space": "management",
2201+ "resource_uri": "/MAAS/api/2.0/subnets/6/"
2202+ }
2203+ }
2204+ ],
2205+ "firmware_version": "N/A",
2206+ "effective_mtu": 1500,
2207+ "product": "OpenStack Nova",
2208+ "mac_address": "fa:16:3e:3d:03:02",
2209+ "system_id": "7xtf67",
2210+ "parents": [],
2211+ "children": [],
2212+ "params": "",
2213+ "enabled": true,
2214+ "discovered": null,
2215+ "vlan": {
2216+ "vid": 0,
2217+ "mtu": 1500,
2218+ "dhcp_on": false,
2219+ "external_dhcp": null,
2220+ "relay_vlan": null,
2221+ "id": 5001,
2222+ "name": "untagged",
2223+ "fabric": "fabric-0",
2224+ "primary_rack": null,
2225+ "fabric_id": 0,
2226+ "secondary_rack": null,
2227+ "space": "management",
2228+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
2229+ },
2230+ "vendor": "OpenStack Foundation",
2231+ "type": "physical",
2232+ "resource_uri": "/MAAS/api/2.0/nodes/7xtf67/interfaces/1/"
2233+ },
2234+ {
2235+ "id": 134,
2236+ "name": "virbr0",
2237+ "tags": [],
2238+ "links": [
2239+ {
2240+ "id": 60,
2241+ "mode": "static",
2242+ "ip_address": "192.168.122.1",
2243+ "subnet": {
2244+ "name": "192.168.122.0/24",
2245+ "vlan": {
2246+ "vid": 0,
2247+ "mtu": 1500,
2248+ "dhcp_on": false,
2249+ "external_dhcp": null,
2250+ "relay_vlan": null,
2251+ "id": 5017,
2252+ "name": "untagged",
2253+ "fabric": "fabric-13",
2254+ "primary_rack": null,
2255+ "fabric_id": 13,
2256+ "secondary_rack": null,
2257+ "space": "undefined",
2258+ "resource_uri": "/MAAS/api/2.0/vlans/5017/"
2259+ },
2260+ "cidr": "192.168.122.0/24",
2261+ "rdns_mode": 2,
2262+ "gateway_ip": null,
2263+ "dns_servers": [],
2264+ "allow_dns": true,
2265+ "allow_proxy": true,
2266+ "active_discovery": false,
2267+ "managed": true,
2268+ "id": 7,
2269+ "space": "undefined",
2270+ "resource_uri": "/MAAS/api/2.0/subnets/7/"
2271+ }
2272+ }
2273+ ],
2274+ "firmware_version": "N/A",
2275+ "effective_mtu": 1500,
2276+ "product": "OpenStack Nova",
2277+ "mac_address": "52:54:00:95:f3:f3",
2278+ "system_id": "7xtf67",
2279+ "parents": [],
2280+ "children": [],
2281+ "params": "",
2282+ "enabled": true,
2283+ "discovered": null,
2284+ "vlan": {
2285+ "vid": 0,
2286+ "mtu": 1500,
2287+ "dhcp_on": false,
2288+ "external_dhcp": null,
2289+ "relay_vlan": null,
2290+ "id": 5017,
2291+ "name": "untagged",
2292+ "fabric": "fabric-13",
2293+ "primary_rack": null,
2294+ "fabric_id": 13,
2295+ "secondary_rack": null,
2296+ "space": "undefined",
2297+ "resource_uri": "/MAAS/api/2.0/vlans/5017/"
2298+ },
2299+ "vendor": "OpenStack Foundation",
2300+ "type": "bridge",
2301+ "resource_uri": "/MAAS/api/2.0/nodes/7xtf67/interfaces/134/"
2302+ }
2303+ ],
2304+ "node_type": 4,
2305+ "memory_test_status_name": "Unknown",
2306+ "memory_test_status": -1,
2307+ "ip_addresses": [
2308+ "10.55.32.42",
2309+ "192.168.122.1"
2310+ ],
2311+ "swap_size": null,
2312+ "testing_status": -1,
2313+ "node_type_name": "Region and rack controller",
2314+ "current_testing_result_id": null,
2315+ "power_type": "virsh",
2316+ "fqdn": "spence-devmaas.maas",
2317+ "storage_test_status_name": "Unknown",
2318+ "storage_test_status": -1,
2319+ "tag_names": [
2320+ "virtual"
2321+ ],
2322+ "zone": {
2323+ "name": "zone-obryVj",
2324+ "description": "qRwvQuFIpM",
2325+ "id": 4,
2326+ "resource_uri": "/MAAS/api/2.0/zones/zone-obryVj/"
2327+ },
2328+ "commissioning_status": 2,
2329+ "cpu_test_status": -1,
2330+ "current_installation_result_id": null,
2331+ "power_state": "on",
2332+ "current_commissioning_result_id": 228,
2333+ "cpu_speed": 2500,
2334+ "architecture": "amd64/generic",
2335+ "cpu_test_status_name": "Unknown",
2336+ "other_test_status": -1,
2337+ "hostname": "spence-devmaas",
2338+ "memory": 8192,
2339+ "commissioning_status_name": "Passed",
2340+ "distro_series": "bionic",
2341+ "status_action": "",
2342+ "hardware_info": {
2343+ "system_vendor": "OpenStack Foundation",
2344+ "system_product": "OpenStack Nova",
2345+ "system_version": "2013.2.3",
2346+ "system_serial": "30353036-3837-5a43-3331-353234584a58",
2347+ "cpu_model": "Westmere E56xx/L56xx/X56xx (Nehalem-C)",
2348+ "mainboard_vendor": "Unknown",
2349+ "mainboard_product": "Unknown",
2350+ "mainboard_firmware_version": "Bochs",
2351+ "mainboard_firmware_date": "01/01/2011"
2352+ },
2353+ "service_set": [
2354+ {
2355+ "name": "proxy",
2356+ "status": "unknown",
2357+ "status_info": ""
2358+ },
2359+ {
2360+ "name": "proxy_rack",
2361+ "status": "unknown",
2362+ "status_info": ""
2363+ },
2364+ {
2365+ "name": "dhcpd6",
2366+ "status": "dead",
2367+ "status_info": ""
2368+ },
2369+ {
2370+ "name": "ntp_rack",
2371+ "status": "dead",
2372+ "status_info": ""
2373+ },
2374+ {
2375+ "name": "syslog_region",
2376+ "status": "unknown",
2377+ "status_info": ""
2378+ },
2379+ {
2380+ "name": "http",
2381+ "status": "unknown",
2382+ "status_info": ""
2383+ },
2384+ {
2385+ "name": "tftp",
2386+ "status": "dead",
2387+ "status_info": ""
2388+ },
2389+ {
2390+ "name": "bind9",
2391+ "status": "unknown",
2392+ "status_info": ""
2393+ },
2394+ {
2395+ "name": "rackd",
2396+ "status": "dead",
2397+ "status_info": ""
2398+ },
2399+ {
2400+ "name": "ntp_region",
2401+ "status": "unknown",
2402+ "status_info": ""
2403+ },
2404+ {
2405+ "name": "regiond",
2406+ "status": "running",
2407+ "status_info": ""
2408+ },
2409+ {
2410+ "name": "dns_rack",
2411+ "status": "unknown",
2412+ "status_info": ""
2413+ },
2414+ {
2415+ "name": "syslog_rack",
2416+ "status": "unknown",
2417+ "status_info": ""
2418+ },
2419+ {
2420+ "name": "dhcpd",
2421+ "status": "dead",
2422+ "status_info": ""
2423+ }
2424+ ],
2425+ "testing_status_name": "Unknown",
2426+ "osystem": "ubuntu",
2427+ "version": "",
2428+ "system_id": "7xtf67",
2429+ "cpu_count": 4,
2430+ "other_test_status_name": "Unknown",
2431+ "domain": {
2432+ "authoritative": true,
2433+ "ttl": null,
2434+ "id": 0,
2435+ "name": "maas",
2436+ "resource_record_count": 0,
2437+ "is_default": true,
2438+ "resource_uri": "/MAAS/api/2.0/domains/0/"
2439+ },
2440+ "resource_uri": "/MAAS/api/2.0/rackcontrollers/7xtf67/"
2441+ }
2442+ ],
2443+ "get-regionc-by-tag": [
2444+ {
2445+ "interface_set": [
2446+ {
2447+ "id": 1,
2448+ "name": "ens3",
2449+ "tags": [
2450+ "tag-0wj45r",
2451+ "tag-ylwPaA",
2452+ "tag-s8HCFS"
2453+ ],
2454+ "links": [
2455+ {
2456+ "id": 59,
2457+ "mode": "static",
2458+ "ip_address": "10.55.32.42",
2459+ "subnet": {
2460+ "name": "10.55.32.0/20",
2461+ "vlan": {
2462+ "vid": 0,
2463+ "mtu": 1500,
2464+ "dhcp_on": false,
2465+ "external_dhcp": null,
2466+ "relay_vlan": null,
2467+ "id": 5001,
2468+ "name": "untagged",
2469+ "fabric": "fabric-0",
2470+ "primary_rack": null,
2471+ "fabric_id": 0,
2472+ "secondary_rack": null,
2473+ "space": "management",
2474+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
2475+ },
2476+ "cidr": "10.55.32.0/20",
2477+ "rdns_mode": 2,
2478+ "gateway_ip": "10.55.32.1",
2479+ "dns_servers": [],
2480+ "allow_dns": true,
2481+ "allow_proxy": true,
2482+ "active_discovery": false,
2483+ "managed": true,
2484+ "id": 6,
2485+ "space": "management",
2486+ "resource_uri": "/MAAS/api/2.0/subnets/6/"
2487+ }
2488+ }
2489+ ],
2490+ "firmware_version": "N/A",
2491+ "effective_mtu": 1500,
2492+ "product": "OpenStack Nova",
2493+ "mac_address": "fa:16:3e:3d:03:02",
2494+ "system_id": "7xtf67",
2495+ "parents": [],
2496+ "children": [],
2497+ "params": "",
2498+ "enabled": true,
2499+ "discovered": null,
2500+ "vlan": {
2501+ "vid": 0,
2502+ "mtu": 1500,
2503+ "dhcp_on": false,
2504+ "external_dhcp": null,
2505+ "relay_vlan": null,
2506+ "id": 5001,
2507+ "name": "untagged",
2508+ "fabric": "fabric-0",
2509+ "primary_rack": null,
2510+ "fabric_id": 0,
2511+ "secondary_rack": null,
2512+ "space": "management",
2513+ "resource_uri": "/MAAS/api/2.0/vlans/5001/"
2514+ },
2515+ "vendor": "OpenStack Foundation",
2516+ "type": "physical",
2517+ "resource_uri": "/MAAS/api/2.0/nodes/7xtf67/interfaces/1/"
2518+ },
2519+ {
2520+ "id": 134,
2521+ "name": "virbr0",
2522+ "tags": [],
2523+ "links": [
2524+ {
2525+ "id": 60,
2526+ "mode": "static",
2527+ "ip_address": "192.168.122.1",
2528+ "subnet": {
2529+ "name": "192.168.122.0/24",
2530+ "vlan": {
2531+ "vid": 0,
2532+ "mtu": 1500,
2533+ "dhcp_on": false,
2534+ "external_dhcp": null,
2535+ "relay_vlan": null,
2536+ "id": 5017,
2537+ "name": "untagged",
2538+ "fabric": "fabric-13",
2539+ "primary_rack": null,
2540+ "fabric_id": 13,
2541+ "secondary_rack": null,
2542+ "space": "undefined",
2543+ "resource_uri": "/MAAS/api/2.0/vlans/5017/"
2544+ },
2545+ "cidr": "192.168.122.0/24",
2546+ "rdns_mode": 2,
2547+ "gateway_ip": null,
2548+ "dns_servers": [],
2549+ "allow_dns": true,
2550+ "allow_proxy": true,
2551+ "active_discovery": false,
2552+ "managed": true,
2553+ "id": 7,
2554+ "space": "undefined",
2555+ "resource_uri": "/MAAS/api/2.0/subnets/7/"
2556+ }
2557+ }
2558+ ],
2559+ "firmware_version": "N/A",
2560+ "effective_mtu": 1500,
2561+ "product": "OpenStack Nova",
2562+ "mac_address": "52:54:00:95:f3:f3",
2563+ "system_id": "7xtf67",
2564+ "parents": [],
2565+ "children": [],
2566+ "params": "",
2567+ "enabled": true,
2568+ "discovered": null,
2569+ "vlan": {
2570+ "vid": 0,
2571+ "mtu": 1500,
2572+ "dhcp_on": false,
2573+ "external_dhcp": null,
2574+ "relay_vlan": null,
2575+ "id": 5017,
2576+ "name": "untagged",
2577+ "fabric": "fabric-13",
2578+ "primary_rack": null,
2579+ "fabric_id": 13,
2580+ "secondary_rack": null,
2581+ "space": "undefined",
2582+ "resource_uri": "/MAAS/api/2.0/vlans/5017/"
2583+ },
2584+ "vendor": "OpenStack Foundation",
2585+ "type": "bridge",
2586+ "resource_uri": "/MAAS/api/2.0/nodes/7xtf67/interfaces/134/"
2587+ }
2588+ ],
2589+ "node_type": 4,
2590+ "memory_test_status_name": "Unknown",
2591+ "memory_test_status": -1,
2592+ "ip_addresses": [
2593+ "10.55.32.42",
2594+ "192.168.122.1"
2595+ ],
2596+ "swap_size": null,
2597+ "testing_status": -1,
2598+ "node_type_name": "Region and rack controller",
2599+ "current_testing_result_id": null,
2600+ "power_type": "virsh",
2601+ "fqdn": "spence-devmaas.maas",
2602+ "storage_test_status_name": "Unknown",
2603+ "storage_test_status": -1,
2604+ "tag_names": [
2605+ "virtual"
2606+ ],
2607+ "zone": {
2608+ "name": "zone-obryVj",
2609+ "description": "qRwvQuFIpM",
2610+ "id": 4,
2611+ "resource_uri": "/MAAS/api/2.0/zones/zone-obryVj/"
2612+ },
2613+ "commissioning_status": 2,
2614+ "cpu_test_status": -1,
2615+ "current_installation_result_id": null,
2616+ "power_state": "on",
2617+ "current_commissioning_result_id": 228,
2618+ "cpu_speed": 2500,
2619+ "architecture": "amd64/generic",
2620+ "cpu_test_status_name": "Unknown",
2621+ "other_test_status": -1,
2622+ "hostname": "spence-devmaas",
2623+ "memory": 8192,
2624+ "commissioning_status_name": "Passed",
2625+ "distro_series": "bionic",
2626+ "status_action": "",
2627+ "hardware_info": {
2628+ "system_vendor": "OpenStack Foundation",
2629+ "system_product": "OpenStack Nova",
2630+ "system_version": "2013.2.3",
2631+ "system_serial": "30353036-3837-5a43-3331-353234584a58",
2632+ "cpu_model": "Westmere E56xx/L56xx/X56xx (Nehalem-C)",
2633+ "mainboard_vendor": "Unknown",
2634+ "mainboard_product": "Unknown",
2635+ "mainboard_firmware_version": "Bochs",
2636+ "mainboard_firmware_date": "01/01/2011"
2637+ },
2638+ "service_set": [
2639+ {
2640+ "name": "proxy",
2641+ "status": "unknown",
2642+ "status_info": ""
2643+ },
2644+ {
2645+ "name": "proxy_rack",
2646+ "status": "unknown",
2647+ "status_info": ""
2648+ },
2649+ {
2650+ "name": "dhcpd6",
2651+ "status": "dead",
2652+ "status_info": ""
2653+ },
2654+ {
2655+ "name": "ntp_rack",
2656+ "status": "dead",
2657+ "status_info": ""
2658+ },
2659+ {
2660+ "name": "syslog_region",
2661+ "status": "unknown",
2662+ "status_info": ""
2663+ },
2664+ {
2665+ "name": "http",
2666+ "status": "unknown",
2667+ "status_info": ""
2668+ },
2669+ {
2670+ "name": "tftp",
2671+ "status": "dead",
2672+ "status_info": ""
2673+ },
2674+ {
2675+ "name": "bind9",
2676+ "status": "unknown",
2677+ "status_info": ""
2678+ },
2679+ {
2680+ "name": "rackd",
2681+ "status": "dead",
2682+ "status_info": ""
2683+ },
2684+ {
2685+ "name": "ntp_region",
2686+ "status": "unknown",
2687+ "status_info": ""
2688+ },
2689+ {
2690+ "name": "regiond",
2691+ "status": "running",
2692+ "status_info": ""
2693+ },
2694+ {
2695+ "name": "dns_rack",
2696+ "status": "unknown",
2697+ "status_info": ""
2698+ },
2699+ {
2700+ "name": "syslog_rack",
2701+ "status": "unknown",
2702+ "status_info": ""
2703+ },
2704+ {
2705+ "name": "dhcpd",
2706+ "status": "dead",
2707+ "status_info": ""
2708+ }
2709+ ],
2710+ "testing_status_name": "Unknown",
2711+ "osystem": "ubuntu",
2712+ "version": "",
2713+ "system_id": "7xtf67",
2714+ "cpu_count": 4,
2715+ "other_test_status_name": "Unknown",
2716+ "domain": {
2717+ "authoritative": true,
2718+ "ttl": null,
2719+ "id": 0,
2720+ "name": "maas",
2721+ "resource_record_count": 0,
2722+ "is_default": true,
2723+ "resource_uri": "/MAAS/api/2.0/domains/0/"
2724+ },
2725+ "resource_uri": "/MAAS/api/2.0/rackcontrollers/7xtf67/"
2726+ }
2727+ ],
2728+ "add-tag": {
2729+ "name": "footag",
2730+ "definition": "",
2731+ "comment": "some comment",
2732+ "kernel_opts": "",
2733+ "resource_uri": "/MAAS/api/2.0/tags/footag/"
2734+ },
2735+ "rebuild-tag": {
2736+ "rebuilding": "footag"
2737+ },
2738+ "update-nodes-tag": {
2739+ "added": 1,
2740+ "removed": 0
2741+ },
2742+ "update-tag": {
2743+ "name": "my_tag",
2744+ "definition": "",
2745+ "comment": "a comment about this tag",
2746+ "kernel_opts": "",
2747+ "resource_uri": "/MAAS/api/2.0/tags/my_tag/"
2748+ },
2749+ "delete-tag": "No content"
2750+}
2751diff --git a/src/maasserver/api/tags.py b/src/maasserver/api/tags.py
2752index 91cf52f..f4dcf54 100644
2753--- a/src/maasserver/api/tags.py
2754+++ b/src/maasserver/api/tags.py
2755@@ -74,8 +74,7 @@ def check_rack_controller_access(request, rack_controller):
2756
2757
2758 class TagHandler(OperationsHandler):
2759- """Manage a Tag.
2760-
2761+ """
2762 Tags are properties that can be associated with a Node and serve as
2763 criteria for selecting and allocating nodes.
2764
2765@@ -92,23 +91,52 @@ class TagHandler(OperationsHandler):
2766 )
2767
2768 def read(self, request, name):
2769- """Read a specific Tag.
2770-
2771- Returns 404 if the tag is not found.
2772+ """@description-title Read a specific tag
2773+ @description Returns a JSON object containing information about a
2774+ specific tag.
2775+
2776+ @param (url-string) "{name}" [required=true] A tag name.
2777+ @param-example "{name}" virtual
2778+
2779+ @success (http-status-code) "server-success" 200
2780+ @success (json) "success-json" A JSON object containing
2781+ information about the requested tag.
2782+ @success-example "success-json" [exkey=get-tag-by-name] placeholder
2783+
2784+ @error (http-status-code) "404" 404
2785+ @error (content) "not-found" The requested tag name is not found.
2786+ @error-example "not-found"
2787+ Not Found
2788 """
2789 return Tag.objects.get_tag_or_404(name=name, user=request.user)
2790
2791 def update(self, request, name):
2792- """Update a specific Tag.
2793-
2794- :param name: The name of the Tag to be created. This should be a short
2795- name, and will be used in the URL of the tag.
2796- :param comment: A long form description of what the tag is meant for.
2797- It is meant as a human readable description of the tag.
2798- :param definition: An XPATH query that will be evaluated against the
2799- hardware_details stored for all nodes (output of `lshw -xml`).
2800-
2801- Returns 404 if the tag is not found.
2802+ """@description-title Update a tag
2803+ @description Update elements of a given tag.
2804+
2805+ @param (url-string) "{name}" [required=true] The tag to update.
2806+ @param-example "{name}" oldname
2807+ @param (string) "name" [required=false] The new tag name. Because
2808+ the name will be used in urls, it should be short.
2809+ @param-example "name" virtual
2810+ @param (string) "comment" [required=false] A description of what the
2811+ the tag will be used for in natural language.
2812+ @param-example "comment" The 'virtual' tag represents virtual
2813+ machines.
2814+ @param (string) "definition" [required=false] An XPATH query that is
2815+ evaluated against the hardware_details stored for all nodes
2816+ (i.e. the output of ``lshw -xml``).
2817+ @param-example "definition"
2818+ //node[@id="display"]/'clock units="Hz"' > 1000000000
2819+
2820+ @success (http-status-code) "200" 200
2821+ @success (json) "success-json" A JSON tag object.
2822+ @success-example "success-json" [exkey=update-tag] placeholder
2823+
2824+ @error (http-status-code) "404" 404
2825+ @error (content) "not-found" The requested tag name is not found.
2826+ @error-example "not-found"
2827+ Not Found
2828 """
2829 tag = Tag.objects.get_tag_or_404(
2830 name=name, user=request.user, to_edit=True)
2831@@ -125,10 +153,18 @@ class TagHandler(OperationsHandler):
2832 raise MAASAPIValidationError(form.errors)
2833
2834 def delete(self, request, name):
2835- """Delete a specific Tag.
2836+ """@description-title Delete a tag
2837+ @description Deletes a tag by name.
2838
2839- Returns 404 if the tag is not found.
2840- Returns 204 if the tag is successfully deleted.
2841+ @param (url-string) "{name}" [required=true] A tag name.
2842+ @param-example "{name}" virtual
2843+
2844+ @success (http-status-code) "204" 204
2845+
2846+ @error (http-status-code) "404" 404
2847+ @error (content) "not-found" The requested tag name is not found.
2848+ @error-example "not-found"
2849+ Not Found
2850 """
2851 tag = Tag.objects.get_tag_or_404(
2852 name=name, user=request.user, to_edit=True)
2853@@ -160,41 +196,101 @@ class TagHandler(OperationsHandler):
2854
2855 @operation(idempotent=True)
2856 def nodes(self, request, name):
2857- """Get the list of nodes that have this tag.
2858+ """@description-title List nodes by tag
2859+ @description Get a JSON list containing node objects that match
2860+ the given tag name.
2861+
2862+ @param (url-string) "{name}" [required=true] A tag name.
2863+ @param-example "{name}" virtual
2864
2865- Returns 404 if the tag is not found.
2866+ @success (json) "success-json" A JSON list containing node objects
2867+ that match the given tag name.
2868+ @success-example "success-json" [exkey=get-nodes-by-tag] placeholder
2869+
2870+ @error (http-status-code) "404" 404
2871+ @error (content) "not-found" The requested tag name is not found.
2872+ @error-example "not-found"
2873+ Not Found
2874 """
2875 return self._get_node_type(Node, request, name)
2876
2877 @operation(idempotent=True)
2878 def machines(self, request, name):
2879- """Get the list of machines that have this tag.
2880+ """@description-title List machines by tag
2881+ @description Get a JSON list containing machine objects that match
2882+ the given tag name.
2883+
2884+ @param (url-string) "{name}" [required=true] A tag name.
2885+ @param-example "{name}" virtual
2886
2887- Returns 404 if the tag is not found.
2888+ @success (json) "success-json" A JSON list containing machine objects
2889+ that match the given tag name.
2890+ @success-example "success-json" [exkey=get-machines-by-tag] placeholder
2891+
2892+ @error (http-status-code) "404" 404
2893+ @error (content) "not-found" The requested tag name is not found.
2894+ @error-example "not-found"
2895+ Not Found
2896 """
2897 return self._get_node_type(Machine, request, name)
2898
2899 @operation(idempotent=True)
2900 def devices(self, request, name):
2901- """Get the list of devices that have this tag.
2902+ """@description-title List devices by tag
2903+ @description Get a JSON list containing device objects that match
2904+ the given tag name.
2905+
2906+ @param (url-string) "{name}" [required=true] A tag name.
2907+ @param-example "{name}" virtual
2908+
2909+ @success (json) "success-json" A JSON list containing device objects
2910+ that match the given tag name.
2911+ @success-example "success-json" [exkey=get-devices-by-tag] placeholder
2912
2913- Returns 404 if the tag is not found.
2914+ @error (http-status-code) "404" 404
2915+ @error (content) "not-found" The requested tag name is not found.
2916+ @error-example "not-found"
2917+ Not Found
2918 """
2919 return self._get_node_type(Device, request, name)
2920
2921 @operation(idempotent=True)
2922 def rack_controllers(self, request, name):
2923- """Get the list of rack controllers that have this tag.
2924+ """@description-title List rack controllers by tag
2925+ @description Get a JSON list containing rack-controller objects
2926+ that match the given tag name.
2927+
2928+ @param (url-string) "{name}" [required=true] A tag name.
2929+ @param-example "{name}" virtual
2930+
2931+ @success (json) "success-json" A JSON list containing rack-controller
2932+ objects that match the given tag name.
2933+ @success-example "success-json" [exkey=get-rackc-by-tag] placeholder
2934
2935- Returns 404 if the tag is not found.
2936+ @error (http-status-code) "404" 404
2937+ @error (content) "not-found" The requested tag name is not found.
2938+ @error-example "not-found"
2939+ Not Found
2940 """
2941 return self._get_node_type(RackController, request, name)
2942
2943 @operation(idempotent=True)
2944 def region_controllers(self, request, name):
2945- """Get the list of region controllers that have this tag.
2946+ """@description-title List region controllers by tag
2947+ @description Get a JSON list containing region-controller objects
2948+ that match the given tag name.
2949
2950- Returns 404 if the tag is not found.
2951+ @param (url-string) "{name}" [required=true] A tag name.
2952+ @param-example "{name}" virtual
2953+
2954+ @success (json) "success-json" A JSON list containing region-controller
2955+ objects that match the given tag name.
2956+ @success-example "success-json" [exkey=get-regionc-by-tag] placeholder
2957+
2958+ @error (http-status-code) "404" 404
2959+ @error (content) "not-found" The requested tag name is not found.
2960+ @error-example "not-found"
2961+ Not Found
2962 """
2963 return self._get_node_type(RegionController, request, name)
2964
2965@@ -208,13 +304,23 @@ class TagHandler(OperationsHandler):
2966
2967 @operation(idempotent=False)
2968 def rebuild(self, request, name):
2969- """Manually trigger a rebuild the tag <=> node mapping.
2970-
2971- This is considered a maintenance operation, which should normally not
2972- be necessary. Adding nodes or updating a tag's definition should
2973- automatically trigger the appropriate changes.
2974-
2975- Returns 404 if the tag is not found.
2976+ """@description-title Trigger a tag-node mapping rebuild
2977+ @description Tells MAAS to rebuild the tag-to-node mappings.
2978+ This is a maintenance operation and should not be necessary under
2979+ normal circumstances. Adding nodes or updating a tag definition
2980+ should automatically trigger the mapping rebuild.
2981+
2982+ @param (url-string) "{name}" [required=true] A tag name.
2983+ @param-example "{name}" virtual
2984+
2985+ @success (json) "success-json" A JSON object indicating which tag-to-
2986+ node mapping is being rebuilt.
2987+ @success-example "success-json" [exkey=rebuild-tag] placeholder
2988+
2989+ @error (http-status-code) "404" 404
2990+ @error (content) "not-found" The requested tag name is not found.
2991+ @error-example "not-found"
2992+ Not Found
2993 """
2994 tag = Tag.objects.get_tag_or_404(name=name, user=request.user,
2995 to_edit=True)
2996@@ -223,22 +329,52 @@ class TagHandler(OperationsHandler):
2997
2998 @operation(idempotent=False)
2999 def update_nodes(self, request, name):
3000- """Add or remove nodes being associated with this tag.
3001-
3002- :param add: system_ids of nodes to add to this tag.
3003- :param remove: system_ids of nodes to remove from this tag.
3004- :param definition: (optional) If supplied, the definition will be
3005- validated against the current definition of the tag. If the value
3006- does not match, then the update will be dropped (assuming this was
3007- just a case of a worker being out-of-date)
3008- :param rack_controller: A system ID of a rack controller that did the
3009- processing. This value is optional. If not supplied, the requester
3010- must be a superuser. If supplied, then the requester must be the
3011- rack controller.
3012-
3013- Returns 404 if the tag is not found.
3014- Returns 401 if the user does not have permission to update the nodes.
3015- Returns 409 if 'definition' doesn't match the current definition.
3016+ """@description-title Add or remove nodes by tag
3017+ @description Add or remove nodes associated with the given tag.
3018+ Note that you must supply either the ``add`` or ``remove``
3019+ parameter.
3020+
3021+ @param (url-string) "{name}" [required=true] A tag name.
3022+ @param-example "{name}" virtual
3023+
3024+ @param (string) "add" [required=false] The system_id to tag.
3025+ @param-example "add" ``fptcnd``
3026+
3027+ @param (string) "remove" [required=false] The system_id to untag.
3028+ @param-example "remove" ``xbpf3n``
3029+
3030+ @param (string) "definition" [required=false] If given, the
3031+ definition (XPATH expression) will be validated against the
3032+ current definition of the tag. If the value does not match, MAAS
3033+ assumes the worker is out of date and will drop the update.
3034+ @param-example "definition"
3035+ //node[&#64;id="display"]/'clock units="Hz"' > 1000000000
3036+
3037+ @param (string) "rack_controller" [required=false] The system ID
3038+ of the rack controller that processed the given tag initially.
3039+ If not given, the requester must be a MAAS admin. If given,
3040+ the requester must be the rack controller.
3041+
3042+ @success (json) "success-json" A JSON object representing the
3043+ updated node.
3044+ @success-example "success-json" [exkey=update-nodes-tag] placeholder
3045+
3046+ @error (http-status-code) "403" 403
3047+ @error (content) "no-perms" The user does not have the permissions
3048+ required to update the nodes.
3049+ @error-example "no-perms"
3050+ Must be a superuser or supply a rack_controller.
3051+
3052+ @error (http-status-code) "409" 409
3053+ @error (content) "no-def-match" The supplied definition doesn't match
3054+ the current definition.
3055+ @error-example "no-def-match"
3056+ Definition supplied 'foobar' doesn't match current definition ''
3057+
3058+ @error (http-status-code) "404" 404
3059+ @error (content) "not-found" The requested tag name is not found.
3060+ @error-example "not-found"
3061+ Not Found
3062 """
3063 tag = Tag.objects.get_tag_or_404(name=name, user=request.user)
3064 rack_controller = None
3065@@ -278,26 +414,44 @@ class TagHandler(OperationsHandler):
3066
3067
3068 class TagsHandler(OperationsHandler):
3069- """Manage the collection of all the Tags in this MAAS."""
3070+ """Manage all tags known to MAAS."""
3071 api_doc_section_name = "Tags"
3072 update = delete = None
3073
3074 def create(self, request):
3075- """Create a new Tag.
3076-
3077- :param name: The name of the Tag to be created. This should be a short
3078- name, and will be used in the URL of the tag.
3079- :param comment: A long form description of what the tag is meant for.
3080- It is meant as a human readable description of the tag.
3081- :param definition: An XPATH query that will be evaluated against the
3082- hardware_details stored for all nodes (output of `lshw -xml`).
3083- :param kernel_opts: Can be None. If set, nodes associated with this tag
3084- will add this string to their kernel options when booting. The
3085- value overrides the global 'kernel_opts' setting. If more than one
3086- tag is associated with a node, the one with the lowest alphabetical
3087- name will be picked (eg 01-my-tag will be taken over 99-tag-name).
3088-
3089- Returns 401 if the user is not an admin.
3090+ """@description-title Create a new tag
3091+ @description Create a new tag.
3092+
3093+ @param (string) "name" [required=true] The new tag name. Because
3094+ the name will be used in urls, it should be short.
3095+ @param-example "name" virtual
3096+ @param (string) "comment" [required=false] A description of what the
3097+ the tag will be used for in natural language.
3098+ @param-example "comment" The 'virtual' tag represents virtual
3099+ machines.
3100+ @param (string) "definition" [required=false] An XPATH query that is
3101+ evaluated against the hardware_details stored for all nodes
3102+ (i.e. the output of ``lshw -xml``).
3103+ @param-example "definition"
3104+ //node[&#64;id="display"]/'clock units="Hz"' > 1000000000
3105+ @param (string) "kernel_opts" [required=false] Nodes associated
3106+ with this tag will add this string to their kernel options
3107+ when booting. The value overrides the global ``kernel_opts``
3108+ setting. If more than one tag is associated with a node, the
3109+ one with the lower alphabetical name will be picked. For example,
3110+ ``01-my-tag`` will be chosen instead of ``99-tag-name``.
3111+ @param-example "kernel_opts"
3112+ nouveau.noaccel=1
3113+
3114+ @success (json) "success-json" A JSON object representing the
3115+ new tag.
3116+ @success-example "success-json" [exkey=add-tag] placeholder
3117+
3118+ @error (http-status-code) "403" 403
3119+ @error (content) "no-perms" The user does not have the permissions
3120+ required to create a tag.
3121+ @error-example "no-perms"
3122+ No content
3123 """
3124 if not request.user.is_superuser:
3125 raise PermissionDenied()
3126@@ -308,9 +462,13 @@ class TagsHandler(OperationsHandler):
3127 raise MAASAPIValidationError(form.errors)
3128
3129 def read(self, request):
3130- """List Tags.
3131+ """@description-title List tags
3132+ @description Outputs a JSON object containing an array of all
3133+ currently defined tag objects.
3134
3135- Get a listing of all tags that are currently defined.
3136+ @success (json) "success-json" A JSON object containing an array
3137+ of all currently defined tag objects.
3138+ @success-example "success-json" [exkey=get-tags] placeholder
3139 """
3140 return Tag.objects.all()
3141
3142diff --git a/src/maasserver/api/tests/test_annotations.py b/src/maasserver/api/tests/test_annotations.py
3143index 5a5eea7..de8886d 100644
3144--- a/src/maasserver/api/tests/test_annotations.py
3145+++ b/src/maasserver/api/tests/test_annotations.py
3146@@ -5,6 +5,7 @@
3147
3148 __all__ = []
3149
3150+
3151 from maasserver.api.annotations import APIDocstringParser
3152 from maasserver.testing.api import APITestCase
3153
3154@@ -17,6 +18,18 @@ class TestAPIAnnotations(APITestCase.ForUser):
3155 # Allowed types
3156 allowed_types = APIDocstringParser.allowed_types
3157
3158+ # URI templates for testing the examples database. The parser uses the
3159+ # URI template to name an examples database. So, here, the parser would
3160+ # look for "examples/for-tests.json". We have plural and singular beause
3161+ # the API often references both plural and singular operations. E.g.
3162+ # zone and zones. Both should use the same examples database.
3163+ #
3164+ # Also, we need to pass this to every parse method call so that we don't
3165+ # see spurious API warnings when the parse cannot find an examples database
3166+ # that the sample_api_annotated_docstring is referencing.
3167+ test_uri_singular = "/MAAS/api/2.0/for-test/{foobar}/"
3168+ test_uri_plural = "/MAAS/api/2.0/for-tests/{foobar}/"
3169+
3170 # Use this sample and modify it in various ways
3171 # inline to perform tests. Note that all allowed
3172 # types/tags are (and should be) represented here, and
3173@@ -44,6 +57,9 @@ class TestAPIAnnotations(APITestCase.ForUser):
3174 @success (content) "success_name" success description
3175 @success-example "success_name" success content
3176
3177+ @success (content) "success_with_exdb" success description
3178+ @success-example "success_with_exdb" [exkey=key1] ignored content
3179+
3180 @error (http-status-code) "error_name" error description
3181 @error-example "error_name" error content
3182 """
3183@@ -70,7 +86,7 @@ class TestAPIAnnotations(APITestCase.ForUser):
3184 self.assertTrue(pdict['warnings'].find("API_SYNTAX_ERROR") != -1)
3185
3186 def do_parse(self, api_docstring_parser, docstring):
3187- api_docstring_parser.parse(docstring)
3188+ api_docstring_parser.parse(docstring, uri=self.test_uri_singular)
3189 return api_docstring_parser.get_dict()
3190
3191 def test_all_allowed_tags_are_represented_in_test(self):
3192@@ -92,16 +108,18 @@ class TestAPIAnnotations(APITestCase.ForUser):
3193
3194 docstring = self.sample_api_annotated_docstring
3195 api_docstring_parser = APIDocstringParser()
3196- api_docstring_parser.parse(docstring, "method", "uri", "operation")
3197+ api_docstring_parser.parse(docstring, http_method="mymethod",
3198+ uri=self.test_uri_singular,
3199+ operation="myoperation")
3200 d = api_docstring_parser.get_dict()
3201
3202 params = d['params']
3203 successes = d['successes']
3204 errors = d['errors']
3205
3206- self.assertEqual(d['http_method'], "method")
3207- self.assertEqual(d['uri'], "uri")
3208- self.assertEqual(d['operation'], "operation")
3209+ self.assertEqual(d['http_method'], "mymethod")
3210+ self.assertEqual(d['uri'], self.test_uri_singular)
3211+ self.assertEqual(d['operation'], "myoperation")
3212 self.assertEqual(d['description_title'], "Docstring title")
3213 self.assertEqual(
3214 " ".join(d['description'].split()),
3215@@ -164,7 +182,8 @@ class TestAPIAnnotations(APITestCase.ForUser):
3216 """Replace a good tag with a bad one and get a syntax error."""
3217 docstring = self.sample_api_annotated_docstring
3218 api_docstring_parser = APIDocstringParser()
3219- api_docstring_parser.parse(docstring.replace("@param", "@bad"))
3220+ api_docstring_parser.parse(docstring.replace("@param", "@bad"),
3221+ uri=self.test_uri_singular)
3222 d = api_docstring_parser.get_dict()
3223 self.assert_has_syntax_error(d)
3224
3225@@ -180,21 +199,21 @@ class TestAPIAnnotations(APITestCase.ForUser):
3226 docstring = docstring.replace(
3227 "@param-example \"param_name\"",
3228 "@param-example \"param_name_bad\"")
3229- api_docstring_parser.parse(docstring)
3230+ api_docstring_parser.parse(docstring, uri=self.test_uri_singular)
3231 d = api_docstring_parser.get_dict()
3232 self.assert_has_api_warning(d)
3233
3234 docstring = docstring.replace(
3235 "@error-example \"error_name\"",
3236 "@error-example \"error_name_bad\"")
3237- api_docstring_parser.parse(docstring)
3238+ api_docstring_parser.parse(docstring, uri=self.test_uri_singular)
3239 d = api_docstring_parser.get_dict()
3240 self.assert_has_api_warning(d)
3241
3242 docstring = docstring.replace(
3243 "@success-example \"success_name\"",
3244 "@success-example \"success_name_bad\"")
3245- api_docstring_parser.parse(docstring)
3246+ api_docstring_parser.parse(docstring, uri=self.test_uri_singular)
3247 d = api_docstring_parser.get_dict()
3248 self.assert_has_api_warning(d)
3249
3250@@ -206,7 +225,7 @@ class TestAPIAnnotations(APITestCase.ForUser):
3251 " multiple lines.\n\n "
3252 )
3253 api_docstring_parser = APIDocstringParser()
3254- api_docstring_parser.parse(docstring)
3255+ api_docstring_parser.parse(docstring, uri=self.test_uri_singular)
3256 d = api_docstring_parser.get_dict()
3257
3258 # Note that we only test one description here because the
3259@@ -224,7 +243,7 @@ class TestAPIAnnotations(APITestCase.ForUser):
3260 " }\n\n "
3261 )
3262 api_docstring_parser = APIDocstringParser()
3263- api_docstring_parser.parse(docstring)
3264+ api_docstring_parser.parse(docstring, uri=self.test_uri_singular)
3265 d = api_docstring_parser.get_dict()
3266
3267 # Note that we only test one example here because the
3268@@ -486,3 +505,56 @@ class TestAPIAnnotations(APITestCase.ForUser):
3269
3270 self.assert_has_api_warning(
3271 self.do_parse(api_docstring_parser, ds_md))
3272+
3273+ def test_find_examples_db(self):
3274+ """Ensure parser correctly finds example databases."""
3275+ ds = self.sample_api_annotated_docstring
3276+
3277+ api_docstring_parser = APIDocstringParser()
3278+ api_docstring_parser.parse(ds, uri=self.test_uri_singular)
3279+ d = api_docstring_parser.get_dict()
3280+
3281+ s = d['successes'][1]
3282+
3283+ self.assertEqual(" ".join(s['example'].split()), '{ "name": "value" }')
3284+
3285+ api_docstring_parser.parse(ds, uri=self.test_uri_plural)
3286+ d = api_docstring_parser.get_dict()
3287+
3288+ s = d['successes'][1]
3289+
3290+ self.assertEqual(" ".join(s['example'].split()), '{ "name": "value" }')
3291+
3292+ def test_warn_on_missing_example_db_entry(self):
3293+ """Ensure we see a warning if there is a missing examples db entry."""
3294+ ds_orig = self.sample_api_annotated_docstring
3295+
3296+ ds_bad_exkey = ds_orig.replace(
3297+ '"success_with_exdb" [exkey=key1]',
3298+ '"success_with_exdb" [exkey=badkey]')
3299+
3300+ api_docstring_parser = APIDocstringParser()
3301+ api_docstring_parser.parse(ds_bad_exkey, uri=self.test_uri_singular)
3302+ d = api_docstring_parser.get_dict()
3303+
3304+ self.assert_has_api_warning(d)
3305+
3306+ def test_warn_on_missing_example_db_when_entry_referenced(self):
3307+ """Missing examples db.
3308+
3309+ If an examples db does not exist for some given URI (like when it
3310+ simply hasn't been created yet) and a key from that missing DB is
3311+ referenced by the API, we should see a warning.
3312+
3313+ Note that _not_ having an examples db for a particular operation is a
3314+ normal and acceptable condition (it takes a while to create one). It
3315+ only becomes an error condition when the API tries to reference
3316+ something inside a non-existent examples database.
3317+ """
3318+ ds = self.sample_api_annotated_docstring
3319+
3320+ api_docstring_parser = APIDocstringParser()
3321+ api_docstring_parser.parse(ds, uri="bad_uri")
3322+ d = api_docstring_parser.get_dict()
3323+
3324+ self.assert_has_api_warning(d)
3325diff --git a/src/maasserver/api/tmpl-apidoc.rst b/src/maasserver/api/tmpl-apidoc.rst
3326index 3198bba..a31139a 100644
3327--- a/src/maasserver/api/tmpl-apidoc.rst
3328+++ b/src/maasserver/api/tmpl-apidoc.rst
3329@@ -1,7 +1,7 @@
3330 .. raw:: html
3331
3332 <details>
3333- <summary>``{{ http_method }} {{ uri }}{{if operation != ""}}?op={{ operation }}{{endif}}``</summary>
3334+ <summary>``{{ http_method }} {{ uri }}{{if operation != ""}}?{{ operation }}{{endif}}``</summary>
3335
3336 ######################################################################################################
3337
3338@@ -58,9 +58,14 @@ THERE ARE PROBLEMS WITH THE DOCSTRING:
3339
3340 *{{ p['type'] }}*
3341
3342+{{py:
3343+from textwrap import indent
3344+example = indent(p['example'], ' ')
3345+}}
3346+
3347 ::
3348
3349- {{ p['example'] }}
3350+{{ example }}
3351
3352 {{endif}}
3353
3354@@ -93,5 +98,5 @@ THERE ARE PROBLEMS WITH THE DOCSTRING:
3355
3356 .. raw:: html
3357
3358+ <p>&nbsp;</p>
3359 </details>
3360-

Subscribers

People subscribed via source and target branches