Merge lp:~jtv/maas/bug-1363900 into lp:~maas-committers/maas/trunk

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 2868
Proposed branch: lp:~jtv/maas/bug-1363900
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 381 lines (+160/-26)
12 files modified
Makefile (+4/-4)
src/maasserver/bootresources.py (+2/-2)
src/maasserver/tests/test_bootresources.py (+1/-1)
src/provisioningserver/auth.py (+11/-4)
src/provisioningserver/path.py (+45/-0)
src/provisioningserver/rpc/boot_images.py (+2/-2)
src/provisioningserver/rpc/tests/test_boot_images.py (+1/-1)
src/provisioningserver/tasks.py (+2/-2)
src/provisioningserver/tests/test_path.py (+66/-0)
src/provisioningserver/tests/test_tasks.py (+2/-2)
src/provisioningserver/tests/test_upgrade_cluster.py (+15/-4)
src/provisioningserver/upgrade_cluster.py (+9/-4)
To merge this branch: bzr merge lp:~jtv/maas/bug-1363900
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+232982@code.launchpad.net

Commit message

When running a dev environment, move hard-coded "absolute" paths into the run/ directory.

This branch introduces a new way to compose such paths, which supports injection of a faux root directory through the MAAS_ROOT environment variable. It unbreaks startup of the dev server. To avoid import side effects, the setting is now wrapped in a getter function. We would like to do the migration of more of the hard-coded paths from celery configs in this way.

Description of the change

Pre-implementation call with Julian.

Also, the branch makes the upgrade/startup code skip the chmod on the GNUPGHOME if not running as root. This is not great; it might be better to try it and ignore errors if errno indicates that it's a matter of privileges. At any rate I did not want to introduce yet another way of indicating "this is just a development system."

Jeroen

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

Looks good. It's a bit annoying that the mechanism for overriding variables in a dev environment in pserv is so different from what we use in maasserver but I guess we will live. See my suggestions inline.

review: Approve
Revision history for this message
Jeroen T. Vermeulen (jtv) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2014-08-21 05:12:29 +0000
3+++ Makefile 2014-09-02 08:26:21 +0000
4@@ -265,17 +265,17 @@
5
6 # The `run` targets do not fit into the mould of the others.
7 run-region:
8- @services/run $(service_names_region)
9+ @env MAAS_ROOT=./run services/run $(service_names_region)
10 run-cluster:
11- @services/run $(service_names_cluster)
12+ @env MAAS_ROOT=./run services/run $(service_names_cluster)
13 run:
14- @services/run $(service_names_all)
15+ @env MAAS_ROOT=./run services/run $(service_names_all)
16
17 phony_services_targets += run-region run-cluster run
18
19 # This one's for the rapper, yo.
20 run+webapp:
21- @services/run $(service_names_region) +webapp
22+ @env MAAS_ROOT=./run services/run $(service_names_region) +webapp
23
24 phony_services_targets += run+webapp
25
26
27=== modified file 'src/maasserver/bootresources.py'
28--- src/maasserver/bootresources.py 2014-09-02 07:29:01 +0000
29+++ src/maasserver/bootresources.py 2014-09-02 08:26:21 +0000
30@@ -49,7 +49,7 @@
31 from maasserver.utils import absolute_reverse
32 from maasserver.utils.async import transactional
33 from maasserver.utils.orm import get_one
34-from provisioningserver.auth import MAAS_USER_GPGHOME
35+from provisioningserver.auth import get_maas_user_gpghome
36 from provisioningserver.import_images.download_descriptions import (
37 download_all_image_descriptions,
38 )
39@@ -878,7 +878,7 @@
40
41 try:
42 variables = {
43- 'GNUPGHOME': MAAS_USER_GPGHOME,
44+ 'GNUPGHOME': get_maas_user_gpghome(),
45 }
46 with environment_variables(variables):
47 maaslog.info("Started importing of boot resources.")
48
49=== modified file 'src/maasserver/tests/test_bootresources.py'
50--- src/maasserver/tests/test_bootresources.py 2014-09-01 10:11:15 +0000
51+++ src/maasserver/tests/test_bootresources.py 2014-09-02 08:26:21 +0000
52@@ -1095,7 +1095,7 @@
53
54 bootresources._import_resources(force=True)
55 self.assertEqual(
56- bootresources.MAAS_USER_GPGHOME,
57+ bootresources.get_maas_user_gpghome(),
58 fake_download.env['GNUPGHOME'])
59
60 def test__import_resources_calls_import_boot_images_on_clusters(self):
61
62=== modified file 'src/provisioningserver/auth.py'
63--- src/provisioningserver/auth.py 2014-04-25 18:03:38 +0000
64+++ src/provisioningserver/auth.py 2014-09-02 08:26:21 +0000
65@@ -13,22 +13,29 @@
66
67 __metaclass__ = type
68 __all__ = [
69+ 'get_maas_user_gpghome',
70 'get_recorded_api_credentials',
71 'get_recorded_nodegroup_uuid',
72- 'MAAS_USER_GPGHOME',
73 'record_api_credentials',
74 'record_nodegroup_uuid',
75 ]
76
77 from apiclient.creds import convert_string_to_tuple
78 from provisioningserver import cache
79+from provisioningserver.path import get_path
80+
81+
82+def get_maas_user_gpghome():
83+ """Return the GPG directory for the `maas` user.
84+
85+ Set $GPGHOME to this value ad-hoc when needed.
86+ """
87+ return get_path('/var/lib/maas/gnupg')
88+
89
90 # Cache key for the API credentials as last sent by the server.
91 API_CREDENTIALS_CACHE_KEY = 'api_credentials'
92
93-# GPG directory for the "maas" user. Set $GPGHOME to this value ad-hoc when
94-# needed.
95-MAAS_USER_GPGHOME = '/var/lib/maas/gnupg'
96
97 # Cache key for the uuid of the nodegroup that this worker manages.
98 NODEGROUP_UUID_CACHE_KEY = 'nodegroup_uuid'
99
100=== added file 'src/provisioningserver/path.py'
101--- src/provisioningserver/path.py 1970-01-01 00:00:00 +0000
102+++ src/provisioningserver/path.py 2014-09-02 08:26:21 +0000
103@@ -0,0 +1,45 @@
104+# Copyright 2014 Canonical Ltd. This software is licensed under the
105+# GNU Affero General Public License version 3 (see the file LICENSE).
106+
107+"""Compute paths relative to root."""
108+
109+from __future__ import (
110+ absolute_import,
111+ print_function,
112+ unicode_literals,
113+ )
114+
115+str = None
116+
117+__metaclass__ = type
118+__all__ = [
119+ 'get_path',
120+ ]
121+
122+from os import getenv
123+import os.path
124+
125+
126+def get_path(*path_elements):
127+ """Return an absolute path based on the `MAAS_ROOT` environment variable.
128+
129+ Use this to compute paths like `/var/lib/maas/gnupg`, so that development
130+ environments can redirect them to a playground location. For example, if
131+ `MAAS_ROOT` is set to `/tmp/maasroot`, then `get_path()` will return
132+ `/tmp/maasroot` and `get_path('/var/lib/maas')` returns
133+ `/tmp/maasroot/var/lib/maas`. If `MAAS_ROOT` is not set, you just get (a
134+ normalised version of) the location you passed in; just `get_path()` will
135+ always return the root directory.
136+
137+ This call may have minor side effects: it reads environment variables and
138+ the current working directory. Side effects during imports are bad, so
139+ avoid using this in global variables. Instead of exporting a variable
140+ that holds your path, export a getter function that returns your path.
141+ Add caching if it becomes a performance problem.
142+ """
143+ maas_root = getenv('MAAS_ROOT', '/')
144+ # Strip off a leading slash, if any. If left in, it would override any
145+ # preceding path elements. The MAAS_ROOT would be ignored.
146+ # The dot is there to make the call work even with zero path elements.
147+ path = os.path.join('.', *path_elements).lstrip('/')
148+ return os.path.abspath(os.path.join(maas_root, path))
149
150=== modified file 'src/provisioningserver/rpc/boot_images.py'
151--- src/provisioningserver/rpc/boot_images.py 2014-08-27 03:38:28 +0000
152+++ src/provisioningserver/rpc/boot_images.py 2014-09-02 08:26:21 +0000
153@@ -18,7 +18,7 @@
154 ]
155
156
157-from provisioningserver.auth import MAAS_USER_GPGHOME
158+from provisioningserver.auth import get_maas_user_gpghome
159 from provisioningserver.boot import tftppath
160 from provisioningserver.config import Config
161 from provisioningserver.import_images import boot_resources
162@@ -41,7 +41,7 @@
163 This is function is synchronous so it must be called with deferToThread.
164 """
165 variables = {
166- 'GNUPGHOME': MAAS_USER_GPGHOME,
167+ 'GNUPGHOME': get_maas_user_gpghome(),
168 }
169 with environment_variables(variables):
170 boot_resources.import_images(sources)
171
172=== modified file 'src/provisioningserver/rpc/tests/test_boot_images.py'
173--- src/provisioningserver/rpc/tests/test_boot_images.py 2014-09-01 08:40:13 +0000
174+++ src/provisioningserver/rpc/tests/test_boot_images.py 2014-09-02 08:26:21 +0000
175@@ -87,7 +87,7 @@
176
177 def test__run_import_sets_GPGHOME(self):
178 home = factory.make_name('home')
179- self.patch(boot_images, 'MAAS_USER_GPGHOME', home)
180+ self.patch(boot_images, 'get_maas_user_gpghome').return_value = home
181 fake = self.patch_boot_resources_function()
182 _run_import(sources=[])
183 self.assertEqual(home, fake.env['GNUPGHOME'])
184
185=== modified file 'src/provisioningserver/tasks.py'
186--- src/provisioningserver/tasks.py 2014-08-28 10:53:00 +0000
187+++ src/provisioningserver/tasks.py 2014-09-02 08:26:21 +0000
188@@ -39,7 +39,7 @@
189 tags,
190 )
191 from provisioningserver.auth import (
192- MAAS_USER_GPGHOME,
193+ get_maas_user_gpghome,
194 record_api_credentials,
195 record_nodegroup_uuid,
196 )
197@@ -468,7 +468,7 @@
198 if data is not None:
199 source["keyring_data"] = b64decode(data)
200 variables = {
201- 'GNUPGHOME': MAAS_USER_GPGHOME,
202+ 'GNUPGHOME': get_maas_user_gpghome(),
203 }
204 if http_proxy is not None:
205 variables['http_proxy'] = http_proxy
206
207=== added file 'src/provisioningserver/tests/test_path.py'
208--- src/provisioningserver/tests/test_path.py 1970-01-01 00:00:00 +0000
209+++ src/provisioningserver/tests/test_path.py 2014-09-02 08:26:21 +0000
210@@ -0,0 +1,66 @@
211+# Copyright 2014 Canonical Ltd. This software is licensed under the
212+# GNU Affero General Public License version 3 (see the file LICENSE).
213+
214+"""Tests for filesystem paths."""
215+
216+from __future__ import (
217+ absolute_import,
218+ print_function,
219+ unicode_literals,
220+ )
221+
222+str = None
223+
224+__metaclass__ = type
225+__all__ = []
226+
227+from os import getcwdu
228+import os.path
229+
230+from fixtures import EnvironmentVariableFixture
231+from maastesting.factory import factory
232+from maastesting.testcase import MAASTestCase
233+from provisioningserver.path import get_path
234+from testtools.matchers import StartsWith
235+
236+
237+class TestGetPath(MAASTestCase):
238+
239+ def set_root(self, root_path=None):
240+ """For the duration of this test, set the `MAAS_ROOT` variable`."""
241+ self.useFixture(EnvironmentVariableFixture('MAAS_ROOT', root_path))
242+
243+ def test__defaults_to_root(self):
244+ self.set_root()
245+ self.assertEqual('/', get_path())
246+
247+ def test__appends_path_elements(self):
248+ self.set_root()
249+ part1 = factory.make_name('dir')
250+ part2 = factory.make_name('file')
251+ self.assertEqual(
252+ os.path.join('/', part1, part2),
253+ get_path(part1, part2))
254+
255+ def test__obeys_MAAS_ROOT_variable(self):
256+ root = factory.make_name('/root')
257+ self.set_root(root)
258+ path = factory.make_name('path')
259+ self.assertEqual(os.path.join(root, path), get_path(path))
260+
261+ def test__returns_absolute_path(self):
262+ self.set_root('.')
263+ self.assertThat(get_path(), StartsWith('/'))
264+ self.assertEqual(getcwdu(), get_path())
265+
266+ def test__concatenates_despite_leading_slash(self):
267+ root = self.make_dir()
268+ self.set_root(root)
269+ filename = factory.make_name('file')
270+ self.assertEqual(
271+ os.path.join(root, filename),
272+ get_path('/' + filename))
273+
274+ def test__normalises(self):
275+ self.set_root()
276+ self.assertEqual('/foo/bar', get_path('foo///szut//..///bar//'))
277
278=== modified file 'src/provisioningserver/tests/test_tasks.py'
279--- src/provisioningserver/tests/test_tasks.py 2014-08-28 05:47:12 +0000
280+++ src/provisioningserver/tests/test_tasks.py 2014-09-02 08:26:21 +0000
281@@ -598,8 +598,8 @@
282 self.assertIsInstance(import_boot_images, Task)
283
284 def test_import_boot_images_sets_GPGHOME(self):
285- home = factory.make_name('home')
286- self.patch(tasks, 'MAAS_USER_GPGHOME', home)
287+ home = self.make_dir()
288+ self.patch(tasks, 'get_maas_user_gpghome').return_value = home
289 fake = self.patch_boot_resources_function()
290 import_boot_images(sources=[])
291 self.assertEqual(home, fake.env['GNUPGHOME'])
292
293=== modified file 'src/provisioningserver/tests/test_upgrade_cluster.py'
294--- src/provisioningserver/tests/test_upgrade_cluster.py 2014-08-13 21:49:35 +0000
295+++ src/provisioningserver/tests/test_upgrade_cluster.py 2014-09-02 08:26:21 +0000
296@@ -16,7 +16,7 @@
297
298 from argparse import ArgumentParser
299 from itertools import product
300-from os import listdir
301+import os
302 import os.path
303
304 from maastesting.factory import factory
305@@ -115,7 +115,8 @@
306 return os.path.join(parent_dir, factory.make_name('gpghome'))
307
308 def patch_gnupg_home(self, gpghome):
309- self.patch(upgrade_cluster, 'MAAS_USER_GPGHOME', gpghome)
310+ self.patch(upgrade_cluster, 'get_maas_user_gpghome').return_value = (
311+ gpghome)
312
313 def patch_call(self):
314 return self.patch(upgrade_cluster, 'check_call')
315@@ -125,7 +126,7 @@
316 self.patch_gnupg_home(existing_home)
317 self.patch_call()
318 upgrade_cluster.create_gnupg_home()
319- self.assertEqual([], listdir(existing_home))
320+ self.assertEqual([], os.listdir(existing_home))
321
322 def test__creates_directory(self):
323 parent = self.make_dir()
324@@ -135,15 +136,25 @@
325 upgrade_cluster.create_gnupg_home()
326 self.assertThat(new_home, DirExists())
327
328- def test__sets_ownership_to_maas(self):
329+ def test__sets_ownership_to_maas_if_running_as_root(self):
330 parent = self.make_dir()
331 new_home = self.make_nonexistent_path(parent)
332 self.patch_gnupg_home(new_home)
333 call = self.patch_call()
334+ self.patch(os, 'geteuid').return_value = 0
335 upgrade_cluster.create_gnupg_home()
336 self.assertThat(
337 call, MockCalledOnceWith(['chown', 'maas:maas', new_home]))
338
339+ def test__does_not_set_ownership_if_not_running_as_root(self):
340+ parent = self.make_dir()
341+ new_home = self.make_nonexistent_path(parent)
342+ self.patch_gnupg_home(new_home)
343+ call = self.patch_call()
344+ self.patch(os, 'geteuid').return_value = 101
345+ upgrade_cluster.create_gnupg_home()
346+ self.assertThat(call, MockNotCalled())
347+
348
349 class TestRetireBootResourcesYAML(MAASTestCase):
350 """Tests for `retire_bootresources_yaml`."""
351
352=== modified file 'src/provisioningserver/upgrade_cluster.py'
353--- src/provisioningserver/upgrade_cluster.py 2014-08-13 21:49:35 +0000
354+++ src/provisioningserver/upgrade_cluster.py 2014-09-02 08:26:21 +0000
355@@ -39,7 +39,7 @@
356 from textwrap import dedent
357
358 from provisioningserver import config
359-from provisioningserver.auth import MAAS_USER_GPGHOME
360+from provisioningserver.auth import get_maas_user_gpghome
361 from provisioningserver.boot.tftppath import (
362 drill_down,
363 list_subdirs,
364@@ -63,9 +63,14 @@
365
366 def create_gnupg_home():
367 """Upgrade hook: create maas user's GNUPG home directory."""
368- if not os.path.isdir(MAAS_USER_GPGHOME):
369- makedirs(MAAS_USER_GPGHOME)
370- check_call(['chown', 'maas:maas', MAAS_USER_GPGHOME])
371+ gpghome = get_maas_user_gpghome()
372+ if not os.path.isdir(gpghome):
373+ makedirs(gpghome)
374+ if os.geteuid() == 0:
375+ # Make the maas user the owner of its GPG home. Do this only if
376+ # running as root; otherwise it would probably fail. We want to
377+ # be able to start a development instance without triggering that.
378+ check_call(['chown', 'maas:maas', gpghome])
379
380
381 # Path to obsolete boot-resources configuration.