launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06996
[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