Merge ~adam-collard/maas:websocket-create-deployed-machine into maas:master

Proposed by Adam Collard
Status: Merged
Approved by: Adam Collard
Approved revision: e038d4d1b1cfb3659236e242f96bc48845536fbf
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~adam-collard/maas:websocket-create-deployed-machine
Merge into: maas:master
Diff against target: 518 lines (+170/-128)
9 files modified
src/maasserver/api/machines.py (+13/-19)
src/maasserver/forms/__init__.py (+20/-2)
src/maasserver/forms/ephemeral.py (+2/-30)
src/maasserver/forms/tests/test_ephemeral.py (+2/-58)
src/maasserver/forms/tests/test_machine.py (+23/-2)
src/maasserver/websockets/handlers/machine.py (+18/-10)
src/maasserver/websockets/handlers/tests/test_machine.py (+28/-0)
src/metadataserver/models/scriptset.py (+21/-6)
src/metadataserver/models/tests/test_scriptset.py (+43/-1)
Reviewer Review Type Date Requested Status
MAAS Lander Needs Fixing
Alberto Donato (community) Approve
Review via email: mp+407385@code.launchpad.net

Commit message

Allow creating a machine as deployed via websocket handler

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) wrote :

nice!

just one question inline

review: Needs Information
Revision history for this message
Adam Collard (adam-collard) :
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b websocket-create-deployed-machine lp:~adam-collard/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 1633be5330aca1675c83e97634f75ec918c97f5a

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

UNIT TESTS
-b websocket-create-deployed-machine lp:~adam-collard/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10713/console
COMMIT: 08c2fc6141b4500ebd4c582b27f9218f12989816

review: Needs Fixing
Revision history for this message
Alberto Donato (ack) wrote :

+1!

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

LANDING
-b websocket-create-deployed-machine lp:~adam-collard/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED BUILD
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10714/consoleText

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

UNIT TESTS
-b websocket-create-deployed-machine lp:~adam-collard/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10715/console
COMMIT: 6de8d707da29ec79c569e46de5b01d9cd4e5a7ec

review: Needs Fixing
e038d4d... by Adam Collard

Flaky test - we don't care about the order

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/api/machines.py b/src/maasserver/api/machines.py
index fe60680..5c234f2 100644
--- a/src/maasserver/api/machines.py
+++ b/src/maasserver/api/machines.py
@@ -71,10 +71,7 @@ from maasserver.forms import (
71 MachineForm,71 MachineForm,
72)72)
73from maasserver.forms.clone import CloneForm73from maasserver.forms.clone import CloneForm
74from maasserver.forms.ephemeral import (74from maasserver.forms.ephemeral import CommissionForm
75 CommissionForm,
76 CreateScriptsForDeployedForm,
77)
78from maasserver.forms.filesystem import (75from maasserver.forms.filesystem import (
79 MountNonStorageFilesystemForm,76 MountNonStorageFilesystemForm,
80 UnmountNonStorageFilesystemForm,77 UnmountNonStorageFilesystemForm,
@@ -1968,6 +1965,10 @@ class MachinesHandler(NodesHandler, PowersMixin):
1968 time out. Machines created by administrators will be commissioned1965 time out. Machines created by administrators will be commissioned
1969 unless set to false.1966 unless set to false.
19701967
1968 @param (boolean) "deployed" [required=false,formatting=true] Request
1969 the newly created machine to be created with status set to
1970 DEPLOYED.
1971
1971 @param (int) "enable_ssh" [required=false] Whether to enable SSH for1972 @param (int) "enable_ssh" [required=false] Whether to enable SSH for
1972 the commissioning environment using the user's SSH key(s). '1' == True,1973 the commissioning environment using the user's SSH key(s). '1' == True,
1973 '0' == False.1974 '0' == False.
@@ -2011,21 +2012,14 @@ class MachinesHandler(NodesHandler, PowersMixin):
2011 request.data, "deployed", default=False, validator=StringBool2012 request.data, "deployed", default=False, validator=StringBool
2012 )2013 )
2013 machine = create_machine(request)2014 machine = create_machine(request)
2014 if request.user.is_superuser:2015 if request.user.is_superuser and commission and not deployed:
2015 if deployed:2016 form = CommissionForm(
2016 form = CreateScriptsForDeployedForm(2017 instance=machine, user=request.user, data=request.data
2017 instance=machine,2018 )
2018 data=request.data,2019 # Silently ignore errors to prevent 500 errors. The commissioning
2019 )2020 # callbacks have their own logging. This fixes LP:1600328.
2020 form.save()2021 if form.is_valid():
2021 elif commission:2022 machine = form.save()
2022 form = CommissionForm(
2023 instance=machine, user=request.user, data=request.data
2024 )
2025 # Silently ignore errors to prevent 500 errors. The commissioning
2026 # callbacks have their own logging. This fixes LP:1600328.
2027 if form.is_valid():
2028 machine = form.save()
20292023
2030 return machine2024 return machine
20312025
diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py
index d4d35df..f5feb77 100644
--- a/src/maasserver/forms/__init__.py
+++ b/src/maasserver/forms/__init__.py
@@ -748,6 +748,7 @@ class MachineForm(NodeForm):
748 "architecture", choices748 "architecture", choices
749 )749 )
750 required = requires_macs_and_architecture(self.data) or requires_arch750 required = requires_macs_and_architecture(self.data) or requires_arch
751 self._need_boot_images = required
751 self.fields["architecture"] = forms.ChoiceField(752 self.fields["architecture"] = forms.ChoiceField(
752 choices=choices,753 choices=choices,
753 required=required,754 required=required,
@@ -853,7 +854,7 @@ class MachineForm(NodeForm):
853 is_valid = super().is_valid()854 is_valid = super().is_valid()
854 if not is_valid:855 if not is_valid:
855 return False856 return False
856 if len(list_all_usable_architectures()) == 0:857 if self._need_boot_images and not list_all_usable_architectures():
857 set_form_error(self, "architecture", NO_ARCHITECTURES_AVAILABLE)858 set_form_error(self, "architecture", NO_ARCHITECTURES_AVAILABLE)
858 is_valid = False859 is_valid = False
859 return is_valid860 return is_valid
@@ -1172,6 +1173,23 @@ class AdminMachineForm(MachineForm, AdminNodeForm, WithPowerTypeMixin):
1172 cleaned_data = WithPowerTypeMixin.check_driver(self, cleaned_data)1173 cleaned_data = WithPowerTypeMixin.check_driver(self, cleaned_data)
1173 return cleaned_data1174 return cleaned_data
11741175
1176 def _setup_deployed_machine(self, machine):
1177 """Configure the Machine before it has been saved."""
1178 from metadataserver.models import NodeKey
1179 from metadataserver.models.scriptset import ScriptSet
1180
1181 machine.status = NODE_STATUS.DEPLOYED
1182 # Foreign relations need to have an id to relate to, have to
1183 # save here
1184 machine.save()
1185 script_set = ScriptSet.objects.create_deployed_machine_script_set(
1186 machine
1187 )
1188 machine.current_commissioning_script_set = script_set
1189
1190 # ensure a token is available for the machine
1191 NodeKey.objects.get_token_for_node(machine)
1192
1175 def save(self, *args, **kwargs):1193 def save(self, *args, **kwargs):
1176 """Persist the node into the database."""1194 """Persist the node into the database."""
1177 machine = super().save(commit=False)1195 machine = super().save(commit=False)
@@ -1182,7 +1200,7 @@ class AdminMachineForm(MachineForm, AdminNodeForm, WithPowerTypeMixin):
1182 if pool:1200 if pool:
1183 machine.pool = pool1201 machine.pool = pool
1184 if self.cleaned_data.get("deployed"):1202 if self.cleaned_data.get("deployed"):
1185 machine.status = NODE_STATUS.DEPLOYED1203 self._setup_deployed_machine(machine)
1186 WithPowerTypeMixin.set_values(self, machine)1204 WithPowerTypeMixin.set_values(self, machine)
1187 if kwargs.get("commit", True):1205 if kwargs.get("commit", True):
1188 machine.save(*args, **kwargs)1206 machine.save(*args, **kwargs)
diff --git a/src/maasserver/forms/ephemeral.py b/src/maasserver/forms/ephemeral.py
index 39d28e2..b736eaa 100644
--- a/src/maasserver/forms/ephemeral.py
+++ b/src/maasserver/forms/ephemeral.py
@@ -21,8 +21,8 @@ from django.http import QueryDict
21from maasserver.enum import NODE_STATUS21from maasserver.enum import NODE_STATUS
22from maasserver.node_action import compile_node_actions22from maasserver.node_action import compile_node_actions
23from maasserver.utils.forms import set_form_error23from maasserver.utils.forms import set_form_error
24from metadataserver.enum import RESULT_TYPE, SCRIPT_TYPE24from metadataserver.enum import SCRIPT_TYPE
25from metadataserver.models import NodeKey, Script, ScriptSet25from metadataserver.models import Script
2626
2727
28class TestForm(Form):28class TestForm(Form):
@@ -322,31 +322,3 @@ class CommissionForm(TestForm):
322 ),322 ),
323 )323 )
324 return self.instance324 return self.instance
325
326
327class CreateScriptsForDeployedForm(Form):
328 def __init__(self, instance, **kwargs):
329 super().__init__(**kwargs)
330 self.instance = instance
331
332 def save(self):
333 node = self.instance
334 scripts = list(
335 Script.objects.filter(
336 script_type=SCRIPT_TYPE.COMMISSIONING,
337 default=True,
338 tags__contains=["deploy-info"],
339 )
340 )
341 script_set = ScriptSet.objects.create(
342 node=node,
343 result_type=RESULT_TYPE.COMMISSIONING,
344 requested_scripts=[script.name for script in scripts],
345 )
346 for script in scripts:
347 script_set.add_pending_script(script)
348 node.current_commissioning_script_set = script_set
349 node.save()
350 # ensure a token is available for the machine
351 NodeKey.objects.get_token_for_node(node)
352 return script_set
diff --git a/src/maasserver/forms/tests/test_ephemeral.py b/src/maasserver/forms/tests/test_ephemeral.py
index a5c2e56..fde9528 100644
--- a/src/maasserver/forms/tests/test_ephemeral.py
+++ b/src/maasserver/forms/tests/test_ephemeral.py
@@ -12,16 +12,11 @@ from maasserver.enum import (
12 NODE_TYPE_CHOICES,12 NODE_TYPE_CHOICES,
13 POWER_STATE,13 POWER_STATE,
14)14)
15from maasserver.forms.ephemeral import (15from maasserver.forms.ephemeral import CommissionForm, TestForm
16 CommissionForm,
17 CreateScriptsForDeployedForm,
18 TestForm,
19)
20from maasserver.testing.factory import factory16from maasserver.testing.factory import factory
21from maasserver.testing.testcase import MAASServerTestCase17from maasserver.testing.testcase import MAASServerTestCase
22from maastesting.matchers import MockCalledOnceWith18from maastesting.matchers import MockCalledOnceWith
23from metadataserver.enum import SCRIPT_STATUS, SCRIPT_TYPE19from metadataserver.enum import SCRIPT_TYPE
24from metadataserver.models import NodeKey
2520
2621
27class TestTestForm(MAASServerTestCase):22class TestTestForm(MAASServerTestCase):
@@ -988,54 +983,3 @@ class TestCommissionForm(MAASServerTestCase):
988 script_input={},983 script_input={},
989 ),984 ),
990 )985 )
991
992
993class TestCreateScriptsForDeployedForm(MAASServerTestCase):
994 def test_create_scripts(self):
995 script1 = factory.make_Script(
996 script_type=SCRIPT_TYPE.COMMISSIONING,
997 default=True,
998 tags=["foo", "deploy-info"],
999 )
1000 script2 = factory.make_Script(
1001 script_type=SCRIPT_TYPE.COMMISSIONING,
1002 default=True,
1003 tags=["bar", "deploy-info"],
1004 )
1005 # other scripts that are not matched
1006 factory.make_Script( # not for commissioning
1007 script_type=SCRIPT_TYPE.TESTING,
1008 default=True,
1009 tags=["bar", "deploy-info"],
1010 )
1011 factory.make_Script( # not a builtin script
1012 script_type=SCRIPT_TYPE.COMMISSIONING,
1013 default=False,
1014 tags=["deploy-info"],
1015 )
1016 factory.make_Script( # no deploy-info tag
1017 script_type=SCRIPT_TYPE.COMMISSIONING,
1018 default=True,
1019 tags=["foo"],
1020 )
1021 node = factory.make_Node(status=NODE_STATUS.DEPLOYED)
1022 form = CreateScriptsForDeployedForm(instance=node, data={})
1023 self.assertTrue(form.is_valid())
1024 script_set = form.save()
1025 self.assertEqual(script_set.node, node)
1026 script_results = list(script_set.scriptresult_set.order_by("id"))
1027 self.assertEqual(
1028 [script_result.script for script_result in script_results],
1029 [script1, script2],
1030 )
1031 for script_result in script_results:
1032 self.assertEqual(script_result.status, SCRIPT_STATUS.PENDING)
1033 self.assertIs(node.current_commissioning_script_set, script_set)
1034
1035 def test_create_node_token(self):
1036 node = factory.make_Node(status=NODE_STATUS.DEPLOYED)
1037 self.assertFalse(NodeKey.objects.filter(node=node).exists())
1038 form = CreateScriptsForDeployedForm(instance=node, data={})
1039 self.assertTrue(form.is_valid())
1040 form.save()
1041 self.assertTrue(NodeKey.objects.filter(node=node).exists())
diff --git a/src/maasserver/forms/tests/test_machine.py b/src/maasserver/forms/tests/test_machine.py
index cef7cbe..bf61313 100644
--- a/src/maasserver/forms/tests/test_machine.py
+++ b/src/maasserver/forms/tests/test_machine.py
@@ -24,6 +24,7 @@ from maasserver.testing.osystems import (
24 patch_usable_osystems,24 patch_usable_osystems,
25)25)
26from maasserver.testing.testcase import MAASServerTestCase26from maasserver.testing.testcase import MAASServerTestCase
27from metadataserver.models import NodeKey
27from provisioningserver.rpc.exceptions import (28from provisioningserver.rpc.exceptions import (
28 NoConnectionsAvailable,29 NoConnectionsAvailable,
29 NoSuchOperatingSystem,30 NoSuchOperatingSystem,
@@ -409,11 +410,9 @@ class TestAdminMachineForm(MAASServerTestCase):
409410
410 def test_AdminMachineForm_new_machine_deployed(self):411 def test_AdminMachineForm_new_machine_deployed(self):
411 hostname = factory.make_string()412 hostname = factory.make_string()
412 arch = make_usable_architecture(self)
413 form = AdminMachineForm(413 form = AdminMachineForm(
414 data={414 data={
415 "hostname": hostname,415 "hostname": hostname,
416 "architecture": arch,
417 "deployed": True,416 "deployed": True,
418 },417 },
419 )418 )
@@ -518,3 +517,25 @@ class TestAdminMachineForm(MAASServerTestCase):
518 ]517 ]
519 },518 },
520 )519 )
520
521 def test_AdminMachineForm_creates_scriptset_for_deployed(self):
522 hostname = factory.make_string()
523 form = AdminMachineForm(
524 data={
525 "hostname": hostname,
526 "deployed": True,
527 },
528 )
529 machine = form.save()
530 self.assertIsNotNone(machine.current_commissioning_script_set)
531
532 def test_AdminMachineForm_creates_node_token_for_deployed(self):
533 hostname = factory.make_string()
534 form = AdminMachineForm(
535 data={
536 "hostname": hostname,
537 "deployed": True,
538 },
539 )
540 machine = form.save()
541 self.assertTrue(NodeKey.objects.filter(node=machine).exists())
diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py
index 3236647..4425c78 100644
--- a/src/maasserver/websockets/handlers/machine.py
+++ b/src/maasserver/websockets/handlers/machine.py
@@ -22,6 +22,7 @@ from maasserver.enum import (
22from maasserver.exceptions import NodeActionError, NodeStateViolation22from maasserver.exceptions import NodeActionError, NodeStateViolation
23from maasserver.forms import (23from maasserver.forms import (
24 AddPartitionForm,24 AddPartitionForm,
25 AdminMachineForm,
25 AdminMachineWithMACAddressesForm,26 AdminMachineWithMACAddressesForm,
26 CreateBcacheForm,27 CreateBcacheForm,
27 CreateCacheSetForm,28 CreateCacheSetForm,
@@ -246,6 +247,10 @@ class MachineHandler(NodeHandler):
246 edit_permission = NodePermission.admin247 edit_permission = NodePermission.admin
247 delete_permission = NodePermission.admin248 delete_permission = NodePermission.admin
248249
250 def __init__(self, *args, **kwargs):
251 super().__init__(*args, **kwargs)
252 self._deployed = False
253
249 def get_queryset(self, for_list=False):254 def get_queryset(self, for_list=False):
250 """Return `QuerySet` for devices only viewable by `user`."""255 """Return `QuerySet` for devices only viewable by `user`."""
251 return Machine.objects.get_nodes(256 return Machine.objects.get_nodes(
@@ -391,6 +396,8 @@ class MachineHandler(NodeHandler):
391396
392 def get_form_class(self, action):397 def get_form_class(self, action):
393 """Return the form class used for `action`."""398 """Return the form class used for `action`."""
399 if action == "create" and self._deployed:
400 return AdminMachineForm
394 if action in ("create", "update"):401 if action in ("create", "update"):
395 return AdminMachineWithMACAddressesForm402 return AdminMachineWithMACAddressesForm
396 else:403 else:
@@ -408,6 +415,7 @@ class MachineHandler(NodeHandler):
408 new_params["description"] = params.get("description")415 new_params["description"] = params.get("description")
409 new_params["power_type"] = params.get("power_type")416 new_params["power_type"] = params.get("power_type")
410 new_params["power_parameters"] = params.get("power_parameters")417 new_params["power_parameters"] = params.get("power_parameters")
418 new_params["deployed"] = params.get("deployed")
411 if "zone" in params:419 if "zone" in params:
412 new_params["zone"] = params["zone"]["name"]420 new_params["zone"] = params["zone"]["name"]
413 if params.get("pool"):421 if params.get("pool"):
@@ -427,18 +435,18 @@ class MachineHandler(NodeHandler):
427 return super().preprocess_form(action, new_params)435 return super().preprocess_form(action, new_params)
428436
429 def create(self, params):437 def create(self, params):
430 """Create the object from params."""438 self._deployed = bool(params.get("deployed", False))
431 data = super().create(params)439 data = super().create(params)
432 node_obj = Node.objects.get(system_id=data["system_id"])440 if not self._deployed:
441 machine = Node.objects.get(system_id=data["system_id"])
442 # Start the commissioning process right away, which has the
443 # desired side effect of initializing the node's power state.
444 d = machine.start_commissioning(self.user)
445 # Silently ignore errors to prevent tracebacks. The commissioning
446 # callbacks have their own logging. This fixes LP1600328.
447 d.addErrback(lambda _: None)
433448
434 # Start the commissioning process right away, which has the449 return data
435 # desired side effect of initializing the node's power state.
436 d = node_obj.start_commissioning(self.user)
437 # Silently ignore errors to prevent tracebacks. The commissioning
438 # callbacks have their own logging. This fixes LP1600328.
439 d.addErrback(lambda _: None)
440
441 return self.full_dehydrate(node_obj)
442450
443 def update(self, params):451 def update(self, params):
444 """Update the object from params."""452 """Update the object from params."""
diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
index 391127b..b13f953 100644
--- a/src/maasserver/websockets/handlers/tests/test_machine.py
+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
@@ -114,6 +114,7 @@ from metadataserver.enum import (
114 SCRIPT_STATUS_FAILED,114 SCRIPT_STATUS_FAILED,
115 SCRIPT_TYPE,115 SCRIPT_TYPE,
116)116)
117from metadataserver.models.nodekey import NodeKey
117from metadataserver.models.scriptset import get_status_from_qs118from metadataserver.models.scriptset import get_status_from_qs
118from provisioningserver.refresh.node_info_scripts import (119from provisioningserver.refresh.node_info_scripts import (
119 LIST_MODALIASES_OUTPUT_NAME,120 LIST_MODALIASES_OUTPUT_NAME,
@@ -2416,6 +2417,33 @@ class TestMachineHandler(MAASServerTestCase):
2416 )2417 )
2417 self.assertThat(mock_start_commissioning, MockCalledOnceWith(user))2418 self.assertThat(mock_start_commissioning, MockCalledOnceWith(user))
24182419
2420 def test_create_creates_deployed_node(self):
2421 user = factory.make_admin()
2422 handler = MachineHandler(user, {}, None)
2423 hostname = factory.make_name("hostname")
2424 description = factory.make_name("description")
2425 zone = factory.make_Zone()
2426
2427 mock_start_commissioning = self.patch(
2428 node_model, "start_commissioning"
2429 )
2430
2431 created_node = handler.create(
2432 {
2433 "hostname": hostname,
2434 "description": description,
2435 "zone": {"name": zone.name},
2436 "deployed": True,
2437 }
2438 )
2439 # the commissioning process is not started
2440 mock_start_commissioning.assert_not_called()
2441 self.assertEqual(created_node["status"], "Deployed")
2442 node = Node.objects.get(system_id=created_node["system_id"])
2443 self.assertIsNotNone(node.current_commissioning_script_set)
2444 self.assertTrue(NodeKey.objects.filter(node=node).exists())
2445 self.assertEqual(node.status, NODE_STATUS.DEPLOYED)
2446
2419 def test_update_raise_permissions_error_for_non_admin(self):2447 def test_update_raise_permissions_error_for_non_admin(self):
2420 user = factory.make_User()2448 user = factory.make_User()
2421 node = factory.make_Node()2449 node = factory.make_Node()
diff --git a/src/metadataserver/models/scriptset.py b/src/metadataserver/models/scriptset.py
index e9eed4a..2e8dea6 100644
--- a/src/metadataserver/models/scriptset.py
+++ b/src/metadataserver/models/scriptset.py
@@ -227,6 +227,25 @@ class ScriptSetManager(Manager):
227 self._clean_old(node, RESULT_TYPE.INSTALLATION, script_set)227 self._clean_old(node, RESULT_TYPE.INSTALLATION, script_set)
228 return script_set228 return script_set
229229
230 def create_deployed_machine_script_set(self, machine):
231 """Setup ScriptSet for a brown-field deployment.
232
233 Built-in scripts with deploy-info tag are queued up.
234 """
235 scripts = list(
236 Script.objects.filter(
237 script_type=SCRIPT_TYPE.COMMISSIONING,
238 default=True,
239 tags__contains=["deploy-info"],
240 )
241 )
242 script_set = machine.scriptset_set.create(
243 requested_scripts=[script.name for script in scripts]
244 )
245 for script in scripts:
246 script_set.add_pending_script(script)
247 return script_set
248
230 def _find_scripts(self, script_qs, hw_pairs, modaliases):249 def _find_scripts(self, script_qs, hw_pairs, modaliases):
231 for script in script_qs:250 for script in script_qs:
232 # If a script is not for specific hardware, it is always included251 # If a script is not for specific hardware, it is always included
@@ -444,13 +463,10 @@ class ScriptSet(CleanSave, Model):
444 def add_pending_script(self, script, script_input=None):463 def add_pending_script(self, script, script_input=None):
445 """Create and add a new ScriptResult for the given Script.464 """Create and add a new ScriptResult for the given Script.
446465
447 Creates a new ScriptResult for the given script and assoicates it with466 Creates a new ScriptResult for the given script and associates it with
448 this ScriptSet. Raises a ValidationError if ParametersForm validation467 this ScriptSet. Raises a ValidationError if ParametersForm validation
449 fails.468 fails.
450 """469 """
451 # Avoid circular dependencies.
452 from metadataserver.models import ScriptResult
453
454 if script_input is None:470 if script_input is None:
455 script_input = {}471 script_input = {}
456 form = ParametersForm(472 form = ParametersForm(
@@ -461,8 +477,7 @@ class ScriptSet(CleanSave, Model):
461 if not form.is_valid():477 if not form.is_valid():
462 raise ValidationError(form.errors)478 raise ValidationError(form.errors)
463 for param in form.cleaned_data["input"]:479 for param in form.cleaned_data["input"]:
464 ScriptResult.objects.create(480 self.scriptresult_set.create(
465 script_set=self,
466 status=SCRIPT_STATUS.PENDING,481 status=SCRIPT_STATUS.PENDING,
467 script=script,482 script=script,
468 script_name=script.name,483 script_name=script.name,
diff --git a/src/metadataserver/models/tests/test_scriptset.py b/src/metadataserver/models/tests/test_scriptset.py
index 68a5b9f..ad37878 100644
--- a/src/metadataserver/models/tests/test_scriptset.py
+++ b/src/metadataserver/models/tests/test_scriptset.py
@@ -10,7 +10,7 @@ from unittest.mock import PropertyMock
10from django.core.exceptions import ValidationError10from django.core.exceptions import ValidationError
11from django.db.models import Q11from django.db.models import Q
1212
13from maasserver.enum import NODE_TYPE13from maasserver.enum import NODE_STATUS, NODE_TYPE
14from maasserver.exceptions import NoScriptsFound14from maasserver.exceptions import NoScriptsFound
15from maasserver.models import Config, Event, EventType, Node15from maasserver.models import Config, Event, EventType, Node
16from maasserver.preseed import CURTIN_INSTALL_LOG16from maasserver.preseed import CURTIN_INSTALL_LOG
@@ -1012,6 +1012,48 @@ class TestScriptSetManager(MAASServerTestCase):
1012 ).all(),1012 ).all(),
1013 )1013 )
10141014
1015 def test_create_deployed_machine_script_set_scripts(self):
1016 # Avoid builtins muddying the test
1017 Script.objects.all().delete()
1018 script1 = factory.make_Script(
1019 script_type=SCRIPT_TYPE.COMMISSIONING,
1020 default=True,
1021 tags=["foo", "deploy-info"],
1022 )
1023 script2 = factory.make_Script(
1024 script_type=SCRIPT_TYPE.COMMISSIONING,
1025 default=True,
1026 tags=["bar", "deploy-info"],
1027 )
1028 # other scripts that are not matched
1029 factory.make_Script( # not for commissioning
1030 script_type=SCRIPT_TYPE.TESTING,
1031 default=True,
1032 tags=["bar", "deploy-info"],
1033 )
1034 factory.make_Script( # not a builtin script
1035 script_type=SCRIPT_TYPE.COMMISSIONING,
1036 default=False,
1037 tags=["deploy-info"],
1038 )
1039 factory.make_Script( # no deploy-info tag
1040 script_type=SCRIPT_TYPE.COMMISSIONING,
1041 default=True,
1042 tags=["foo"],
1043 )
1044 node = factory.make_Node(status=NODE_STATUS.DEPLOYED)
1045 script_set = ScriptSet.objects.create_deployed_machine_script_set(node)
1046 self.assertItemsEqual(
1047 [
1048 (script_result.script, script_result.status)
1049 for script_result in script_set.scriptresult_set.all()
1050 ],
1051 [
1052 (script1, SCRIPT_STATUS.PENDING),
1053 (script2, SCRIPT_STATUS.PENDING),
1054 ],
1055 )
1056
10151057
1016class TestScriptSet(MAASServerTestCase):1058class TestScriptSet(MAASServerTestCase):
1017 """Test the ScriptSet model."""1059 """Test the ScriptSet model."""

Subscribers

People subscribed via source and target branches