Merge lp:~rvb/maas/agent-bug-1239488-acquire into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 1709
Proposed branch: lp:~rvb/maas/agent-bug-1239488-acquire
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~rvb/maas/agent-bug-1239488
Diff against target: 137 lines (+52/-8)
5 files modified
src/maasserver/api.py (+8/-2)
src/maasserver/models/node.py (+3/-1)
src/maasserver/models/tests/test_node.py (+11/-5)
src/maasserver/tests/test_api_node.py (+9/-0)
src/maasserver/tests/test_api_nodes.py (+21/-0)
To merge this branch: bzr merge lp:~rvb/maas/agent-bug-1239488-acquire
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+191141@code.launchpad.net

Commit message

Change API's acquire() and release() so that they set/reset the agent_name field.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good, but I have some questions. Approved, but please consider
them before landing.

[1]

Node.agent_name is only ever set in Node.acquire() and cleared in
Node.release(). Node.acquire() sets the node's status to ALLOCATED,
and Node.release() to READY. However, a node /could/ also transition
to RETIRED or MISSING from ALLOCATED:

NODE_TRANSITIONS = {
    ...
    NODE_STATUS.ALLOCATED: [
        NODE_STATUS.READY,
        NODE_STATUS.RETIRED,
        NODE_STATUS.MISSING,
        ],

I'm not sure we're doing this anywhere, but my point is that nothing
is managing agent_name - or token or owner, other attributes set and
reset by acquire() and release() - in the event that a status
transition like this occurs.

Can we instead sort out these invariants in Node.clean()? For example,
Node.agent_name would always be "" if status != ALLOCATED.

[2]

+    def test_POST_release_resets_agent_name(self):
...
+    def test_POST_acquire_sets_agent_name(self):
...
+    def test_POST_acquire_agent_name_defaults_to_empty_string(self):

These are fine, but they're testing model code indirectly too. There
are no unit tests for the changes to the Node model. I think we ought
to nail down the behaviour of agent_name to the model code.

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

> Looks good, but I have some questions. Approved, but please consider
> them before landing.
>
>
> [1]
>
> Node.agent_name is only ever set in Node.acquire() and cleared in
> Node.release(). Node.acquire() sets the node's status to ALLOCATED,
> and Node.release() to READY. However, a node /could/ also transition
> to RETIRED or MISSING from ALLOCATED:
>
> NODE_TRANSITIONS = {
>    ...
>    NODE_STATUS.ALLOCATED: [
>        NODE_STATUS.READY,
>        NODE_STATUS.RETIRED,
>        NODE_STATUS.MISSING,
>        ],
>
> I'm not sure we're doing this anywhere, but my point is that nothing
> is managing agent_name - or token or owner, other attributes set and
> reset by acquire() and release() - in the event that a status
> transition like this occurs.
>
> Can we instead sort out these invariants in Node.clean()? For example,
> Node.agent_name would always be "" if status != ALLOCATED.

I think this is a very good point. I'd rather tackle that in a follow-up branch though. This is of no consequence now because we don't use NODE_STATUS.RETIRED or NODE_STATUS.MISSING.

> [2]
>
> +    def test_POST_release_resets_agent_name(self):
> ...
> +    def test_POST_acquire_sets_agent_name(self):
> ...
> +    def test_POST_acquire_agent_name_defaults_to_empty_string(self):
>
> These are fine, but they're testing model code indirectly too. There
> are no unit tests for the changes to the Node model. I think we ought
> to nail down the behaviour of agent_name to the model code.

Well, I also test the changed behavior of the model's code. See the changes to test_node.py.

test_POST_release_resets_agent_name is a test I /could/ remove… but I don't think a little bit of duplication hurts if it means thorough testing of the expected behavior.

Revision history for this message
Gavin Panella (allenap) wrote :

> > Can we instead sort out these invariants in Node.clean()? For example,
> > Node.agent_name would always be "" if status != ALLOCATED.
>
> I think this is a very good point.  I'd rather tackle that in a follow-up
> branch though.

Sure, that's fine.

> This is of no consequence now because we don't use
> NODE_STATUS.RETIRED or NODE_STATUS.MISSING.

Then we should remove them (in another branch). They're cognitive
baggage right now.

>
> > [2]
> >
> > +    def test_POST_release_resets_agent_name(self):
> > ...
> > +    def test_POST_acquire_sets_agent_name(self):
> > ...
> > +    def test_POST_acquire_agent_name_defaults_to_empty_string(self):
> >
> > These are fine, but they're testing model code indirectly too. There
> > are no unit tests for the changes to the Node model. I think we ought
> > to nail down the behaviour of agent_name to the model code.
>
> Well, I also test the changed behavior of the model's code.  See the changes
> to test_node.py.

So you have. I'm going code-blind :-/

>
> test_POST_release_resets_agent_name is a test I /could/ remove… but I don't
> think a little bit of duplication hurts if it means thorough testing of the
> expected behavior.

Yeah, that's fine.

Revision history for this message
Gavin Panella (allenap) wrote :

> Then we should remove them (in another branch). They're cognitive
> baggage right now.

Filed as bug 1240051.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

> I'm not sure we're doing this anywhere, but my point is that nothing
> is managing agent_name - or token or owner, other attributes set and
> reset by acquire() and release() - in the event that a status
> transition like this occurs.

This has long bothered me.

I think we need a single place for state transitions, for example:

Node.transitionToStatus(NODE_STATUS.MISSING)

Then all the logic is in one place rather than littered around.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api.py'
2--- src/maasserver/api.py 2013-10-07 09:12:40 +0000
3+++ src/maasserver/api.py 2013-10-15 11:53:34 +0000
4@@ -796,7 +796,10 @@
5 :param not_connected_to: List of routers' MAC Addresses the returned
6 node must not be connected to.
7 :type connected_to: list of unicodes
8- """
9+ :param agent_name: An optional agent name to attach to the
10+ acquired node.
11+ :type agent_name: unicode
12+ """
13 form = AcquireNodeForm(data=request.data)
14 if form.is_valid():
15 nodes = Node.objects.get_available_nodes_for_acquisition(
16@@ -805,7 +808,10 @@
17 node = get_first(nodes)
18 if node is None:
19 raise NodesNotAvailable("No matching node is available.")
20- node.acquire(request.user, get_oauth_token(request))
21+ agent_name = request.data.get('agent_name', '')
22+ node.acquire(
23+ request.user, get_oauth_token(request),
24+ agent_name=agent_name)
25 return node
26 raise ValidationError(form.errors)
27
28
29=== modified file 'src/maasserver/models/node.py'
30--- src/maasserver/models/node.py 2013-10-15 11:53:34 +0000
31+++ src/maasserver/models/node.py 2013-10-15 11:53:34 +0000
32@@ -770,12 +770,13 @@
33 power_params['mac_address'] = primary_mac.mac_address
34 return power_params
35
36- def acquire(self, user, token=None):
37+ def acquire(self, user, token=None, agent_name=''):
38 """Mark commissioned node as acquired by the given user and token."""
39 assert self.owner is None
40 assert token is None or token.user == user
41 self.status = NODE_STATUS.ALLOCATED
42 self.owner = user
43+ self.agent_name = agent_name
44 self.token = token
45 self.save()
46
47@@ -785,6 +786,7 @@
48 self.status = NODE_STATUS.READY
49 self.owner = None
50 self.token = None
51+ self.agent_name = ''
52 self.set_netboot()
53 self.save()
54
55
56=== modified file 'src/maasserver/models/tests/test_node.py'
57--- src/maasserver/models/tests/test_node.py 2013-10-08 10:48:28 +0000
58+++ src/maasserver/models/tests/test_node.py 2013-10-15 11:53:34 +0000
59@@ -374,15 +374,21 @@
60 node = factory.make_node(status=NODE_STATUS.READY)
61 user = factory.make_user()
62 token = create_auth_token(user)
63- node.acquire(user, token)
64- self.assertEqual(user, node.owner)
65- self.assertEqual(NODE_STATUS.ALLOCATED, node.status)
66+ agent_name = factory.make_name('agent-name')
67+ node.acquire(user, token, agent_name)
68+ self.assertEqual(
69+ (user, NODE_STATUS.ALLOCATED, agent_name),
70+ (node.owner, node.status, node.agent_name))
71
72 def test_release(self):
73+ agent_name = factory.make_name('agent-name')
74 node = factory.make_node(
75- status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
76+ status=NODE_STATUS.ALLOCATED, owner=factory.make_user(),
77+ agent_name=agent_name)
78 node.release()
79- self.assertEqual((NODE_STATUS.READY, None), (node.status, node.owner))
80+ self.assertEqual(
81+ (NODE_STATUS.READY, None, node.agent_name),
82+ (node.status, node.owner, ''))
83
84 def test_ip_addresses_queries_leases(self):
85 node = factory.make_node()
86
87=== modified file 'src/maasserver/tests/test_api_node.py'
88--- src/maasserver/tests/test_api_node.py 2013-10-07 09:12:40 +0000
89+++ src/maasserver/tests/test_api_node.py 2013-10-15 11:53:34 +0000
90@@ -297,6 +297,15 @@
91 self.client.post(self.get_node_uri(node), {'op': 'release'})
92 self.assertEqual('', reload_object(node).distro_series)
93
94+ def test_POST_release_resets_agent_name(self):
95+ agent_name = factory.make_name('agent-name')
96+ node = factory.make_node(
97+ status=NODE_STATUS.ALLOCATED, owner=self.logged_in_user,
98+ distro_series=factory.getRandomEnum(DISTRO_SERIES),
99+ agent_name=agent_name)
100+ self.client.post(self.get_node_uri(node), {'op': 'release'})
101+ self.assertEqual('', reload_object(node).agent_name)
102+
103 def test_POST_release_removes_token_and_user(self):
104 node = factory.make_node(status=NODE_STATUS.READY)
105 self.client.post(self.get_uri('nodes/'), {'op': 'acquire'})
106
107=== modified file 'src/maasserver/tests/test_api_nodes.py'
108--- src/maasserver/tests/test_api_nodes.py 2013-10-07 09:12:40 +0000
109+++ src/maasserver/tests/test_api_nodes.py 2013-10-15 11:53:34 +0000
110@@ -374,6 +374,27 @@
111 node = Node.objects.get(system_id=node.system_id)
112 self.assertEqual(self.logged_in_user, node.owner)
113
114+ def test_POST_acquire_sets_agent_name(self):
115+ available_status = NODE_STATUS.READY
116+ node = factory.make_node(
117+ status=available_status, owner=None,
118+ agent_name=factory.make_name('agent-name'))
119+ agent_name = factory.make_name('agent-name')
120+ self.client.post(
121+ self.get_uri('nodes/'),
122+ {'op': 'acquire', 'agent_name': agent_name})
123+ node = Node.objects.get(system_id=node.system_id)
124+ self.assertEqual(agent_name, node.agent_name)
125+
126+ def test_POST_acquire_agent_name_defaults_to_empty_string(self):
127+ available_status = NODE_STATUS.READY
128+ agent_name = factory.make_name('agent-name')
129+ node = factory.make_node(
130+ status=available_status, owner=None, agent_name=agent_name)
131+ self.client.post(self.get_uri('nodes/'), {'op': 'acquire'})
132+ node = Node.objects.get(system_id=node.system_id)
133+ self.assertEqual('', node.agent_name)
134+
135 def test_POST_acquire_fails_if_no_node_present(self):
136 # The "acquire" operation returns a Conflict error if no nodes
137 # are available.