Merge ~ltrager/maas:lp1807991_2.5 into maas:2.5

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: 03561be1f8a6f7285b0b876362b7ea4eb31b99a0
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:lp1807991_2.5
Merge into: maas:2.5
Diff against target: 455 lines (+118/-26)
12 files modified
src/maasserver/api/machines.py (+19/-6)
src/maasserver/api/tests/test_enlistment.py (+22/-3)
src/maasserver/forms/__init__.py (+23/-0)
src/maasserver/forms/settings.py (+11/-0)
src/maasserver/forms/tests/test_machine.py (+2/-0)
src/maasserver/forms/tests/test_machinewithmacaddresses.py (+17/-1)
src/maasserver/models/config.py (+2/-0)
src/metadataserver/api.py (+6/-6)
src/metadataserver/tests/test_api.py (+0/-4)
src/metadataserver/user_data/templates/enlistment.template (+5/-1)
src/metadataserver/user_data/templates/snippets/maas_enlist.sh (+9/-4)
src/metadataserver/user_data/tests/test_generate_user_data.py (+2/-1)
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
Review via email: mp+361631@code.launchpad.net

Commit message

Backport 51b971 LP: #1807991 - Directly go into commissioning during enlistment.

The anonymous API now accepts the 'commission' flag. When set to true
newly created machines will be created in COMMISSIONING and a ScriptSet
with all builtin Scripts will be set as the current_commissioning_script_set.

Commissioning during enlistment can now be disabled using the
'enlist_commissioning' configuration option.

To post a comment you must log in.
Revision history for this message
Lee Trager (ltrager) wrote :
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/machines.py b/src/maasserver/api/machines.py
2index 0ac51b1..0af350b 100644
3--- a/src/maasserver/api/machines.py
4+++ b/src/maasserver/api/machines.py
5@@ -1611,6 +1611,12 @@ class AnonMachinesHandler(AnonNodesHandler):
6 power_type. `Power types`_ section for a list of the available power
7 parameters for each power type.
8
9+ @param (boolean) "commission" [required=false,formatting=true] Request
10+ the newly created machine to be created with status set to
11+ COMMISSIONING. Machines will wait for COMMISSIONING results and not
12+ time out. After commissioning is complete machines will still have to
13+ be accepted by an administrator.
14+
15 @success (http-status-code) "200" 200
16 @success (json) "success-json" A JSON object containing the machine
17 information.
18@@ -1621,6 +1627,8 @@ class AnonMachinesHandler(AnonNodesHandler):
19 power_type = request.data.get('power_type')
20 power_parameters = request.data.get('power_parameters')
21 mac_addresses = request.data.getlist('mac_addresses')
22+ commission = get_optional_param(
23+ request.data, 'commission', default=False, validator=StringBool)
24 machine = None
25
26 # BMC enlistment - Check if there is a pre-existing machine within MAAS
27@@ -1667,12 +1675,12 @@ class AnonMachinesHandler(AnonNodesHandler):
28 if machine is None:
29 machine = create_machine(request, requires_arch=True)
30
31- if machine.status == NODE_STATUS.NEW:
32- # Make sure an enlisting NodeMetadata object exists if the machine
33- # is NEW. When commissioning finishes this is how MAAS knows to
34- # set the status to NEW instead of READY.
35- NodeMetadata.objects.update_or_create(
36- node=machine, key='enlisting', defaults={'value': 'True'})
37+ if commission:
38+ # Make sure an enlisting NodeMetadata object exists if the
39+ # machine is NEW. When commissioning finishes this is how
40+ # MAAS knows to set the status to NEW instead of READY.
41+ NodeMetadata.objects.update_or_create(
42+ node=machine, key='enlisting', defaults={'value': 'True'})
43
44 return machine
45
46@@ -1743,6 +1751,11 @@ class MachinesHandler(NodesHandler, PowersMixin):
47 power_type. `Power types`_ section for a list of the available power
48 parameters for each power type.
49
50+ @param (boolean) "commission" [required=false,formatting=true] Request
51+ the newly created machine to be created with status set to
52+ COMMISSIONING. Machines will wait for COMMISSIONING results and not
53+ time out.
54+
55 @success (http-status-code) "200" 200
56 @success (json) "success-json" A JSON object containing the machine
57 information.
58diff --git a/src/maasserver/api/tests/test_enlistment.py b/src/maasserver/api/tests/test_enlistment.py
59index 30a3413..8111f6b 100644
60--- a/src/maasserver/api/tests/test_enlistment.py
61+++ b/src/maasserver/api/tests/test_enlistment.py
62@@ -452,8 +452,6 @@ class AnonymousEnlistmentAPITest(APITestCase.ForAnonymous):
63 self.assertEqual(architecture, machine.architecture)
64 self.assertDictContainsSubset(
65 machine.bmc.power_parameters, power_parameters)
66- node_metadata = NodeMetadata.objects.get(node=machine, key='enlisting')
67- self.assertEqual(node_metadata.value, 'True')
68 self.assertThat(mock_create_machine, MockNotCalled())
69 self.assertEqual(
70 machine.system_id, json_load_bytes(response.content)['system_id'])
71@@ -501,12 +499,33 @@ class AnonymousEnlistmentAPITest(APITestCase.ForAnonymous):
72 })
73 self.assertEqual(http.client.OK, response.status_code)
74 node_metadata = NodeMetadata.objects.get(key='enlisting')
75- self.assertEqual(node_metadata.value, 'True')
76+ self.assertIsNone(node_metadata)
77 [machine] = Machine.objects.filter(hostname=hostname)
78 self.assertEqual(architecture, machine.architecture)
79 self.assertEqual(
80 machine.system_id, json_load_bytes(response.content)['system_id'])
81
82+ def test_POST_create_creates_machine_commission(self):
83+ hostname = factory.make_name("hostname")
84+ architecture = make_usable_architecture(self)
85+ response = self.client.post(
86+ reverse('machines_handler'),
87+ {
88+ 'hostname': hostname,
89+ 'architecture': architecture,
90+ 'power_type': 'manual',
91+ 'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
92+ 'commission': True,
93+ })
94+ self.assertEqual(http.client.OK, response.status_code)
95+ node_metadata = NodeMetadata.objects.get(key='enlisting')
96+ self.assertEqual('True', node_metadata.value)
97+ [machine] = Machine.objects.filter(hostname=hostname)
98+ self.assertEqual(architecture, machine.architecture)
99+ self.assertEqual(
100+ machine.system_id, json_load_bytes(response.content)['system_id'])
101+ self.assertEqual(NODE_STATUS.COMMISSIONING, machine.status)
102+
103 def test_POST_create_requires_architecture(self):
104 hostname = factory.make_name("hostname")
105 response = self.client.post(
106diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py
107index 582b2af..6c3ab95 100644
108--- a/src/maasserver/forms/__init__.py
109+++ b/src/maasserver/forms/__init__.py
110@@ -113,6 +113,7 @@ from maasserver.enum import (
111 FILESYSTEM_GROUP_RAID_TYPE_CHOICES,
112 FILESYSTEM_TYPE,
113 INTERFACE_TYPE,
114+ NODE_STATUS,
115 NODE_TYPE,
116 )
117 from maasserver.exceptions import NodeActionError
118@@ -893,6 +894,27 @@ class MachineForm(NodeForm):
119 self.is_bound = True
120 self.data['install_kvm'] = install_kvm
121
122+ def save(self, *args, **kwargs):
123+ # Prevent circular imports
124+ from metadataserver.models import ScriptSet
125+ # LP:1807991 - If requested when creating a new Machine, set the status
126+ # to COMMISSIONING when the object is created.
127+ commission = not self.instance.id and self.cleaned_data['commission']
128+ if commission:
129+ self.instance.status = NODE_STATUS.COMMISSIONING
130+ machine = super(MachineForm, self).save(*args, **kwargs)
131+ # For a ScriptSet to be created it must be associated with a Node
132+ # object in the database.
133+ if commission:
134+ script_set = ScriptSet.objects.create_commissioning_script_set(
135+ machine, ['none'])
136+ machine.current_commissioning_script_set = script_set
137+ machine.save(update_fields=['current_commissioning_script_set'])
138+
139+ return machine
140+
141+ commission = forms.BooleanField(required=False, widget=forms.HiddenInput())
142+
143 class Meta:
144 model = Machine
145
146@@ -905,6 +927,7 @@ class MachineForm(NodeForm):
147 'hwe_kernel',
148 'install_rackd',
149 'install_kvm',
150+ 'commission'
151 )
152
153
154diff --git a/src/maasserver/forms/settings.py b/src/maasserver/forms/settings.py
155index d318759..e6bae82 100644
156--- a/src/maasserver/forms/settings.py
157+++ b/src/maasserver/forms/settings.py
158@@ -766,6 +766,17 @@ CONFIG_ITEMS = {
159 "in minutes.")
160 }
161 },
162+ 'enlist_commissioning': {
163+ 'default': True,
164+ 'form': forms.BooleanField,
165+ 'form_kwargs': {
166+ 'label': 'Whether to run commissioning during enlistment.',
167+ 'required': False,
168+ 'help_text': (
169+ 'Enables running all built-in commissioning scripts during '
170+ 'enlistment.'),
171+ }
172+ },
173 }
174
175
176diff --git a/src/maasserver/forms/tests/test_machine.py b/src/maasserver/forms/tests/test_machine.py
177index c8ed726..b4564d6 100644
178--- a/src/maasserver/forms/tests/test_machine.py
179+++ b/src/maasserver/forms/tests/test_machine.py
180@@ -51,6 +51,7 @@ class TestMachineForm(MAASServerTestCase):
181 'hwe_kernel',
182 'install_rackd',
183 'install_kvm',
184+ 'commission',
185 ], list(form.fields))
186
187 def test_accepts_usable_architecture(self):
188@@ -374,6 +375,7 @@ class TestAdminMachineForm(MAASServerTestCase):
189 'power_parameters',
190 'power_type',
191 'pool',
192+ 'commission',
193 ],
194 list(form.fields))
195
196diff --git a/src/maasserver/forms/tests/test_machinewithmacaddresses.py b/src/maasserver/forms/tests/test_machinewithmacaddresses.py
197index fefaaf9..8bc5f98 100644
198--- a/src/maasserver/forms/tests/test_machinewithmacaddresses.py
199+++ b/src/maasserver/forms/tests/test_machinewithmacaddresses.py
200@@ -6,7 +6,10 @@
201 __all__ = []
202
203 from django.http import QueryDict
204-from maasserver.enum import INTERFACE_TYPE
205+from maasserver.enum import (
206+ INTERFACE_TYPE,
207+ NODE_STATUS,
208+)
209 from maasserver.forms import MachineWithMACAddressesForm
210 from maasserver.testing.architecture import (
211 make_usable_architecture,
212@@ -160,15 +163,18 @@ class MachineWithMACAddressesFormTest(MAASServerTestCase):
213 macs = ['aa:bb:cc:dd:ee:ff', '9a:bb:c3:33:e5:7f']
214 form = MachineWithMACAddressesForm(
215 data=self.make_params(mac_addresses=macs))
216+ self.assertTrue(form.is_valid())
217 node = form.save()
218
219 self.assertIsNotNone(node.id) # The node is persisted.
220+ self.assertEquals(NODE_STATUS.NEW, node.status)
221 self.assertItemsEqual(
222 macs,
223 [nic.mac_address for nic in node.interface_set.all()])
224
225 def test_form_without_hostname_generates_hostname(self):
226 form = MachineWithMACAddressesForm(data=self.make_params(hostname=''))
227+ self.assertTrue(form.is_valid())
228 node = form.save()
229 self.assertTrue(len(node.hostname) > 0)
230
231@@ -176,6 +182,7 @@ class MachineWithMACAddressesFormTest(MAASServerTestCase):
232 ip_based_hostname = '192-168-12-10.maas'
233 form = MachineWithMACAddressesForm(
234 data=self.make_params(hostname=ip_based_hostname))
235+ self.assertTrue(form.is_valid())
236 node = form.save()
237 self.assertNotEqual('192-168-12-10', node.hostname)
238
239@@ -183,5 +190,14 @@ class MachineWithMACAddressesFormTest(MAASServerTestCase):
240 ip_prefixed_hostname = '192-168-12-10-extra.maas'
241 form = MachineWithMACAddressesForm(
242 data=self.make_params(hostname=ip_prefixed_hostname))
243+ self.assertTrue(form.is_valid())
244 node = form.save()
245 self.assertEqual('192-168-12-10-extra', node.hostname)
246+
247+ def test_form_with_commissioning(self):
248+ form = MachineWithMACAddressesForm(data={
249+ 'commission': True, **self.make_params()})
250+ self.assertTrue(form.is_valid())
251+ machine = form.save()
252+ self.assertEquals(NODE_STATUS.COMMISSIONING, machine.status)
253+ self.assertIsNotNone(machine.current_commissioning_script_set)
254diff --git a/src/maasserver/models/config.py b/src/maasserver/models/config.py
255index a95246b..0c76e3c 100644
256--- a/src/maasserver/models/config.py
257+++ b/src/maasserver/models/config.py
258@@ -130,6 +130,8 @@ def get_default_config():
259 'prometheus_enabled': False,
260 'prometheus_push_gateway': None,
261 'prometheus_push_interval': 60,
262+ # Enlistment options
263+ 'enlist_commissioning': True,
264 }
265
266
267diff --git a/src/metadataserver/api.py b/src/metadataserver/api.py
268index e76a651..3470a29 100644
269--- a/src/metadataserver/api.py
270+++ b/src/metadataserver/api.py
271@@ -509,9 +509,9 @@ class VersionIndexHandler(MetadataViewHandler):
272 script_result.save(update_fields=['status'])
273
274 def _process_new(self, node, request, status):
275- # MAAS only cares if a NEW node is trying to commission itself. Ignore
276- # other signals.
277 if status != SIGNAL_STATUS.COMMISSIONING:
278+ # MAAS only cares if a NEW node is trying to commission itself.
279+ # Ignore other signals.
280 return None
281
282 # Check if the node currently has a pending or running commissioning
283@@ -525,9 +525,6 @@ class VersionIndexHandler(MetadataViewHandler):
284 node, ['none'])
285 node.current_commissioning_script_set = script_set
286
287- node.min_hwe_kernel = Config.objects.get_config(
288- 'default_min_hwe_kernel')
289-
290 return NODE_STATUS.COMMISSIONING
291
292 def _process_testing(self, node, request, status):
293@@ -1155,7 +1152,10 @@ class EnlistUserDataHandler(OperationsHandler):
294 return HttpResponse(
295 generate_user_data_for_status(
296 None, NODE_STATUS.NEW, rack_controller=rack_controller,
297- request=request),
298+ request=request, extra_content={
299+ 'enlist_commissioning': Config.objects.get_config(
300+ 'enlist_commissioning'),
301+ }),
302 content_type="text/plain")
303
304
305diff --git a/src/metadataserver/tests/test_api.py b/src/metadataserver/tests/test_api.py
306index 2e7b6cb..68d8b2f 100644
307--- a/src/metadataserver/tests/test_api.py
308+++ b/src/metadataserver/tests/test_api.py
309@@ -47,7 +47,6 @@ from maasserver.exceptions import (
310 Unauthorized,
311 )
312 from maasserver.models import (
313- Config,
314 NodeMetadata,
315 Event,
316 SSHKey,
317@@ -2546,8 +2545,6 @@ class TestNewAPI(MAASServerTestCase):
318 factory.make_Script(script_type=SCRIPT_TYPE.COMMISSIONING)
319 factory.make_Script(
320 script_type=SCRIPT_TYPE.TESTING, tags=['commissioning'])
321- min_hwe_kernel = factory.make_name('hwe_kernel')
322- Config.objects.set_config('default_min_hwe_kernel', min_hwe_kernel)
323
324 client = make_node_client(node)
325 response = call_signal(client, status=SIGNAL_STATUS.COMMISSIONING)
326@@ -2560,7 +2557,6 @@ class TestNewAPI(MAASServerTestCase):
327 [script.name for script in node.current_commissioning_script_set])
328 self.assertIsNone(node.current_testing_script_set)
329 self.assertEqual(NODE_STATUS.COMMISSIONING, node.status)
330- self.assertEqual(min_hwe_kernel, node.min_hwe_kernel)
331
332 def test_signal_commissioning_only_creates_scriptsets_when_needed(self):
333 # This happens when commissioning is started by the user with correct
334diff --git a/src/metadataserver/user_data/templates/enlistment.template b/src/metadataserver/user_data/templates/enlistment.template
335index f389108..726be19 100644
336--- a/src/metadataserver/user_data/templates/enlistment.template
337+++ b/src/metadataserver/user_data/templates/enlistment.template
338@@ -48,7 +48,7 @@ main() {
339 # already exists and is not in a NEW state this will fail.
340 output=$(maas-enlist --quite --serverurl "{{server_url}}" \
341 ${power_params:+--power-params "${power_params}" \
342- --power-type "${power_type}"})
343+ --power-type "${power_type}"}{{if enlist_commissioning}} --commission{{endif}})
344 ret=$?
345 echo $output | jq .
346 if [ $ret -ne 0 ]; then
347@@ -57,6 +57,7 @@ main() {
348 exit 1
349 fi
350
351+ {{if enlist_commissioning}}
352 # Reload cloud-init preseed using the system_id to get OAUTH credentials.
353 system_id=$(echo $output | jq -r .system_id)
354 mv ${CRED_CFG} ${CRED_CFG}.orig
355@@ -65,6 +66,7 @@ main() {
356 signal COMMISSIONING "Enlistment complete, starting commissioning"
357
358 maas-run-remote-scripts "--config=${CRED_CFG}" "${TEMP_D}"
359+ {{endif}}
360 }
361
362 ### begin writing files ###
363@@ -85,6 +87,7 @@ add_bin "maas-wedge-autodetect" <<"END_MAAS_WEDGE_AUTODETECT"
364 {{maas_wedge_autodetect_sh}}
365 END_MAAS_WEDGE_AUTODETECT
366
367+{{if enlist_commissioning}}
368 add_bin "maas_api_helper.py" <<"END_MAAS_API_HELPER"
369 {{maas_api_helper_py}}
370 END_MAAS_API_HELPER
371@@ -96,6 +99,7 @@ END_MAAS_SIGNAL
372 add_bin "maas-run-remote-scripts" <<"END_MAAS_RUN_REMOTE_SCRIPTS"
373 {{maas_run_remote_scripts_py}}
374 END_MAAS_RUN_REMOTE_SCRIPTS
375+{{endif}}
376
377 add_bin "maas-enlist" <<"END_MAAS_ENLIST"
378 {{maas_enlist_sh}}
379diff --git a/src/metadataserver/user_data/templates/snippets/maas_enlist.sh b/src/metadataserver/user_data/templates/snippets/maas_enlist.sh
380index 394b0bd..895c0d8 100644
381--- a/src/metadataserver/user_data/templates/snippets/maas_enlist.sh
382+++ b/src/metadataserver/user_data/templates/snippets/maas_enlist.sh
383@@ -2,7 +2,7 @@
384 #
385 # maas-enlist: MAAS Enlistment Tool
386 #
387-# Copyright (C) 2014-2016 Canonical Ltd.
388+# Copyright (C) 2014-2018 Canonical Ltd.
389 #
390 # Authors: Andres Rodriguez <andres.rodriguez@canonical.com>
391 #
392@@ -107,6 +107,7 @@ enlist_node() {
393 hostname="${5}"
394 power_type="${6}"
395 power_params="${7}"
396+ commission="${8}"
397
398 local macparms=""
399 macparms=$(get_mac_address_curl_parms "$mac")
400@@ -119,6 +120,7 @@ enlist_node() {
401 --data-urlencode "subarchitecture=${subarch}" \
402 --data-urlencode "power_type=${power_type}" \
403 --data-urlencode "power_parameters=${power_params}" \
404+ --data-urlencode "commission=${commission}" \
405 ${macparms} \
406 "${serverurl}"
407
408@@ -171,6 +173,8 @@ Usage: ${0##*/} [ options ]
409 -e | --exists checks if the machine already exists in MAAS
410 -w | --in-action checks if the machine already exists in MAAS in a 'in-progress'
411 action like 'deploying' or 'commissioning'.
412+ -c | --commission tell MAAS when creating a new machine to set the status to
413+ 'commissioning'.
414 --subarch subarchitecture of the node to register
415
416 Example:
417@@ -181,8 +185,8 @@ EOF
418
419 bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || Error "$@"; exit 1; }
420
421-short_opts="hs:n:i:a:t:p:ewq"
422-long_opts="help,serverurl:,hostname:,interface:,arch:,subarch:,power-type:,power-params:,exists,in-action,quite"
423+short_opts="hs:n:i:a:t:p:ewqc"
424+long_opts="help,serverurl:,hostname:,interface:,arch:,subarch:,power-type:,power-params:,exists,in-action,quite,commission"
425 getopt_out=$(getopt --name "${0##*/}" \
426 --options "${short_opts}" --long "${long_opts}" -- "$@") &&
427 eval set -- "${getopt_out}" ||
428@@ -202,6 +206,7 @@ while [ $# -ne 0 ]; do
429 -e|--exists) check_exists=true;;
430 -w|--in-action) check_action_in_progress=true;;
431 -q|--quite) quite=true;;
432+ -c|--commission) commission=true;;
433 --) shift; break;;
434 esac
435 shift;
436@@ -268,5 +273,5 @@ elif [ "$check_action_in_progress" = true ]; then
437 check_node "$protocol://$servername/$api_url" "${mac_addrs}" "is_action_in_progress"
438 exit $?
439 else
440- enlist_node "$protocol://$servername/$api_url" "${mac_addrs}" "$arch" "$subarch" "$hostname" "$power_type" "$power_parameters"
441+ enlist_node "$protocol://$servername/$api_url" "${mac_addrs}" "$arch" "$subarch" "$hostname" "$power_type" "$power_parameters" "$commission"
442 fi
443diff --git a/src/metadataserver/user_data/tests/test_generate_user_data.py b/src/metadataserver/user_data/tests/test_generate_user_data.py
444index 3632799..4b51a79 100644
445--- a/src/metadataserver/user_data/tests/test_generate_user_data.py
446+++ b/src/metadataserver/user_data/tests/test_generate_user_data.py
447@@ -26,7 +26,8 @@ class TestGenerateUserData(MAASServerTestCase):
448 # both definitions and use of various commands in python.
449 rack = factory.make_RackController()
450 user_data = generate_user_data_for_status(
451- None, NODE_STATUS.NEW, rack_controller=rack)
452+ None, NODE_STATUS.NEW, rack_controller=rack,
453+ extra_content={'enlist_commissioning': True})
454 parsed_data = email.message_from_string(user_data.decode("utf-8"))
455 self.assertTrue(parsed_data.is_multipart())
456

Subscribers

People subscribed via source and target branches