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
=== modified file 'src/maas/settings.py'
--- src/maas/settings.py 2012-12-18 12:31:47 +0000
+++ src/maas/settings.py 2013-01-30 15:35:12 +0000
@@ -85,6 +85,11 @@
85# maasserver.middleware.APIErrorsMiddleware)85# maasserver.middleware.APIErrorsMiddleware)
86PISTON_DISPLAY_ERRORS = False86PISTON_DISPLAY_ERRORS = False
8787
88# Location of the local cluster config file (installed by
89# the package maas-cluster-controller). Use to distinguish the local cluster
90# from the others.
91LOCAL_CLUSTER_CONFIG = "/etc/maas/maas_cluster.conf"
92
88TEMPLATE_DEBUG = DEBUG93TEMPLATE_DEBUG = DEBUG
8994
90# Set this to where RaphaelJS files can be found.95# Set this to where RaphaelJS files can be found.
9196
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2013-01-16 11:05:53 +0000
+++ src/maasserver/api.py 2013-01-30 15:35:12 +0000
@@ -178,6 +178,7 @@
178 absolute_reverse,178 absolute_reverse,
179 build_absolute_uri,179 build_absolute_uri,
180 find_nodegroup,180 find_nodegroup,
181 is_local_cluster_UUID,
181 map_enum,182 map_enum,
182 strip_domain,183 strip_domain,
183 )184 )
@@ -912,17 +913,30 @@
912 "eth0"}]'913 "eth0"}]'
913 """914 """
914 uuid = get_mandatory_param(request.data, 'uuid')915 uuid = get_mandatory_param(request.data, 'uuid')
916 is_local_cluster = is_local_cluster_UUID(uuid)
915 existing_nodegroup = get_one(NodeGroup.objects.filter(uuid=uuid))917 existing_nodegroup = get_one(NodeGroup.objects.filter(uuid=uuid))
916 if existing_nodegroup is None:918 if existing_nodegroup is None:
917 master = NodeGroup.objects.ensure_master()919 master = NodeGroup.objects.ensure_master()
918 # Does master.uuid look like it's a proper uuid?920 # Does master.uuid look like it's a proper uuid?
919 if master.uuid in ('master', ''):921 if master.uuid in ('master', ''):
920 # Master nodegroup not yet configured, configure it.922 # Master nodegroup not yet configured, configure it.
923 if is_local_cluster:
924 # Connecting from localhost, accept the cluster
925 # controller.
926 status = NODEGROUP_STATUS.ACCEPTED
927 else:
928 # Connecting remotely, mark the cluster as pending.
929 status = NODEGROUP_STATUS.PENDING
921 form = NodeGroupWithInterfacesForm(930 form = NodeGroupWithInterfacesForm(
922 data=request.data, instance=master)931 data=request.data, status=status, instance=master)
923 if form.is_valid():932 if form.is_valid():
924 form.save()933 form.save()
925 return get_celery_credentials()934 if status == NODEGROUP_STATUS.ACCEPTED:
935 return get_celery_credentials()
936 else:
937 return HttpResponse(
938 "Cluster registered. Awaiting admin approval.",
939 status=httplib.ACCEPTED)
926 else:940 else:
927 raise ValidationError(form.errors)941 raise ValidationError(form.errors)
928 else:942 else:
929943
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2013-01-16 11:05:53 +0000
+++ src/maasserver/tests/test_api.py 2013-01-30 15:35:12 +0000
@@ -31,6 +31,7 @@
31import random31import random
32import shutil32import shutil
33import sys33import sys
34from textwrap import dedent
34from urlparse import urlparse35from urlparse import urlparse
3536
36from apiclient.maas_client import MAASClient37from apiclient.maas_client import MAASClient
@@ -3673,9 +3674,20 @@
3673 # validated by an admin.3674 # validated by an admin.
3674 self.assertEqual(httplib.ACCEPTED, response.status_code)3675 self.assertEqual(httplib.ACCEPTED, response.status_code)
36753676
3676 def test_register_nodegroup_returns_credentials_if_master(self):3677 def setup_local_cluster_config(self, uuid):
3678 # Helper to get settings.LOCAL_CLUSTER_CONFIG to point to a valid
3679 # cluster config file with CLUSTER_UUID set to the given uuid.
3680 contents = dedent("""
3681 MAAS_URL=http://localhost/MAAS
3682 CLUSTER_UUID="%s"
3683 """ % uuid)
3684 file_name = self.make_file(contents=contents)
3685 self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
3686
3687 def test_register_nodegroup_returns_credentials_if_local_cluster(self):
3677 name = factory.make_name('name')3688 name = factory.make_name('name')
3678 uuid = factory.getRandomUUID()3689 uuid = factory.getRandomUUID()
3690 self.setup_local_cluster_config(uuid)
3679 fake_broker_url = factory.make_name('fake-broker_url')3691 fake_broker_url = factory.make_name('fake-broker_url')
3680 celery_conf = app_or_default().conf3692 celery_conf = app_or_default().conf
3681 self.patch(celery_conf, 'BROKER_URL', fake_broker_url)3693 self.patch(celery_conf, 'BROKER_URL', fake_broker_url)
@@ -3692,9 +3704,27 @@
3692 ({'BROKER_URL': fake_broker_url}, uuid),3704 ({'BROKER_URL': fake_broker_url}, uuid),
3693 (parsed_result, NodeGroup.objects.ensure_master().uuid))3705 (parsed_result, NodeGroup.objects.ensure_master().uuid))
36943706
3707 def test_register_nodegroup_gets_accepted_if_not_local_cluster(self):
3708 name = factory.make_name('name')
3709 uuid = factory.getRandomUUID()
3710 fake_broker_url = factory.make_name('fake-broker_url')
3711 celery_conf = app_or_default().conf
3712 self.patch(celery_conf, 'BROKER_URL', fake_broker_url)
3713 response = self.client.post(
3714 reverse('nodegroups_handler'),
3715 {
3716 'op': 'register',
3717 'name': name,
3718 'uuid': uuid,
3719 })
3720 self.assertEqual(httplib.ACCEPTED, response.status_code, response)
3721 self.assertEqual(
3722 response.content, "Cluster registered. Awaiting admin approval.")
3723
3695 def test_register_nodegroup_configures_master_if_unconfigured(self):3724 def test_register_nodegroup_configures_master_if_unconfigured(self):
3696 name = factory.make_name('nodegroup')3725 name = factory.make_name('nodegroup')
3697 uuid = factory.getRandomUUID()3726 uuid = factory.getRandomUUID()
3727 self.setup_local_cluster_config(uuid)
3698 interface = make_interface_settings()3728 interface = make_interface_settings()
3699 self.client.post(3729 self.client.post(
3700 reverse('nodegroups_handler'),3730 reverse('nodegroups_handler'),
@@ -3713,6 +3743,7 @@
37133743
3714 def test_register_nodegroup_uses_default_zone_name(self):3744 def test_register_nodegroup_uses_default_zone_name(self):
3715 uuid = factory.getRandomUUID()3745 uuid = factory.getRandomUUID()
3746 self.setup_local_cluster_config(uuid)
3716 self.client.post(3747 self.client.post(
3717 reverse('nodegroups_handler'),3748 reverse('nodegroups_handler'),
3718 {3749 {
37193750
=== modified file 'src/maasserver/utils/__init__.py'
--- src/maasserver/utils/__init__.py 2012-12-03 10:06:56 +0000
+++ src/maasserver/utils/__init__.py 2013-01-30 15:35:12 +0000
@@ -16,10 +16,13 @@
16 'find_nodegroup',16 'find_nodegroup',
17 'get_db_state',17 'get_db_state',
18 'ignore_unused',18 'ignore_unused',
19 'is_local_cluster_UUID',
19 'map_enum',20 'map_enum',
20 'strip_domain',21 'strip_domain',
21 ]22 ]
2223
24import errno
25import re
23from urllib import urlencode26from urllib import urlencode
24from urlparse import urljoin27from urlparse import urljoin
2528
@@ -110,6 +113,26 @@
110 return hostname.split('.', 1)[0]113 return hostname.split('.', 1)[0]
111114
112115
116def is_local_cluster_UUID(uuid):
117 """Return whether the given UUID is the UUID of the local cluster."""
118 try:
119 cluster_config = open(settings.LOCAL_CLUSTER_CONFIG).read()
120 match = re.search(
121 "CLUSTER_UUID=(?P<quote>[\"']?)([^\"']+)(?P=quote)",
122 cluster_config)
123 if match is not None:
124 return uuid == match.groups()[1]
125 else:
126 return False
127 except IOError as error:
128 if error.errno == errno.ENOENT:
129 # Cluster config file is not present.
130 return False
131 else:
132 # Anything else is an error.
133 raise
134
135
113def find_nodegroup(request):136def find_nodegroup(request):
114 """Find the nodegroup whose subnet contains the IP Address of the137 """Find the nodegroup whose subnet contains the IP Address of the
115 originating host of the request..138 originating host of the request..
116139
=== modified file 'src/maasserver/utils/tests/test_utils.py'
--- src/maasserver/utils/tests/test_utils.py 2012-11-29 20:15:12 +0000
+++ src/maasserver/utils/tests/test_utils.py 2013-01-30 15:35:12 +0000
@@ -34,6 +34,7 @@
34 build_absolute_uri,34 build_absolute_uri,
35 find_nodegroup,35 find_nodegroup,
36 get_db_state,36 get_db_state,
37 is_local_cluster_UUID,
37 map_enum,38 map_enum,
38 strip_domain,39 strip_domain,
39 )40 )
@@ -194,6 +195,32 @@
194 self.assertEqual(results, map(strip_domain, inputs))195 self.assertEqual(results, map(strip_domain, inputs))
195196
196197
198class TestIsLocalClusterUUID(TestCase):
199
200 def test_is_local_cluster_UUID_returns_false_if_no_config_file(self):
201 bogus_file_name = '/tmp/bogus/%s' % factory.make_name('name')
202 self.patch(settings, 'LOCAL_CLUSTER_CONFIG', bogus_file_name)
203 self.assertFalse(is_local_cluster_UUID(factory.getRandomUUID()))
204
205 def test_is_local_cluster_UUID_returns_false_if_parsing_fails(self):
206 file_name = self.make_file(contents="wrong content")
207 self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
208 self.assertFalse(is_local_cluster_UUID(factory.getRandomUUID()))
209
210 def test_is_local_cluster_UUID_returns_false_if_wrong_UUID(self):
211 uuid = factory.getRandomUUID()
212 other_uuid = factory.getRandomUUID()
213 file_name = self.make_file(contents='CLUSTER_UUID="%s"' % other_uuid)
214 self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
215 self.assertFalse(is_local_cluster_UUID(uuid))
216
217 def test_is_local_cluster_UUID_returns_true_if_local_UUID(self):
218 uuid = factory.getRandomUUID()
219 file_name = self.make_file(contents='CLUSTER_UUID="%s"' % uuid)
220 self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name)
221 self.assertTrue(is_local_cluster_UUID(uuid))
222
223
197def get_request(origin_ip):224def get_request(origin_ip):
198 return RequestFactory().post('/', REMOTE_ADDR=origin_ip)225 return RequestFactory().post('/', REMOTE_ADDR=origin_ip)
199226