← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~julian-edwards/maas/commission-ui into lp:maas

 

Julian Edwards has proposed merging lp:~julian-edwards/maas/commission-ui into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~julian-edwards/maas/commission-ui/+merge/100739

Add a "commissioning" button to the node page.  Its action is to set the status to COMMISSIONING, sets a special commissioning profile in cobbler, and starts the node up.

The profile is set on every start_node now, it makes more sense than doing it add node addition time; that can be removed later if desired.

I had a pre-imp call with jtv about this.  (He is doing the node side of the commissioning stuff)
-- 
https://code.launchpad.net/~julian-edwards/maas/commission-ui/+merge/100739
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~julian-edwards/maas/commission-ui into lp:maas.
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py	2012-04-04 06:51:17 +0000
+++ src/maasserver/forms.py	2012-04-04 07:33:26 +0000
@@ -224,6 +224,11 @@
             'permission': NODE_PERMISSION.ADMIN,
             'execute': lambda node, user: Node.accept_enlistment(node),
         },
+        {
+            'display': "Commission node",
+            'permission': NODE_PERMISSION.ADMIN,
+            'execute': lambda node, user: Node.start_commissioning(node, user),
+        },
     ],
 }
 

=== modified file 'src/maasserver/models.py'
--- src/maasserver/models.py	2012-04-04 07:10:49 +0000
+++ src/maasserver/models.py	2012-04-04 07:33:26 +0000
@@ -425,12 +425,25 @@
         :return: Those Nodes for which power-on was actually requested.
         :rtype: list
         """
+        # TODO: File structure needs sorting out to avoid this circular
+        # import dance.
         from metadataserver.models import NodeUserData
+<<<<<<< TREE
         nodes = self.get_nodes(by_user, NODE_PERMISSION.EDIT, ids=ids)
+=======
+        from maasserver.provisioning import select_profile_for_node
+        nodes = self.get_editable_nodes(by_user, ids=ids)
+>>>>>>> MERGE-SOURCE
         if user_data is not None:
             for node in nodes:
                 NodeUserData.objects.set_user_data(node, user_data)
-        get_papi().start_nodes([node.system_id for node in nodes])
+        profiles = {}
+        for node in nodes:
+            profiles[node.system_id] = {
+                'profile': select_profile_for_node(node)}
+        papi = get_papi()
+        papi.modify_nodes(profiles)
+        papi.start_nodes([node.system_id for node in nodes])
         return nodes
 
 
@@ -610,6 +623,15 @@
         self.save()
         return self
 
+    def start_commissioning(self, user):
+        """Install OS and self-test a new node."""
+        self.status = NODE_STATUS.COMMISSIONING
+        self.owner = user
+        self.save()
+        # The commissioning profile is handled in start_nodes.
+        Node.objects.start_nodes(
+            [self.system_id], user, user_data=None)
+
     def delete(self):
         # Delete the related mac addresses first.
         self.macaddress_set.all().delete()

=== modified file 'src/maasserver/provisioning.py'
--- src/maasserver/provisioning.py	2012-03-29 16:02:51 +0000
+++ src/maasserver/provisioning.py	2012-04-04 07:33:26 +0000
@@ -31,6 +31,7 @@
     Config,
     MACAddress,
     Node,
+    NODE_STATUS,
     )
 from provisioningserver.enum import PSERV_FAULT
 
@@ -228,18 +229,21 @@
     return conversions.get(architecture, architecture)
 
 
-def select_profile_for_node(node, papi):
+def select_profile_for_node(node):
     """Select which profile a node should be configured for."""
     assert node.architecture, "Node's architecture is not known."
     cobbler_arch = name_arch_in_cobbler_style(node.architecture)
-    return "maas-%s-%s" % ("precise", cobbler_arch)
+    profile = "maas-%s-%s" % ("precise", cobbler_arch)
+    if node.status == NODE_STATUS.COMMISSIONING:
+        profile += "-commissioning"
+    return profile
 
 
 @receiver(post_save, sender=Node)
 def provision_post_save_Node(sender, instance, created, **kwargs):
     """Create or update nodes in the provisioning server."""
     papi = get_provisioning_api_proxy()
-    profile = select_profile_for_node(instance, papi)
+    profile = select_profile_for_node(instance)
     power_type = instance.get_effective_power_type()
     metadata = compose_metadata(instance)
     papi.add_node(

=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py	2012-04-04 06:51:17 +0000
+++ src/maasserver/tests/test_forms.py	2012-04-04 07:33:26 +0000
@@ -248,19 +248,35 @@
 
 class TestNodeActionForm(TestCase):
 
+<<<<<<< TREE
     def test_available_action_methods_for_declared_node_admin(self):
         # An admin has access to the "Accept Enlisted node" action for a
+=======
+    def test_available_transition_methods_for_declared_node_admin(self):
+        # Check which transitions are available for an admin on a
+>>>>>>> MERGE-SOURCE
         # 'Declared' node.
         admin = factory.make_admin()
         node = factory.make_node(status=NODE_STATUS.DECLARED)
         form = get_action_form(admin)(node)
         actions = form.available_action_methods(node, admin)
         self.assertEqual(
+<<<<<<< TREE
             ["Accept Enlisted node"],
             [action['display'] for action in actions])
+=======
+            ["Accept Enlisted node", "Commission node"],
+            [transition['display'] for transition in transitions])
+        # All permissions should be ADMIN.
+>>>>>>> MERGE-SOURCE
         self.assertEqual(
+<<<<<<< TREE
             [NODE_PERMISSION.ADMIN],
             [action['permission'] for action in actions])
+=======
+            [NODE_PERMISSION.ADMIN] * len(transitions),
+            [transition['permission'] for transition in transitions])
+>>>>>>> MERGE-SOURCE
 
     def test_available_action_methods_for_declared_node_simple_user(self):
         # A simple user sees no actions for a 'Declared' node.
@@ -291,8 +307,16 @@
 
         self.assertItemsEqual(
             {"Accept Enlisted node": (
+<<<<<<< TREE
                 'accept_enlistment', NODE_PERMISSION.ADMIN)},
             form.action_dict)
+=======
+                'accept_enlistment', NODE_PERMISSION.ADMIN),
+             "Commission node": (
+                'start_commissioning', NODE_PERMISSION.ADMIN),
+            },
+            form.transition_dict)
+>>>>>>> MERGE-SOURCE
 
     def test_get_action_form_for_user(self):
         user = factory.make_user()

=== modified file 'src/maasserver/tests/test_models.py'
--- src/maasserver/tests/test_models.py	2012-04-03 13:55:42 +0000
+++ src/maasserver/tests/test_models.py	2012-04-04 07:33:26 +0000
@@ -259,6 +259,19 @@
             {status: status for status in unacceptable_states},
             {status: node.status for status, node in nodes.items()})
 
+    def test_start_commissioning_changes_status_and_starts_node(self):
+        user = factory.make_user()
+        node = factory.make_node(status=NODE_STATUS.DECLARED)
+        node.start_commissioning(user)
+
+        expected_attrs = {
+            'status': NODE_STATUS.COMMISSIONING,
+            'owner': user,
+        }
+        self.assertAttributes(node, expected_attrs)
+        power_status = get_provisioning_api_proxy().power_status
+        self.assertEqual('start', power_status[node.system_id])
+
     def test_full_clean_checks_status_transition_and_raises_if_invalid(self):
         # RETIRED -> ALLOCATED is an invalid transition.
         node = factory.make_node(
@@ -535,6 +548,30 @@
         power_status = get_provisioning_api_proxy().power_status
         self.assertEqual('start', power_status[node.system_id])
 
+    def test_start_nodes_sets_commissioning_profile(self):
+        # Starting up a node should always set a profile. Here we test
+        # that a commissioning profile was set for nodes in the
+        # commissioning status.
+        user = factory.make_user()
+        node = factory.make_node(
+            set_hostname=True, status=NODE_STATUS.COMMISSIONING, owner=user)
+        output = Node.objects.start_nodes([node.system_id], user)
+
+        self.assertItemsEqual([node], output)
+        profile = get_provisioning_api_proxy().nodes[node.system_id]['profile']
+        self.assertEqual('maas-precise-i386-commissioning', profile)
+
+    def test_start_nodes_doesnt_set_commissioning_profile(self):
+        # Starting up a node should always set a profile. Complement the
+        # above test to show that a different profile can be set.
+        user = factory.make_user()
+        node = self.make_node(user)
+        output = Node.objects.start_nodes([node.system_id], user)
+
+        self.assertItemsEqual([node], output)
+        profile = get_provisioning_api_proxy().nodes[node.system_id]['profile']
+        self.assertEqual('maas-precise-i386', profile)
+
     def test_start_nodes_ignores_uneditable_nodes(self):
         nodes = [self.make_node(factory.make_user()) for counter in range(3)]
         ids = [node.system_id for node in nodes]

=== modified file 'src/maasserver/tests/test_provisioning.py'
--- src/maasserver/tests/test_provisioning.py	2012-03-29 16:31:32 +0000
+++ src/maasserver/tests/test_provisioning.py	2012-04-04 07:33:26 +0000
@@ -92,7 +92,7 @@
         self.papi.modify_nodes(
             {node.system_id: {'profile': self.make_papi_profile()}})
         self.assertEqual(
-            'maas-precise-i386', select_profile_for_node(node, self.papi))
+            'maas-precise-i386', select_profile_for_node(node))
 
     def test_select_profile_for_node_selects_Precise_and_right_arch(self):
         nodes = {
@@ -102,15 +102,23 @@
                 'maas-precise-%s' % name_arch_in_cobbler_style(arch)
                 for arch in nodes.keys()],
             [
-                select_profile_for_node(node, self.papi)
+                select_profile_for_node(node)
                 for node in nodes.values()])
 
     def test_select_profile_for_node_converts_architecture_name(self):
         node = factory.make_node(architecture='amd64')
-        profile = select_profile_for_node(node, self.papi)
+        profile = select_profile_for_node(node)
         self.assertNotIn('amd64', profile)
         self.assertIn('x86_64', profile)
 
+    def test_select_profile_for_node_works_for_commissioning(self):
+        # A special profile is chosen for nodes in the commissioning
+        # state.
+        node = factory.make_node(
+            status=NODE_STATUS.COMMISSIONING, architecture='i386')
+        profile = select_profile_for_node(node)
+        self.assertEqual('maas-precise-i386-commissioning', profile)
+
     def test_provision_post_save_Node_create(self):
         # The handler for Node's post-save signal registers the node in
         # its current state with the provisioning server.

=== modified file 'src/maasserver/tests/test_views.py'
--- src/maasserver/tests/test_views.py	2012-04-04 06:51:17 +0000
+++ src/maasserver/tests/test_views.py	2012-04-04 07:33:26 +0000
@@ -597,8 +597,8 @@
             input for input in doc.cssselect('form#node_actions input')
             if input.name == NodeActionForm.input_name]
 
-        self.assertSequenceEqual(
-            ["Accept Enlisted node"], [input.value for input in inputs])
+        self.assertIn(
+            "Accept Enlisted node", [input.value for input in inputs])
 
     def test_view_node_POST_admin_can_enlist_node(self):
         self.logged_in_user.is_superuser = True