Merge lp:~allenap/maas/bug-1081701-b into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 1380
Proposed branch: lp:~allenap/maas/bug-1081701-b
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 125 lines (+90/-0)
2 files modified
src/maasserver/api.py (+8/-0)
src/maasserver/tests/test_api.py (+82/-0)
To merge this branch: bzr merge lp:~allenap/maas/bug-1081701-b
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+136202@code.launchpad.net

Commit message

Update the node group's MAAS URL field when contacted by a starting cluster.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

Looks generally ok but I'm afraid I've got a few questions about the specifics of this branch :).

[0]

8 - form.save()
9 + nodegroup = form.save()
10 + update_nodegroup_maas_url(nodegroup, request)

I don't really see the need for that: the maas_url field will be updated when the cluster will have been accepted and will try to connect again so why bother updating it also when the nodegroup is created (also, at this stage, this might be a malicious request)?

[1]

23 elif existing_nodegroup.status == NODEGROUP_STATUS.PENDING:
24 + update_nodegroup_maas_url(existing_nodegroup, request)

Again, why the need to update nodegroup.maas_url here? We only need it after the cluster controller has been accepted (and tries to connect) and that is already taken care of by the code you've added after " if existing_nodegroup.status == NODEGROUP_STATUS.ACCEPTED:".

[3]

We need to have a test to make sure that when the master nodegroup registers itself, the value of maas_url is not changed and is still the empty string. This is fairly important since the master nodegroup runs on the same machine as the region controller and is configured to reach the region controller through the localhost interface. When dealing with the master nodegroup, we need to use DEFAULT_MAAS_URL where we would have used nodegroup.maas_url otherwise. All the code I've written in the fixes for c), d) and e) assumes that if nodegroup.maas_url is '', then DEFAULT_MAAS_URL should be used.

[4]

49 + self.assertIn(
50 + response.status_code,
51 + {code for code in httplib.responses if code // 100 == 2})

If this fails, the error message is not going to be really explicit, it would be good to have the content of the response in the failure message.

[5]

116 +class TestUpdateNodeGroupMAASURL(TransactionTestCase):
The test for update_nodegroup_maas_url should be in a transaction.

Why? I don't see anything in there that justifies the usage of a TransactionTestCase over the regular (Django-based) TestCase.

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

Yes, I had a point named [2] but then I ditched it and forgot to update the numbering :)

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

> Looks generally ok but I'm afraid I've got a few questions about the specifics
> of this branch :).

Thanks for the review!

>
> [0]
>
> 8       -                    form.save()
> 9       +                    nodegroup = form.save()
> 10      +                    update_nodegroup_maas_url(nodegroup, request)
>
> I don't really see the need for that:  the maas_url field will be updated when
> the cluster will have been accepted and will try to connect again so why
> bother updating it also when the nodegroup is created (also, at this stage,
> this might be a malicious request)?

You're right. I was thinking "it might not restart before it's
accepted", but that doesn't matter, because it will call register
again before it starts.

Fixed.

>
> [1]
>
> 23                   elif existing_nodegroup.status ==
> NODEGROUP_STATUS.PENDING:
> 24      +                update_nodegroup_maas_url(existing_nodegroup,
> request)
>
> Again, why the need to update nodegroup.maas_url here?  We only need it after
> the cluster controller has been accepted (and tries to connect) and that is
> already taken care of by the code you've added after " if
> existing_nodegroup.status == NODEGROUP_STATUS.ACCEPTED:".

As for [0].

>
> [3]
>
> We need to have a test to make sure that when the master nodegroup registers
> itself,

Do you mean, that it calls op=register, or is there some other
mechanism? test_register_master_nodegroup_does_not_update_maas_url
tests the former already I think.

>         the value of maas_url is not changed and is still the empty string.
> This is fairly important since the master nodegroup runs on the same machine
> as the region controller and is configured to reach the region controller
> through the localhost interface.  When dealing with the master nodegroup, we
> need to use DEFAULT_MAAS_URL where we would have used nodegroup.maas_url
> otherwise.  All the code I've written in the fixes for c), d) and e) assumes
> that if nodegroup.maas_url is '', then DEFAULT_MAAS_URL should be used.

I've added an assertion to
test_register_master_nodegroup_does_not_update_maas_url that tests the
model too.

>
> [4]
>
> 49      +        self.assertIn(
> 50      +            response.status_code,
> 51      +            {code for code in httplib.responses if code // 100 == 2})
>
> If this fails, the error message is not going to be really explicit, it would
> be good to have the content of the response in the failure message.

Fixed.

>
> [5]
>
> 116     +class TestUpdateNodeGroupMAASURL(TransactionTestCase):
> The test for update_nodegroup_maas_url should be in a transaction.
>
> Why?  I don't see anything in there that justifies the usage of a
> TransactionTestCase over the regular (Django-based) TestCase.

'Cause it updates the database? I suspect this is going to be another
WTFDjango moments.

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

> > [3]
> >
> > We need to have a test to make sure that when the master nodegroup registers
> > itself,
>
> Do you mean, that it calls op=register, or is there some other
> mechanism? test_register_master_nodegroup_does_not_update_maas_url
> tests the former already I think.

Oh yeah, you're right, I missed that test, my bad.

> > [5]
> >
> > 116     +class TestUpdateNodeGroupMAASURL(TransactionTestCase):
> > The test for update_nodegroup_maas_url should be in a transaction.
> >
> > Why?  I don't see anything in there that justifies the usage of a
> > TransactionTestCase over the regular (Django-based) TestCase.
>
> 'Cause it updates the database? I suspect this is going to be another
> WTFDjango moments.

Well, the transaction test case is only useful if the code you're testing explicitly uses transactions (see https://docs.djangoproject.com/en/dev/topics/testing/#django.test.TransactionTestCase for details).

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

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 2012-11-26 08:52:01 +0000
3+++ src/maasserver/api.py 2012-11-27 09:57:26 +0000
4@@ -929,6 +929,7 @@
5 raise ValidationError(form.errors)
6 else:
7 if existing_nodegroup.status == NODEGROUP_STATUS.ACCEPTED:
8+ update_nodegroup_maas_url(existing_nodegroup, request)
9 # The nodegroup exists and is validated, return the RabbitMQ
10 return get_celery_credentials()
11 elif existing_nodegroup.status == NODEGROUP_STATUS.REJECTED:
12@@ -938,6 +939,13 @@
13 "Awaiting admin approval.", status=httplib.ACCEPTED)
14
15
16+def update_nodegroup_maas_url(nodegroup, request):
17+ """Update `nodegroup.maas_url` from the given `request`."""
18+ path = request.META["SCRIPT_NAME"]
19+ nodegroup.maas_url = build_absolute_uri(request, path)
20+ nodegroup.save()
21+
22+
23 class NodeGroupsHandler(OperationsHandler):
24 """Manage NodeGroups."""
25 anonymous = AnonNodeGroupsHandler
26
27=== modified file 'src/maasserver/tests/test_api.py'
28--- src/maasserver/tests/test_api.py 2012-11-26 08:52:01 +0000
29+++ src/maasserver/tests/test_api.py 2012-11-27 09:57:26 +0000
30@@ -144,6 +144,7 @@
31 from testtools.matchers import (
32 AfterPreprocessing,
33 AllMatch,
34+ Annotate,
35 Contains,
36 Equals,
37 Is,
38@@ -3782,6 +3783,87 @@
39 self.assertIn('application/json', response['Content-Type'])
40 self.assertEqual({'BROKER_URL': fake_broker_url}, parsed_result)
41
42+ def assertSuccess(self, response):
43+ """Assert that `response` was successful (i.e. HTTP 2xx)."""
44+ self.assertThat(
45+ {code for code in httplib.responses if code // 100 == 2},
46+ Annotate(response, Contains(response.status_code)))
47+
48+ def test_register_new_nodegroup_does_not_record_maas_url(self):
49+ # When registering a cluster, the URL with which the call was made
50+ # (i.e. from the perspective of the cluster) is *not* recorded.
51+ self.create_configured_master()
52+ name = factory.make_name('name')
53+ uuid = factory.getRandomUUID()
54+ update_maas_url = self.patch(api, "update_nodegroup_maas_url")
55+ response = self.client.post(
56+ reverse('nodegroups_handler'),
57+ {'op': 'register', 'name': name, 'uuid': uuid})
58+ self.assertSuccess(response)
59+ self.assertEqual([], update_maas_url.call_args_list)
60+
61+ def test_register_accepted_nodegroup_updates_maas_url(self):
62+ # When registering an existing, accepted, cluster, the URL with which
63+ # the call was made is updated.
64+ self.create_configured_master()
65+ nodegroup = factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED)
66+ update_maas_url = self.patch(api, "update_nodegroup_maas_url")
67+ response = self.client.post(
68+ reverse('nodegroups_handler'),
69+ {'op': 'register', 'uuid': nodegroup.uuid})
70+ self.assertSuccess(response)
71+ update_maas_url.assert_called_once_with(nodegroup, ANY)
72+
73+ def test_register_pending_nodegroup_does_not_update_maas_url(self):
74+ # When registering an existing, pending, cluster, the URL with which
75+ # the call was made is *not* updated.
76+ self.create_configured_master()
77+ nodegroup = factory.make_node_group(status=NODEGROUP_STATUS.PENDING)
78+ update_maas_url = self.patch(api, "update_nodegroup_maas_url")
79+ response = self.client.post(
80+ reverse('nodegroups_handler'),
81+ {'op': 'register', 'uuid': nodegroup.uuid})
82+ self.assertSuccess(response)
83+ self.assertEqual([], update_maas_url.call_args_list)
84+
85+ def test_register_rejected_nodegroup_does_not_update_maas_url(self):
86+ # When registering an existing, pending, cluster, the URL with which
87+ # the call was made is *not* updated.
88+ self.create_configured_master()
89+ nodegroup = factory.make_node_group(status=NODEGROUP_STATUS.REJECTED)
90+ update_maas_url = self.patch(api, "update_nodegroup_maas_url")
91+ response = self.client.post(
92+ reverse('nodegroups_handler'),
93+ {'op': 'register', 'uuid': nodegroup.uuid})
94+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
95+ self.assertEqual([], update_maas_url.call_args_list)
96+
97+ def test_register_master_nodegroup_does_not_update_maas_url(self):
98+ # When registering the master cluster, the URL with which the call was
99+ # made is *not* updated.
100+ name = factory.make_name('name')
101+ update_maas_url = self.patch(api, "update_nodegroup_maas_url")
102+ response = self.client.post(
103+ reverse('nodegroups_handler'),
104+ {'op': 'register', 'name': name, 'uuid': 'master'})
105+ self.assertSuccess(response)
106+ self.assertEqual([], update_maas_url.call_args_list)
107+ # The new node group's maas_url field remains empty.
108+ nodegroup = NodeGroup.objects.get(uuid='master')
109+ self.assertEqual("", nodegroup.maas_url)
110+
111+
112+class TestUpdateNodeGroupMAASURL(TestCase):
113+ """Tests for `update_nodegroup_maas_url`."""
114+
115+ def test_update_from_request(self):
116+ request_factory = RequestFactory(SCRIPT_NAME="/script")
117+ request = request_factory.get(
118+ "/script/path", SERVER_NAME="example.com")
119+ nodegroup = factory.make_node_group()
120+ api.update_nodegroup_maas_url(nodegroup, request)
121+ self.assertEqual("http://example.com/script", nodegroup.maas_url)
122+
123
124 def dict_subset(obj, fields):
125 """Return a dict of a subset of the fields/values of an object."""