Merge lp:~rvb/maas/bug-1104215 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: 1426
Proposed branch: lp:~rvb/maas/bug-1104215
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 219 lines (+103/-3)
5 files modified
src/maas/settings.py (+5/-0)
src/maasserver/api.py (+16/-2)
src/maasserver/tests/test_api.py (+32/-1)
src/maasserver/utils/__init__.py (+23/-0)
src/maasserver/utils/tests/test_utils.py (+27/-0)
To merge this branch: bzr merge lp:~rvb/maas/bug-1104215
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+145588@code.launchpad.net

Commit message

Detect if the connecting cluster is the local cluster by comparing its UUID with the local cluster UUID.

Description of the change

- We now support an external cluster to connect first (for instance if no cluster was installed alongside the region cluster). It will need to be accepted by an admin.
- No pre-imp for this but consensus was reached in the discussion on the bug's comments.
- This has been tested in the lab.

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

Looks good.

[1]

+                    if is_local_cluster:
+                        return get_celery_credentials()
+                    else:
+                        return HttpResponse(
+                            "Cluster registered.  Awaiting admin approval.",
+                            status=httplib.ACCEPTED)

The condition - if is_local_cluster - is a secondhand indicator for
what you should be testing here: that the nodegroup status is
ACCEPTED.

[2]

+    try:
+        cluster_config = file(settings.LOCAL_CLUSTER_CONFIG).read()

`file` is gone in Python 3.x. Use `open`.

[3]

+    except IOError:
+        # Cluster config file is not present.
+        return False

Be more specific here:

    except IOError as error:
        if error.errno == errno.ENOENT:
            # Cluster config file is not present.
            return False
        else:
            # Anything else is an error.
            raise

[4]

+        match = re.search('CLUSTER_UUID="([^"]*)"', cluster_config)
+        if match is not None:
+            return uuid == match.groups()[0]

I know you're probably well aware of this, but: euargh.

[5]

+        match = re.search('CLUSTER_UUID="([^"]*)"', cluster_config)

Just in case someone comes along and removes the quotes, or changes
them to single quotes, or does either of those things in another part
of MAAS's code base, consider the following expression:

    re.search(
        "CLUSTER_UUID=(?P<quote>[\"']?)([^\"']+)(?P=quote)",
        cluster_config)

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

A great review, thanks. I've done all the changes you suggested.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maas/settings.py'
2--- src/maas/settings.py 2012-12-18 12:31:47 +0000
3+++ src/maas/settings.py 2013-01-30 15:35:12 +0000
4@@ -85,6 +85,11 @@
5 # maasserver.middleware.APIErrorsMiddleware)
6 PISTON_DISPLAY_ERRORS = False
7
8+# Location of the local cluster config file (installed by
9+# the package maas-cluster-controller). Use to distinguish the local cluster
10+# from the others.
11+LOCAL_CLUSTER_CONFIG = "/etc/maas/maas_cluster.conf"
12+
13 TEMPLATE_DEBUG = DEBUG
14
15 # Set this to where RaphaelJS files can be found.
16
17=== modified file 'src/maasserver/api.py'
18--- src/maasserver/api.py 2013-01-16 11:05:53 +0000
19+++ src/maasserver/api.py 2013-01-30 15:35:12 +0000
20@@ -178,6 +178,7 @@
21 absolute_reverse,
22 build_absolute_uri,
23 find_nodegroup,
24+ is_local_cluster_UUID,
25 map_enum,
26 strip_domain,
27 )
28@@ -912,17 +913,30 @@
29 "eth0"}]'
30 """
31 uuid = get_mandatory_param(request.data, 'uuid')
32+ is_local_cluster = is_local_cluster_UUID(uuid)
33 existing_nodegroup = get_one(NodeGroup.objects.filter(uuid=uuid))
34 if existing_nodegroup is None:
35 master = NodeGroup.objects.ensure_master()
36 # Does master.uuid look like it's a proper uuid?
37 if master.uuid in ('master', ''):
38 # Master nodegroup not yet configured, configure it.
39+ if is_local_cluster:
40+ # Connecting from localhost, accept the cluster
41+ # controller.
42+ status = NODEGROUP_STATUS.ACCEPTED
43+ else:
44+ # Connecting remotely, mark the cluster as pending.
45+ status = NODEGROUP_STATUS.PENDING
46 form = NodeGroupWithInterfacesForm(
47- data=request.data, instance=master)
48+ data=request.data, status=status, instance=master)
49 if form.is_valid():
50 form.save()
51- return get_celery_credentials()
52+ if status == NODEGROUP_STATUS.ACCEPTED:
53+ return get_celery_credentials()
54+ else:
55+ return HttpResponse(
56+ "Cluster registered. Awaiting admin approval.",
57+ status=httplib.ACCEPTED)
58 else:
59 raise ValidationError(form.errors)
60 else:
61
62=== modified file 'src/maasserver/tests/test_api.py'
63--- src/maasserver/tests/test_api.py 2013-01-16 11:05:53 +0000
64+++ src/maasserver/tests/test_api.py 2013-01-30 15:35:12 +0000
65@@ -31,6 +31,7 @@
66 import random
67 import shutil
68 import sys
69+from textwrap import dedent
70 from urlparse import urlparse
71
72 from apiclient.maas_client import MAASClient
73@@ -3673,9 +3674,20 @@
74 # validated by an admin.
75 self.assertEqual(httplib.ACCEPTED, response.status_code)
76
77- def test_register_nodegroup_returns_credentials_if_master(self):
78+ def setup_local_cluster_config(self, uuid):
79+ # Helper to get settings.LOCAL_CLUSTER_CONFIG to point to a valid
80+ # cluster config file with CLUSTER_UUID set to the given uuid.
81+ contents = dedent("""
82+ MAAS_URL=http://localhost/MAAS
83+ CLUSTER_UUID="%s"
84+ """ % uuid)
85+ file_name = self.make_file(contents=contents)
86+ self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
87+
88+ def test_register_nodegroup_returns_credentials_if_local_cluster(self):
89 name = factory.make_name('name')
90 uuid = factory.getRandomUUID()
91+ self.setup_local_cluster_config(uuid)
92 fake_broker_url = factory.make_name('fake-broker_url')
93 celery_conf = app_or_default().conf
94 self.patch(celery_conf, 'BROKER_URL', fake_broker_url)
95@@ -3692,9 +3704,27 @@
96 ({'BROKER_URL': fake_broker_url}, uuid),
97 (parsed_result, NodeGroup.objects.ensure_master().uuid))
98
99+ def test_register_nodegroup_gets_accepted_if_not_local_cluster(self):
100+ name = factory.make_name('name')
101+ uuid = factory.getRandomUUID()
102+ fake_broker_url = factory.make_name('fake-broker_url')
103+ celery_conf = app_or_default().conf
104+ self.patch(celery_conf, 'BROKER_URL', fake_broker_url)
105+ response = self.client.post(
106+ reverse('nodegroups_handler'),
107+ {
108+ 'op': 'register',
109+ 'name': name,
110+ 'uuid': uuid,
111+ })
112+ self.assertEqual(httplib.ACCEPTED, response.status_code, response)
113+ self.assertEqual(
114+ response.content, "Cluster registered. Awaiting admin approval.")
115+
116 def test_register_nodegroup_configures_master_if_unconfigured(self):
117 name = factory.make_name('nodegroup')
118 uuid = factory.getRandomUUID()
119+ self.setup_local_cluster_config(uuid)
120 interface = make_interface_settings()
121 self.client.post(
122 reverse('nodegroups_handler'),
123@@ -3713,6 +3743,7 @@
124
125 def test_register_nodegroup_uses_default_zone_name(self):
126 uuid = factory.getRandomUUID()
127+ self.setup_local_cluster_config(uuid)
128 self.client.post(
129 reverse('nodegroups_handler'),
130 {
131
132=== modified file 'src/maasserver/utils/__init__.py'
133--- src/maasserver/utils/__init__.py 2012-12-03 10:06:56 +0000
134+++ src/maasserver/utils/__init__.py 2013-01-30 15:35:12 +0000
135@@ -16,10 +16,13 @@
136 'find_nodegroup',
137 'get_db_state',
138 'ignore_unused',
139+ 'is_local_cluster_UUID',
140 'map_enum',
141 'strip_domain',
142 ]
143
144+import errno
145+import re
146 from urllib import urlencode
147 from urlparse import urljoin
148
149@@ -110,6 +113,26 @@
150 return hostname.split('.', 1)[0]
151
152
153+def is_local_cluster_UUID(uuid):
154+ """Return whether the given UUID is the UUID of the local cluster."""
155+ try:
156+ cluster_config = open(settings.LOCAL_CLUSTER_CONFIG).read()
157+ match = re.search(
158+ "CLUSTER_UUID=(?P<quote>[\"']?)([^\"']+)(?P=quote)",
159+ cluster_config)
160+ if match is not None:
161+ return uuid == match.groups()[1]
162+ else:
163+ return False
164+ except IOError as error:
165+ if error.errno == errno.ENOENT:
166+ # Cluster config file is not present.
167+ return False
168+ else:
169+ # Anything else is an error.
170+ raise
171+
172+
173 def find_nodegroup(request):
174 """Find the nodegroup whose subnet contains the IP Address of the
175 originating host of the request..
176
177=== modified file 'src/maasserver/utils/tests/test_utils.py'
178--- src/maasserver/utils/tests/test_utils.py 2012-11-29 20:15:12 +0000
179+++ src/maasserver/utils/tests/test_utils.py 2013-01-30 15:35:12 +0000
180@@ -34,6 +34,7 @@
181 build_absolute_uri,
182 find_nodegroup,
183 get_db_state,
184+ is_local_cluster_UUID,
185 map_enum,
186 strip_domain,
187 )
188@@ -194,6 +195,32 @@
189 self.assertEqual(results, map(strip_domain, inputs))
190
191
192+class TestIsLocalClusterUUID(TestCase):
193+
194+ def test_is_local_cluster_UUID_returns_false_if_no_config_file(self):
195+ bogus_file_name = '/tmp/bogus/%s' % factory.make_name('name')
196+ self.patch(settings, 'LOCAL_CLUSTER_CONFIG', bogus_file_name)
197+ self.assertFalse(is_local_cluster_UUID(factory.getRandomUUID()))
198+
199+ def test_is_local_cluster_UUID_returns_false_if_parsing_fails(self):
200+ file_name = self.make_file(contents="wrong content")
201+ self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
202+ self.assertFalse(is_local_cluster_UUID(factory.getRandomUUID()))
203+
204+ def test_is_local_cluster_UUID_returns_false_if_wrong_UUID(self):
205+ uuid = factory.getRandomUUID()
206+ other_uuid = factory.getRandomUUID()
207+ file_name = self.make_file(contents='CLUSTER_UUID="%s"' % other_uuid)
208+ self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
209+ self.assertFalse(is_local_cluster_UUID(uuid))
210+
211+ def test_is_local_cluster_UUID_returns_true_if_local_UUID(self):
212+ uuid = factory.getRandomUUID()
213+ file_name = self.make_file(contents='CLUSTER_UUID="%s"' % uuid)
214+ self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
215+ self.assertTrue(is_local_cluster_UUID(uuid))
216+
217+
218 def get_request(origin_ip):
219 return RequestFactory().post('/', REMOTE_ADDR=origin_ip)
220