Merge lp:~allenap/maas/kill-all-the-configs-cluster into lp:~maas-committers/maas/trunk
- kill-all-the-configs-cluster
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4066 |
Proposed branch: | lp:~allenap/maas/kill-all-the-configs-cluster |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
5182 lines (+776/-1974) 78 files modified
.bzrignore (+4/-1) .gitignore (+6/-0) HACKING.txt (+1/-1) INSTALL.txt (+1/-1) Makefile (+17/-2) buildout.cfg (+0/-3) docs/development/cluster-registration.rst (+3/-23) etc/demo_maas_cluster.conf (+0/-8) etc/maas/pserv.yaml (+0/-24) etc/maas_cluster.conf (+0/-5) services/clusterd/run (+10/-8) setup.py (+1/-3) src/maas/demo.py (+0/-3) src/maas/development.py (+0/-8) src/maas/settings.py (+0/-5) src/maasserver/clusterrpc/tests/test_boot_images.py (+19/-21) src/maasserver/config.py (+1/-10) src/maasserver/rpc/tests/test_regionservice.py (+2/-2) src/maasserver/tests/test_config.py (+0/-14) src/maasserver/utils/__init__.py (+9/-19) src/maasserver/utils/tests/test_utils.py (+4/-11) src/maastesting/protractor/runner.py (+6/-19) src/provisioningserver/__main__.py (+0/-2) src/provisioningserver/boot/install_grub.py (+5/-5) src/provisioningserver/boot/tests/test_boot.py (+3/-3) src/provisioningserver/boot/tests/test_install_grub.py (+3/-4) src/provisioningserver/boot/tests/test_powernv.py (+7/-4) src/provisioningserver/boot/tests/test_pxe.py (+2/-2) src/provisioningserver/boot/tests/test_tftppath.py (+4/-14) src/provisioningserver/boot/tests/test_windows.py (+3/-9) src/provisioningserver/boot/tftppath.py (+2/-6) src/provisioningserver/boot/windows.py (+3/-2) src/provisioningserver/cluster_config.py (+0/-88) src/provisioningserver/cluster_config_command.py (+18/-32) src/provisioningserver/config.py (+121/-150) src/provisioningserver/configure_maas_url.py (+0/-123) src/provisioningserver/dhcp/tests/test_config.py (+3/-3) src/provisioningserver/dhcp/tests/test_detect.py (+3/-5) src/provisioningserver/diskless.py (+15/-9) src/provisioningserver/drivers/osystem/custom.py (+6/-5) src/provisioningserver/drivers/osystem/tests/test_custom.py (+7/-3) src/provisioningserver/drivers/osystem/tests/test_windows.py (+2/-6) src/provisioningserver/drivers/osystem/windows.py (+3/-2) src/provisioningserver/import_images/boot_resources.py (+16/-8) src/provisioningserver/import_images/tests/test_boot_resources.py (+14/-5) src/provisioningserver/path.py (+16/-5) src/provisioningserver/plugin.py (+36/-51) src/provisioningserver/pserv_services/image_download_service.py (+16/-15) src/provisioningserver/pserv_services/service_monitor_service.py (+9/-3) src/provisioningserver/pserv_services/tests/test_image_download_service.py (+13/-11) src/provisioningserver/pserv_services/tests/test_service_monitor_service.py (+21/-1) src/provisioningserver/pserv_services/tests/test_tftp.py (+36/-20) src/provisioningserver/pserv_services/tftp.py (+7/-5) src/provisioningserver/rpc/boot_images.py (+6/-6) src/provisioningserver/rpc/clusterservice.py (+11/-10) src/provisioningserver/rpc/tags.py (+6/-6) src/provisioningserver/rpc/testing/__init__.py (+0/-6) src/provisioningserver/rpc/tests/test_boot_images.py (+10/-4) src/provisioningserver/rpc/tests/test_clusterservice.py (+20/-24) src/provisioningserver/rpc/tests/test_tags.py (+7/-6) src/provisioningserver/testing/config.py (+0/-20) src/provisioningserver/testing/testcase.py (+0/-9) src/provisioningserver/tests/test_cluster_config.py (+0/-48) src/provisioningserver/tests/test_cluster_config_command.py (+36/-31) src/provisioningserver/tests/test_config.py (+66/-454) src/provisioningserver/tests/test_configure_maas_url.py (+0/-351) src/provisioningserver/tests/test_diskless.py (+24/-17) src/provisioningserver/tests/test_path.py (+8/-0) src/provisioningserver/tests/test_plugin.py (+18/-43) src/provisioningserver/tests/test_tags.py (+4/-2) src/provisioningserver/tests/test_upgrade_cluster.py (+10/-9) src/provisioningserver/upgrade_cluster.py (+10/-7) src/provisioningserver/utils/__init__.py (+13/-28) src/provisioningserver/utils/fs.py (+7/-2) src/provisioningserver/utils/script.py (+2/-19) src/provisioningserver/utils/tests/test_fs.py (+21/-5) src/provisioningserver/utils/tests/test_script.py (+0/-25) src/provisioningserver/utils/tests/test_utils.py (+19/-50) |
To merge this branch: | bzr merge lp:~allenap/maas/kill-all-the-configs-cluster |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andres Rodriguez (community) | Approve | ||
Review via email: mp+263494@code.launchpad.net |
Commit message
Consolidate the local configuration for the cluster into a single file.
Description of the change
This is largely based on Nate's work which has already been reviewed,
but the changes I made warrant a new review from the ground up.
You can see from the diffstat - http://
the majority of this branch is small changes to many files, resulting in
a lot of context in the diff. The summary is:
75 files changed, 726 insertions(+), 1968 deletions(-)
It's a fairly big change, but little of it is worth more than a moment's
contemplation, and the biggest overall change is removals.
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~allenap/maas/kill-all-the-configs-cluster into lp:maas failed. Below is the output from the failed tests.
Ign http://
Get:1 http://
Get:2 http://
Ign http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Get:6 http://
Hit http://
Get:7 http://
Get:8 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:9 http://
Get:10 http://
Get:11 http://
Get:12 http://
Hit http://
Hit http://
Ign http://
Ign http://
Fetched 1,848 kB in 4s (439 kB/s)
Reading package lists...
sudo DEBIAN_
--
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2015-06-15 10:58:41 +0000 |
3 | +++ .bzrignore 2015-07-03 11:18:28 +0000 |
4 | @@ -24,8 +24,9 @@ |
5 | ./docs/_autosummary |
6 | ./docs/_build |
7 | ./docs/api.rst |
8 | +./eggs |
9 | +./etc/maas/clusterd.conf |
10 | ./etc/maas/regiond.conf |
11 | -./eggs |
12 | ./include |
13 | ./lib |
14 | ./local |
15 | @@ -35,6 +36,8 @@ |
16 | ./media/development |
17 | ./parts |
18 | ./run/* |
19 | +./run/etc/* |
20 | +./run/etc/maas/* |
21 | ./services/*/introspect |
22 | ./services/*/supervise |
23 | ./src/maasserver/static/js/enums.js |
24 | |
25 | === modified file '.gitignore' |
26 | --- .gitignore 2015-04-10 09:45:46 +0000 |
27 | +++ .gitignore 2015-07-03 11:18:28 +0000 |
28 | @@ -4,6 +4,8 @@ |
29 | /.db.lock |
30 | /.hypothesis |
31 | /.idea |
32 | +/.idea/scopes |
33 | +/.idea/workspace.xml |
34 | /.installed.cfg |
35 | /.noseids |
36 | /acceptance/*.build |
37 | @@ -23,6 +25,8 @@ |
38 | /docs/_build |
39 | /docs/api.rst |
40 | /eggs |
41 | +/etc/maas/clusterd.conf |
42 | +/etc/maas/regiond.conf |
43 | /include |
44 | /lib |
45 | /local |
46 | @@ -32,6 +36,8 @@ |
47 | /media/development |
48 | /parts |
49 | /run/* |
50 | +/run/etc/* |
51 | +/run/etc/maas/* |
52 | /services/*/introspect |
53 | /services/*/supervise |
54 | /src/maasserver/static/js/enums.js |
55 | |
56 | === modified file 'HACKING.txt' |
57 | --- HACKING.txt 2015-06-10 11:49:32 +0000 |
58 | +++ HACKING.txt 2015-07-03 11:18:28 +0000 |
59 | @@ -223,7 +223,7 @@ |
60 | you want to boot some real hardware. By default, it's set to start up on |
61 | port 5244 for testing purposes. Make these changes:: |
62 | |
63 | - * Edit ``etc/maas/pserv.yaml`` to change the tftp/port setting to 69 |
64 | + * Use ``bin/maas-provision`` to change the tftp-port setting to 69 |
65 | * Install the ``authbind``package: |
66 | |
67 | $ sudo apt-get install authbind |
68 | |
69 | === modified file 'INSTALL.txt' |
70 | --- INSTALL.txt 2015-05-22 14:59:47 +0000 |
71 | +++ INSTALL.txt 2015-07-03 11:18:28 +0000 |
72 | @@ -330,7 +330,7 @@ |
73 | |
74 | * Initiate TCP connections (for HTTP) to each region controller on |
75 | port 80 or port 5240, the choice of which depends on the setting of |
76 | - ``MAAS_URL``. |
77 | + the MAAS URL. |
78 | |
79 | * Initiate TCP connections (for RPC) to each region controller between |
80 | port 5250 and 5259 inclusive. This permits up to 10 ``maas-regiond`` |
81 | |
82 | === modified file 'Makefile' |
83 | --- Makefile 2015-06-16 21:10:26 +0000 |
84 | +++ Makefile 2015-07-03 11:18:28 +0000 |
85 | @@ -297,7 +297,7 @@ |
86 | clean-styles: |
87 | $(RM) $(scss_output) |
88 | |
89 | -clean: stop |
90 | +clean: stop clean-run |
91 | $(MAKE) -C acceptance $@ |
92 | find . -type f -name '*.py[co]' -print0 | xargs -r0 $(RM) |
93 | find . -type f -name '*~' -print0 | xargs -r0 $(RM) |
94 | @@ -316,7 +316,21 @@ |
95 | $(RM) -r build dist logs/* parts |
96 | $(RM) tags TAGS .installed.cfg |
97 | $(RM) -r *.egg *.egg-info src/*.egg-info |
98 | - $(RM) -r run/* run-e2e/* services/*/supervise |
99 | + $(RM) -r services/*/supervise |
100 | + |
101 | +# Be selective about what to remove from run and run-e2e. |
102 | +define clean-run-template |
103 | +find $(1) -depth ! -type d \ |
104 | + ! -path $(1)/etc/maas/templates \ |
105 | + ! -path $(1)/etc/maas/drivers.yaml \ |
106 | + -print0 | xargs -r0 $(RM) |
107 | +find $(1) -depth -type d \ |
108 | + -print0 | xargs -r0 rmdir --ignore-fail-on-non-empty |
109 | +endef |
110 | + |
111 | +clean-run: |
112 | + $(call clean-run-template,run) |
113 | + $(call clean-run-template,run-e2e) |
114 | |
115 | clean+db: clean |
116 | $(RM) -r db |
117 | @@ -348,6 +362,7 @@ |
118 | check |
119 | clean |
120 | clean+db |
121 | + clean-run |
122 | clean-styles |
123 | configure-buildout |
124 | copyright |
125 | |
126 | === modified file 'buildout.cfg' |
127 | --- buildout.cfg 2015-06-15 10:58:41 +0000 |
128 | +++ buildout.cfg 2015-07-03 11:18:28 +0000 |
129 | @@ -46,7 +46,6 @@ |
130 | ${common:environment} |
131 | environment = |
132 | from os import environ |
133 | - environ.setdefault("MAAS_CONFIG_DIR", "${buildout:directory}/etc/maas") |
134 | environ.setdefault("MAAS_ROOT", "${buildout:directory}/run") |
135 | warnings = |
136 | from warnings import filterwarnings |
137 | @@ -77,7 +76,6 @@ |
138 | twistd.region=twisted.scripts.twistd:run |
139 | initialization = |
140 | ${common:initialization} |
141 | - environ.setdefault("MAAS_REGION_CONFIG", "${buildout:directory}/etc/maas/regiond.conf") |
142 | environ.setdefault("DJANGO_SETTINGS_MODULE", "maas.development") |
143 | scripts = |
144 | maas-region-admin |
145 | @@ -190,7 +188,6 @@ |
146 | twistd.cluster |
147 | initialization = |
148 | ${common:initialization} |
149 | - environ.setdefault("MAAS_PROVISION_CMD", "${buildout:directory}/bin/maas-provision") |
150 | environ.setdefault("MAAS_CLUSTER_DEVELOP", "TRUE") |
151 | |
152 | [cluster-test] |
153 | |
154 | === modified file 'docs/development/cluster-registration.rst' |
155 | --- docs/development/cluster-registration.rst 2015-02-19 21:49:54 +0000 |
156 | +++ docs/development/cluster-registration.rst 2015-07-03 11:18:28 +0000 |
157 | @@ -21,29 +21,9 @@ |
158 | Region Controller Location |
159 | -------------------------- |
160 | |
161 | -The cluster obviously needs to know where the region controller is, and this is |
162 | -configured in a file ``/etc/maas/maas_cluster.conf`` (or |
163 | -``etc/demo_maas_cluster.conf`` for development environments). |
164 | - |
165 | -Cluster configuration file |
166 | --------------------------- |
167 | - |
168 | -This config file generally contains two items like this:: |
169 | - |
170 | - MAAS_URL=http://0.0.0.0:5240/ |
171 | - CLUSTER_UUID="adfd3977-f251-4f2c-8d61-745dbd690bf2" |
172 | - |
173 | -The values here are the defaults in the development environment. MAAS_URL |
174 | -tells the cluster controller where to find the region controller. |
175 | - |
176 | -``CLUSTER_UUID`` is what the region uses to tell clusters apart when they |
177 | -connect. Each cluster is free to generate its own UUID but the development |
178 | -environment fixes it in advance. The Ubuntu packaging generates a new UUID for |
179 | -a cluster controller each time it is installed. |
180 | - |
181 | -.. warning:: |
182 | - The format of this config file is very sensitive due to the code that parses |
183 | - it. It will not accept quoting, or any kind of comments. |
184 | +The cluster obviously needs to know where the region controller is, and |
185 | +this is configured in a file ``/etc/maas/clusterd.conf``. This should |
186 | +only ever be modified via the ``maas-provision`` command. |
187 | |
188 | .. _first-cluster: |
189 | |
190 | |
191 | === removed file 'etc/demo_maas_cluster.conf' |
192 | --- etc/demo_maas_cluster.conf 2015-03-12 18:13:14 +0000 |
193 | +++ etc/demo_maas_cluster.conf 1970-01-01 00:00:00 +0000 |
194 | @@ -1,8 +0,0 @@ |
195 | -# This is the version of maas_cluster.conf that's used when running from a |
196 | -# branch. The region controller will serve HTTP on a different port from a |
197 | -# packaged MAAS, and the master cluster controller (running on the same |
198 | -# development machine) has a fixed UUID. |
199 | -# It is also carefully crafted so it works as both a bash script and a |
200 | -# Python script. |
201 | -MAAS_URL="http://0.0.0.0:5240/MAAS/" |
202 | -CLUSTER_UUID="adfd3977-f251-4f2c-8d61-745dbd690bf2" |
203 | |
204 | === removed file 'etc/maas/pserv.yaml' |
205 | --- etc/maas/pserv.yaml 2015-04-24 13:27:39 +0000 |
206 | +++ etc/maas/pserv.yaml 1970-01-01 00:00:00 +0000 |
207 | @@ -1,24 +0,0 @@ |
208 | -## |
209 | -## Provisioning Server (pserv) configuration. |
210 | -## |
211 | - |
212 | -## Where to log. This log can be rotated by sending SIGUSR1 to the |
213 | -## running server. |
214 | -# |
215 | -# logfile: "pserv.log" |
216 | -logfile: "/dev/null" |
217 | - |
218 | -## TFTP configuration. |
219 | -# |
220 | -tftp: |
221 | - # The "root" setting has been replaced by "resource_root". The old setting |
222 | - # is used one final time when upgrading a pre-14.04 cluster controller to a |
223 | - # 14.04 version. After that upgrade, it can be removed. |
224 | - # |
225 | - # resource_root: /var/lib/maas/boot-resources/current/ |
226 | - |
227 | - # port: 69 |
228 | - port: 5244 |
229 | - ## The URL to be contacted to generate PXE configurations. |
230 | - # generator: http://localhost/MAAS/api/1.0/pxeconfig/ |
231 | - generator: http://localhost:5240/MAAS/api/1.0/pxeconfig/ |
232 | |
233 | === removed file 'etc/maas_cluster.conf' |
234 | --- etc/maas_cluster.conf 2015-03-12 18:13:14 +0000 |
235 | +++ etc/maas_cluster.conf 1970-01-01 00:00:00 +0000 |
236 | @@ -1,5 +0,0 @@ |
237 | -# This file is used by the cluster controller startup script: |
238 | -# `maas-provision start-cluster-controller` |
239 | -# to get the URL to the MAAS region controller's API. Normally, packaging |
240 | -# will update this file automatically but it may also be configured manually. |
241 | -MAAS_URL=http://localhost/MAAS |
242 | |
243 | === added directory 'run-e2e/etc' |
244 | === added directory 'run-e2e/etc/maas' |
245 | === added symlink 'run-e2e/etc/maas/drivers.yaml' |
246 | === target is u'../../../etc/maas/drivers.yaml' |
247 | === added symlink 'run-e2e/etc/maas/templates' |
248 | === target is u'../../../etc/maas/templates' |
249 | === added directory 'run/etc' |
250 | === added directory 'run/etc/maas' |
251 | === added symlink 'run/etc/maas/drivers.yaml' |
252 | === target is u'../../../etc/maas/drivers.yaml' |
253 | === added symlink 'run/etc/maas/templates' |
254 | === target is u'../../../etc/maas/templates' |
255 | === modified file 'services/clusterd/run' |
256 | --- services/clusterd/run 2015-05-29 16:47:37 +0000 |
257 | +++ services/clusterd/run 2015-07-03 11:18:28 +0000 |
258 | @@ -17,16 +17,18 @@ |
259 | # because there are race issues when restarting. |
260 | [ -z "${logdir:-}" ] || exec &>> "${logdir}/current" |
261 | |
262 | +# Configure the cluster's UUID to match sampledata, and also use a high |
263 | +# port for TFTP to match this branch's etc/services. |
264 | +bin/maas-provision config \ |
265 | + --uuid adfd3977-f251-4f2c-8d61-745dbd690bf2 \ |
266 | + --tftp-port 5244 |
267 | + |
268 | +# Wait for the installation of the shared-secret. |
269 | +until bin/maas-provision check-for-shared-secret; do sleep 2; done |
270 | + |
271 | # Exec the Provisioning Server. |
272 | script="$(readlink -f bin/twistd.cluster)" |
273 | -config="$(readlink -f etc/maas/pserv.yaml)" |
274 | - |
275 | -# Obtain the development setting for CLUSTER_UUID. |
276 | -. etc/demo_maas_cluster.conf |
277 | -export CLUSTER_UUID |
278 | -export MAAS_URL |
279 | |
280 | exec $(command -v authbind && echo --deep) \ |
281 | "${script}" --nodaemon --pidfile="" maas-clusterd \ |
282 | - --introspect="unix:${here}/introspect:lockfile=0" \ |
283 | - --config-file "${config}" |
284 | + --introspect="unix:${here}/introspect:lockfile=0" |
285 | |
286 | === modified file 'setup.py' |
287 | --- setup.py 2015-05-29 16:47:37 +0000 |
288 | +++ setup.py 2015-07-03 11:18:28 +0000 |
289 | @@ -64,9 +64,7 @@ |
290 | |
291 | data_files=[ |
292 | ('/etc/maas', |
293 | - ['etc/maas/pserv.yaml', |
294 | - 'etc/maas/drivers.yaml', |
295 | - 'etc/maas_cluster.conf', |
296 | + ['etc/maas/drivers.yaml', |
297 | 'contrib/maas-http.conf']), |
298 | ('/etc/maas/templates/uefi', |
299 | glob('etc/maas/templates/uefi/*.template')), |
300 | |
301 | === modified file 'src/maas/demo.py' |
302 | --- src/maas/demo.py 2015-06-15 10:58:41 +0000 |
303 | +++ src/maas/demo.py 2015-07-03 11:18:28 +0000 |
304 | @@ -38,9 +38,6 @@ |
305 | |
306 | MAAS_CLI = abspath("bin/maas-region-admin") |
307 | |
308 | -# Use the in-branch development version of maas_cluster.conf. |
309 | -LOCAL_CLUSTER_CONFIG = abspath("etc/demo_maas_cluster.conf") |
310 | - |
311 | # For demo purposes, give nodes unauthenticated access to their metadata |
312 | # even if we can't pass boot parameters. This is not safe; do not |
313 | # enable it on a production MAAS. |
314 | |
315 | === modified file 'src/maas/development.py' |
316 | --- src/maas/development.py 2015-06-15 10:58:41 +0000 |
317 | +++ src/maas/development.py 2015-07-03 11:18:28 +0000 |
318 | @@ -95,14 +95,6 @@ |
319 | # Inject custom code for setting up the test database. |
320 | patch_db_creation(abspath('db'), abspath('schema/baseline.sql')) |
321 | |
322 | -# Override the default provisioning config filename. |
323 | -import provisioningserver.config |
324 | -provisioningserver.config.Config.DEFAULT_FILENAME = abspath( |
325 | - "etc/maas/pserv.yaml") |
326 | - |
327 | -# Use the in-branch development version of maas_cluster.conf. |
328 | -LOCAL_CLUSTER_CONFIG = abspath("etc/demo_maas_cluster.conf") |
329 | - |
330 | PASSWORD_HASHERS = ( |
331 | 'django.contrib.auth.hashers.MD5PasswordHasher', |
332 | ) |
333 | |
334 | === modified file 'src/maas/settings.py' |
335 | --- src/maas/settings.py 2015-06-10 10:08:45 +0000 |
336 | +++ src/maas/settings.py 2015-07-03 11:18:28 +0000 |
337 | @@ -68,11 +68,6 @@ |
338 | # same model, so we silence the warnings that Piston gives. |
339 | PISTON_IGNORE_DUPE_MODELS = True |
340 | |
341 | -# Location of the local cluster config file (installed by |
342 | -# the package maas-cluster-controller). Use to distinguish the local cluster |
343 | -# from the others. |
344 | -LOCAL_CLUSTER_CONFIG = "/etc/maas/maas_cluster.conf" |
345 | - |
346 | TEMPLATE_DEBUG = DEBUG |
347 | |
348 | YUI_DEBUG = DEBUG |
349 | |
350 | === modified file 'src/maasserver/clusterrpc/tests/test_boot_images.py' |
351 | --- src/maasserver/clusterrpc/tests/test_boot_images.py 2015-05-29 16:47:37 +0000 |
352 | +++ src/maasserver/clusterrpc/tests/test_boot_images.py 2015-07-03 11:18:28 +0000 |
353 | @@ -45,11 +45,12 @@ |
354 | make_boot_image_storage_params, |
355 | make_image, |
356 | ) |
357 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
358 | from twisted.internet.defer import succeed |
359 | |
360 | |
361 | -def make_image_dir(image_params, tftproot): |
362 | - """Fake a boot image matching `image_params` under `tftproot`.""" |
363 | +def make_image_dir(image_params, tftp_root): |
364 | + """Fake a boot image matching `image_params` under `tftp_root`.""" |
365 | image_dir = locate_tftp_path( |
366 | compose_image_path( |
367 | osystem=image_params['osystem'], |
368 | @@ -57,7 +58,7 @@ |
369 | subarch=image_params['subarchitecture'], |
370 | release=image_params['release'], |
371 | label=image_params['label']), |
372 | - tftproot) |
373 | + tftp_root) |
374 | os.makedirs(image_dir) |
375 | factory.make_file(image_dir, 'linux') |
376 | factory.make_file(image_dir, 'initrd.gz') |
377 | @@ -136,16 +137,21 @@ |
378 | self.assertFalse(is_import_boot_images_running_for(nodegroup)) |
379 | |
380 | |
381 | +def prepare_tftp_root(test): |
382 | + """Create a `current` directory and configure its use.""" |
383 | + test.tftp_root = os.path.join(test.make_dir(), 'current') |
384 | + os.mkdir(test.tftp_root) |
385 | + test.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
386 | + config = ClusterConfigurationFixture(tftp_root=test.tftp_root) |
387 | + test.useFixture(config) |
388 | + |
389 | + |
390 | class TestGetBootImages(MAASServerTestCase): |
391 | """Tests for `get_boot_images`.""" |
392 | |
393 | def setUp(self): |
394 | super(TestGetBootImages, self).setUp() |
395 | - resource_dir = self.make_dir() |
396 | - self.tftproot = os.path.join(resource_dir, 'current') |
397 | - os.mkdir(self.tftproot) |
398 | - self.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
399 | - self.patch(boot_images, 'BOOT_RESOURCES_STORAGE', resource_dir) |
400 | + prepare_tftp_root(self) # Sets self.tftp_root. |
401 | |
402 | def test_returns_boot_images(self): |
403 | nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
404 | @@ -154,7 +160,7 @@ |
405 | purposes = ['install', 'commissioning', 'xinstall'] |
406 | params = [make_boot_image_storage_params() for _ in range(3)] |
407 | for param in params: |
408 | - make_image_dir(param, self.tftproot) |
409 | + make_image_dir(param, self.tftp_root) |
410 | test_tftppath.make_osystem(self, param['osystem'], purposes) |
411 | self.assertItemsEqual( |
412 | [ |
413 | @@ -170,11 +176,7 @@ |
414 | |
415 | def setUp(self): |
416 | super(TestGetAvailableBootImages, self).setUp() |
417 | - resource_dir = self.make_dir() |
418 | - self.tftproot = os.path.join(resource_dir, 'current') |
419 | - os.mkdir(self.tftproot) |
420 | - self.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
421 | - self.patch(boot_images, 'BOOT_RESOURCES_STORAGE', resource_dir) |
422 | + prepare_tftp_root(self) # Sets self.tftp_root. |
423 | |
424 | def test_returns_boot_images_for_one_cluster(self): |
425 | factory.make_NodeGroup().accept() |
426 | @@ -183,7 +185,7 @@ |
427 | purposes = ['install', 'commissioning', 'xinstall'] |
428 | params = [make_boot_image_storage_params() for _ in range(3)] |
429 | for param in params: |
430 | - make_image_dir(param, self.tftproot) |
431 | + make_image_dir(param, self.tftp_root) |
432 | test_tftppath.make_osystem(self, param['osystem'], purposes) |
433 | self.assertItemsEqual( |
434 | [ |
435 | @@ -260,17 +262,13 @@ |
436 | |
437 | def setUp(self): |
438 | super(TestGetBootImagesFor, self).setUp() |
439 | - resource_dir = self.make_dir() |
440 | - self.tftproot = os.path.join(resource_dir, 'current') |
441 | - os.mkdir(self.tftproot) |
442 | - self.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
443 | - self.patch(boot_images, 'BOOT_RESOURCES_STORAGE', resource_dir) |
444 | + prepare_tftp_root(self) # Sets self.tftp_root. |
445 | |
446 | def make_boot_images(self): |
447 | purposes = ['install', 'commissioning', 'xinstall'] |
448 | params = [make_boot_image_storage_params() for _ in range(3)] |
449 | for param in params: |
450 | - make_image_dir(param, self.tftproot) |
451 | + make_image_dir(param, self.tftp_root) |
452 | test_tftppath.make_osystem(self, param['osystem'], purposes) |
453 | return params |
454 | |
455 | |
456 | === modified file 'src/maasserver/config.py' |
457 | --- src/maasserver/config.py 2015-06-19 14:43:57 +0000 |
458 | +++ src/maasserver/config.py 2015-07-03 11:18:28 +0000 |
459 | @@ -25,6 +25,7 @@ |
460 | ConfigurationMeta, |
461 | ConfigurationOption, |
462 | ExtendedURL, |
463 | + is_dev_environment, |
464 | ) |
465 | |
466 | |
467 | @@ -68,13 +69,3 @@ |
468 | return path.join(path.dirname(__file__), "static") |
469 | else: |
470 | return "/usr/share/maas/web/static" |
471 | - |
472 | - |
473 | -def is_dev_environment(): |
474 | - """Is this the development environment, or production?""" |
475 | - try: |
476 | - from maastesting import root # noqa |
477 | - except: |
478 | - return False |
479 | - else: |
480 | - return True |
481 | |
482 | === modified file 'src/maasserver/rpc/tests/test_regionservice.py' |
483 | --- src/maasserver/rpc/tests/test_regionservice.py 2015-06-15 11:14:37 +0000 |
484 | +++ src/maasserver/rpc/tests/test_regionservice.py 2015-07-03 11:18:28 +0000 |
485 | @@ -131,7 +131,7 @@ |
486 | call_responder, |
487 | ) |
488 | from provisioningserver.rpc.testing.doubles import DummyConnection |
489 | -from provisioningserver.testing.config import set_tftp_root |
490 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
491 | from provisioningserver.utils import events |
492 | from provisioningserver.utils.twisted import asynchronous |
493 | from simplejson import dumps |
494 | @@ -362,7 +362,7 @@ |
495 | os.makedirs(os.path.join(tftpdir, *options)) |
496 | |
497 | # Ensure that report_boot_images() uses the above TFTP file tree. |
498 | - self.useFixture(set_tftp_root(tftpdir)) |
499 | + self.useFixture(ClusterConfigurationFixture(tftp_root=tftpdir)) |
500 | |
501 | images = [ |
502 | {"architecture": arch, "subarchitecture": subarch, |
503 | |
504 | === modified file 'src/maasserver/tests/test_config.py' |
505 | --- src/maasserver/tests/test_config.py 2015-06-10 10:19:06 +0000 |
506 | +++ src/maasserver/tests/test_config.py 2015-07-03 11:18:28 +0000 |
507 | @@ -14,11 +14,8 @@ |
508 | __metaclass__ = type |
509 | __all__ = [] |
510 | |
511 | - |
512 | -from maasserver import config |
513 | from maasserver.config import RegionConfiguration |
514 | from maastesting.factory import factory |
515 | -from maastesting.fixtures import ImportErrorFixture |
516 | from maastesting.testcase import MAASTestCase |
517 | |
518 | |
519 | @@ -95,14 +92,3 @@ |
520 | self.assertEqual(example_value, getattr(config, self.option)) |
521 | # It's also stored in the configuration database. |
522 | self.assertEqual({self.option: example_value}, config.store) |
523 | - |
524 | - |
525 | -class TestConfig(MAASTestCase): |
526 | - """Tests for `maasserver.config`.""" |
527 | - |
528 | - def test_is_dev_environment_returns_false(self): |
529 | - self.useFixture(ImportErrorFixture('maastesting', 'root')) |
530 | - self.assertFalse(config.is_dev_environment()) |
531 | - |
532 | - def test_is_dev_environment_returns_true(self): |
533 | - self.assertTrue(config.is_dev_environment()) |
534 | |
535 | === modified file 'src/maasserver/utils/__init__.py' |
536 | --- src/maasserver/utils/__init__.py 2015-06-17 14:15:11 +0000 |
537 | +++ src/maasserver/utils/__init__.py 2015-07-03 11:18:28 +0000 |
538 | @@ -25,21 +25,22 @@ |
539 | 'synchronised', |
540 | ] |
541 | |
542 | -import errno |
543 | from functools import wraps |
544 | -import re |
545 | from urllib import urlencode |
546 | from urlparse import ( |
547 | urljoin, |
548 | urlparse, |
549 | ) |
550 | |
551 | -from django.conf import settings |
552 | from django.core.urlresolvers import reverse |
553 | from maasserver.config import RegionConfiguration |
554 | from maasserver.enum import NODEGROUPINTERFACE_MANAGEMENT |
555 | from maasserver.exceptions import NodeGroupMisconfiguration |
556 | from maasserver.utils.orm import get_one |
557 | +from provisioningserver.config import ( |
558 | + ClusterConfiguration, |
559 | + UUID_NOT_SET, |
560 | +) |
561 | from provisioningserver.utils.text import make_bullet_list |
562 | |
563 | |
564 | @@ -150,22 +151,11 @@ |
565 | |
566 | def get_local_cluster_UUID(): |
567 | """Return the UUID of the local cluster (or None if it cannot be found).""" |
568 | - try: |
569 | - cluster_config = open(settings.LOCAL_CLUSTER_CONFIG).read() |
570 | - match = re.search( |
571 | - "CLUSTER_UUID=(?P<quote>[\"']?)([^\"']+)(?P=quote)", |
572 | - cluster_config) |
573 | - if match is not None: |
574 | - return match.groups()[1] |
575 | - else: |
576 | - return None |
577 | - except IOError as error: |
578 | - if error.errno == errno.ENOENT: |
579 | - # Cluster config file is not present. |
580 | - return None |
581 | - else: |
582 | - # Anything else is an error. |
583 | - raise |
584 | + with ClusterConfiguration.open() as config: |
585 | + if config.cluster_uuid == UUID_NOT_SET: |
586 | + return None |
587 | + else: |
588 | + return config.cluster_uuid |
589 | |
590 | |
591 | def find_nodegroup(request): |
592 | |
593 | === modified file 'src/maasserver/utils/tests/test_utils.py' |
594 | --- src/maasserver/utils/tests/test_utils.py 2015-06-10 10:08:45 +0000 |
595 | +++ src/maasserver/utils/tests/test_utils.py 2015-07-03 11:18:28 +0000 |
596 | @@ -22,7 +22,6 @@ |
597 | urlparse, |
598 | ) |
599 | |
600 | -from django.conf import settings |
601 | from django.core.exceptions import ValidationError |
602 | from django.core.urlresolvers import reverse |
603 | from django.http import HttpRequest |
604 | @@ -49,6 +48,7 @@ |
605 | from maastesting.testcase import MAASTestCase |
606 | from mock import sentinel |
607 | from netaddr import IPAddress |
608 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
609 | |
610 | |
611 | class TestAbsoluteReverse(MAASServerTestCase): |
612 | @@ -231,20 +231,13 @@ |
613 | |
614 | class TestGetLocalClusterUUID(MAASTestCase): |
615 | |
616 | - def test_get_local_cluster_UUID_returns_None_if_no_config_file(self): |
617 | - bogus_file_name = '/tmp/bogus/%s' % factory.make_name('name') |
618 | - self.patch(settings, 'LOCAL_CLUSTER_CONFIG', bogus_file_name) |
619 | - self.assertIsNone(get_local_cluster_UUID()) |
620 | - |
621 | - def test_get_local_cluster_UUID_returns_None_if_parsing_fails(self): |
622 | - file_name = self.make_file(contents="wrong content") |
623 | - self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name) |
624 | + def test_get_local_cluster_UUID_returns_None_if_not_set(self): |
625 | + self.useFixture(ClusterConfigurationFixture()) |
626 | self.assertIsNone(get_local_cluster_UUID()) |
627 | |
628 | def test_get_local_cluster_UUID_returns_cluster_UUID(self): |
629 | uuid = factory.make_UUID() |
630 | - file_name = self.make_file(contents='CLUSTER_UUID="%s"' % uuid) |
631 | - self.patch(settings, 'LOCAL_CLUSTER_CONFIG', file_name) |
632 | + self.useFixture(ClusterConfigurationFixture(cluster_uuid=uuid)) |
633 | self.assertEqual(uuid, get_local_cluster_UUID()) |
634 | |
635 | |
636 | |
637 | === modified file 'src/maastesting/protractor/runner.py' |
638 | --- src/maastesting/protractor/runner.py 2015-03-25 15:33:23 +0000 |
639 | +++ src/maastesting/protractor/runner.py 2015-07-03 11:18:28 +0000 |
640 | @@ -26,15 +26,13 @@ |
641 | from django.core.management import call_command |
642 | from django.db import DEFAULT_DB_ALIAS |
643 | from django.test.runner import setup_databases |
644 | -from fixtures import ( |
645 | - EnvironmentVariableFixture, |
646 | - Fixture, |
647 | -) |
648 | +from fixtures import Fixture |
649 | from maastesting.fixtures import ( |
650 | ChromiumWebDriverFixture, |
651 | DisplayFixture, |
652 | ) |
653 | from postgresfixture import ClusterFixture |
654 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
655 | from testtools.monkey import patch |
656 | from twisted.scripts import twistd |
657 | |
658 | @@ -161,17 +159,12 @@ |
659 | class MAASClusterServiceFixture(Fixture): |
660 | """Starts and stops the MAAS cluster service.""" |
661 | |
662 | - MAAS_URL = "http://0.0.0.0:5253/MAAS/" |
663 | - CLUSTER_UUID = "adfd3977-f251-4f2c-8d61-745dbd690bf2" |
664 | - CONFIG_FILE = "src/maastesting/protractor.yaml" |
665 | - |
666 | def setUp(self): |
667 | """Start the clusterd service.""" |
668 | super(MAASClusterServiceFixture, self).setUp() |
669 | - self.useFixture(EnvironmentVariableFixture( |
670 | - "MAAS_URL", self.MAAS_URL)) |
671 | - self.useFixture(EnvironmentVariableFixture( |
672 | - "CLUSTER_UUID", self.CLUSTER_UUID)) |
673 | + self.useFixture(ClusterConfigurationFixture( |
674 | + cluster_uuid="adfd3977-f251-4f2c-8d61-745dbd690bf2", |
675 | + maas_url="http://0.0.0.0:5253/MAAS/")) |
676 | |
677 | # Fork the process to have clusterd run in its own process. |
678 | twistd_pid = os.fork() |
679 | @@ -180,13 +173,7 @@ |
680 | redirect_to_devnull() |
681 | |
682 | # Add command line options to start twistd. |
683 | - sys.argv[1:] = [ |
684 | - "--nodaemon", |
685 | - "--pidfile", "", |
686 | - "maas-clusterd", |
687 | - "--config-file", |
688 | - self.CONFIG_FILE, |
689 | - ] |
690 | + sys.argv[1:] = ["--nodaemon", "--pidfile", "", "maas-clusterd"] |
691 | |
692 | # Start twistd. |
693 | try: |
694 | |
695 | === modified file 'src/provisioningserver/__main__.py' |
696 | --- src/provisioningserver/__main__.py 2015-06-26 09:48:29 +0000 |
697 | +++ src/provisioningserver/__main__.py 2015-07-03 11:18:28 +0000 |
698 | @@ -18,7 +18,6 @@ |
699 | import provisioningserver.boot.install_bootloader |
700 | import provisioningserver.boot.install_grub |
701 | import provisioningserver.cluster_config_command |
702 | -import provisioningserver.configure_maas_url |
703 | import provisioningserver.dhcp.writer |
704 | import provisioningserver.upgrade_cluster |
705 | from provisioningserver.utils.script import ( |
706 | @@ -30,7 +29,6 @@ |
707 | script_commands = { |
708 | 'atomic-write': AtomicWriteScript, |
709 | 'check-for-shared-secret': security.CheckForSharedSecretScript, |
710 | - 'configure-maas-url': provisioningserver.configure_maas_url, |
711 | 'generate-dhcp-config': provisioningserver.dhcp.writer, |
712 | 'install-shared-secret': security.InstallSharedSecretScript, |
713 | 'install-uefi-config': provisioningserver.boot.install_grub, |
714 | |
715 | === modified file 'src/provisioningserver/boot/install_grub.py' |
716 | --- src/provisioningserver/boot/install_grub.py 2015-05-29 16:47:37 +0000 |
717 | +++ src/provisioningserver/boot/install_grub.py 2015-07-03 11:18:28 +0000 |
718 | @@ -20,7 +20,7 @@ |
719 | import os.path |
720 | |
721 | from provisioningserver.boot.install_bootloader import make_destination |
722 | -from provisioningserver.config import Config |
723 | +from provisioningserver.config import ClusterConfiguration |
724 | from provisioningserver.utils.fs import write_text_file |
725 | |
726 | |
727 | @@ -44,7 +44,7 @@ |
728 | """Install a GRUB2 pre-boot loader config into the TFTP |
729 | directory structure. |
730 | """ |
731 | - config = Config.load(args.config_file) |
732 | - grubroot = os.path.join(config["tftp"]["resource_root"], 'grub') |
733 | - destination_path = make_destination(grubroot) |
734 | - write_text_file(os.path.join(destination_path, 'grub.cfg'), CONFIG_FILE) |
735 | + with ClusterConfiguration.open() as config: |
736 | + destination_path = make_destination(config.grub_root) |
737 | + destination_file = os.path.join(destination_path, 'grub.cfg') |
738 | + write_text_file(destination_file, CONFIG_FILE) |
739 | |
740 | === modified file 'src/provisioningserver/boot/tests/test_boot.py' |
741 | --- src/provisioningserver/boot/tests/test_boot.py 2015-06-11 21:21:22 +0000 |
742 | +++ src/provisioningserver/boot/tests/test_boot.py 2015-07-03 11:18:28 +0000 |
743 | @@ -118,9 +118,9 @@ |
744 | |
745 | def make_fake_templates_dir(self, method): |
746 | """Set up a fake templates dir, and return its path.""" |
747 | - fake_etc_maas = self.make_dir() |
748 | - self.useFixture(EnvironmentVariableFixture( |
749 | - 'MAAS_CONFIG_DIR', fake_etc_maas)) |
750 | + fake_root = self.make_dir() |
751 | + fake_etc_maas = os.path.join(fake_root, "etc", "maas") |
752 | + self.useFixture(EnvironmentVariableFixture('MAAS_ROOT', fake_root)) |
753 | fake_templates = os.path.join( |
754 | fake_etc_maas, 'templates/%s' % method.template_subdir) |
755 | os.makedirs(fake_templates) |
756 | |
757 | === modified file 'src/provisioningserver/boot/tests/test_install_grub.py' |
758 | --- src/provisioningserver/boot/tests/test_install_grub.py 2015-04-09 16:21:27 +0000 |
759 | +++ src/provisioningserver/boot/tests/test_install_grub.py 2015-07-03 11:18:28 +0000 |
760 | @@ -20,7 +20,7 @@ |
761 | from maastesting.testcase import MAASTestCase |
762 | import provisioningserver.boot.install_grub |
763 | from provisioningserver.boot.tftppath import locate_tftp_path |
764 | -from provisioningserver.testing.config import set_tftp_root |
765 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
766 | from provisioningserver.utils.script import MainScript |
767 | from testtools.matchers import FileExists |
768 | |
769 | @@ -29,13 +29,12 @@ |
770 | |
771 | def test_integration(self): |
772 | tftproot = self.make_dir() |
773 | - config_fixture = self.useFixture(set_tftp_root(tftproot)) |
774 | + self.useFixture(ClusterConfigurationFixture(tftp_root=tftproot)) |
775 | |
776 | action = factory.make_name("action") |
777 | script = MainScript(action) |
778 | script.register(action, provisioningserver.boot.install_grub) |
779 | - script.execute( |
780 | - ("--config-file", config_fixture.filename, action)) |
781 | + script.execute((action,)) |
782 | |
783 | config_filename = os.path.join('grub', 'grub.cfg') |
784 | self.assertThat( |
785 | |
786 | === modified file 'src/provisioningserver/boot/tests/test_powernv.py' |
787 | --- src/provisioningserver/boot/tests/test_powernv.py 2015-05-07 18:14:38 +0000 |
788 | +++ src/provisioningserver/boot/tests/test_powernv.py 2015-07-03 11:18:28 +0000 |
789 | @@ -19,6 +19,7 @@ |
790 | |
791 | from maastesting.factory import factory |
792 | from maastesting.testcase import MAASTestCase |
793 | +from mock import sentinel |
794 | from provisioningserver.boot import ( |
795 | BytesReader, |
796 | powernv as powernv_module, |
797 | @@ -32,7 +33,7 @@ |
798 | from provisioningserver.boot.tests.test_pxe import parse_pxe_config |
799 | from provisioningserver.boot.tftppath import compose_image_path |
800 | from provisioningserver.pserv_services.tftp import TFTPBackend |
801 | -from provisioningserver.testing.config import set_tftp_root |
802 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
803 | from provisioningserver.tests.test_kernel_opts import make_kernel_parameters |
804 | from testtools.matchers import ( |
805 | IsInstance, |
806 | @@ -78,7 +79,7 @@ |
807 | def make_tftp_root(self): |
808 | """Set, and return, a temporary TFTP root directory.""" |
809 | tftproot = self.make_dir() |
810 | - self.useFixture(set_tftp_root(tftproot)) |
811 | + self.useFixture(ClusterConfigurationFixture(tftp_root=tftproot)) |
812 | return tftproot |
813 | |
814 | def test_compose_config_path_follows_maas_pxe_directory_layout(self): |
815 | @@ -255,7 +256,8 @@ |
816 | data = factory.make_string().encode("ascii") |
817 | temp_file = self.make_file(name="example", contents=data) |
818 | temp_dir = os.path.dirname(temp_file) |
819 | - backend = TFTPBackend(temp_dir, "http://nowhere.example.com/") |
820 | + backend = TFTPBackend( |
821 | + temp_dir, "http://nowhere.example.com/", sentinel.uuid) |
822 | method = PowerNVBootMethod() |
823 | options = { |
824 | 'backend': backend, |
825 | @@ -274,7 +276,8 @@ |
826 | temp_subdir = os.path.join(temp_dir, 'ppc64el') |
827 | os.mkdir(temp_subdir) |
828 | factory.make_file(temp_subdir, "example", data) |
829 | - backend = TFTPBackend(temp_dir, "http://nowhere.example.com/") |
830 | + backend = TFTPBackend( |
831 | + temp_dir, "http://nowhere.example.com/", sentinel.uuid) |
832 | method = PowerNVBootMethod() |
833 | options = { |
834 | 'backend': backend, |
835 | |
836 | === modified file 'src/provisioningserver/boot/tests/test_pxe.py' |
837 | --- src/provisioningserver/boot/tests/test_pxe.py 2015-05-07 18:14:38 +0000 |
838 | +++ src/provisioningserver/boot/tests/test_pxe.py 2015-07-03 11:18:28 +0000 |
839 | @@ -34,7 +34,7 @@ |
840 | re_config_file, |
841 | ) |
842 | from provisioningserver.boot.tftppath import compose_image_path |
843 | -from provisioningserver.testing.config import set_tftp_root |
844 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
845 | from provisioningserver.tests.test_kernel_opts import make_kernel_parameters |
846 | from testtools.matchers import ( |
847 | Contains, |
848 | @@ -72,7 +72,7 @@ |
849 | def make_tftp_root(self): |
850 | """Set, and return, a temporary TFTP root directory.""" |
851 | tftproot = self.make_dir() |
852 | - self.useFixture(set_tftp_root(tftproot)) |
853 | + self.useFixture(ClusterConfigurationFixture(tftp_root=tftproot)) |
854 | return tftproot |
855 | |
856 | def make_dummy_bootloader_sources(self, destination, loader_names): |
857 | |
858 | === modified file 'src/provisioningserver/boot/tests/test_tftppath.py' |
859 | --- src/provisioningserver/boot/tests/test_tftppath.py 2015-05-29 16:47:37 +0000 |
860 | +++ src/provisioningserver/boot/tests/test_tftppath.py 2015-07-03 11:18:28 +0000 |
861 | @@ -18,10 +18,8 @@ |
862 | import os.path |
863 | |
864 | from maastesting.factory import factory |
865 | -from maastesting.matchers import MockCalledOnceWith |
866 | from maastesting.testcase import MAASTestCase |
867 | from mock import Mock |
868 | -from provisioningserver import config |
869 | from provisioningserver.boot import tftppath |
870 | from provisioningserver.boot.tftppath import ( |
871 | compose_image_path, |
872 | @@ -48,7 +46,7 @@ |
873 | make_boot_image_storage_params, |
874 | make_image, |
875 | ) |
876 | -from provisioningserver.testing.config import set_tftp_root |
877 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
878 | from provisioningserver.testing.os import make_osystem |
879 | from testtools.matchers import ( |
880 | Not, |
881 | @@ -62,7 +60,7 @@ |
882 | def setUp(self): |
883 | super(TestTFTPPath, self).setUp() |
884 | self.tftproot = self.make_dir() |
885 | - self.useFixture(set_tftp_root(self.tftproot)) |
886 | + self.useFixture(ClusterConfigurationFixture(tftp_root=self.tftproot)) |
887 | |
888 | def make_image_dir(self, image_params, tftproot): |
889 | """Fake a boot image matching `image_params` under `tftproot`.""" |
890 | @@ -101,20 +99,12 @@ |
891 | os.path.join(self.tftproot, "maas.meta")) |
892 | self.assertIsNone(observed) |
893 | |
894 | - def test_maas_meta_last_modified_defaults_tftproot(self): |
895 | - path = factory.make_file(self.tftproot, name="maas.meta") |
896 | - maas_meta_file_path = self.patch(tftppath, 'maas_meta_file_path') |
897 | - maas_meta_file_path.return_value = path |
898 | - maas_meta_last_modified() |
899 | - expected_path = os.path.join( |
900 | - config.BOOT_RESOURCES_STORAGE, 'current') |
901 | - self.assertThat(maas_meta_file_path, MockCalledOnceWith(expected_path)) |
902 | - |
903 | def test_maas_meta_last_modified_reraises_non_ENOENT(self): |
904 | + path = factory.make_file(self.tftproot, name="maas.meta") |
905 | oserror = OSError() |
906 | oserror.errno = errno.E2BIG |
907 | self.patch(os.path, 'getmtime').side_effect = oserror |
908 | - self.assertRaises(OSError, maas_meta_last_modified) |
909 | + self.assertRaises(OSError, maas_meta_last_modified, path) |
910 | |
911 | def test_compose_image_path_follows_storage_directory_layout(self): |
912 | osystem = factory.make_name('osystem') |
913 | |
914 | === modified file 'src/provisioningserver/boot/tests/test_windows.py' |
915 | --- src/provisioningserver/boot/tests/test_windows.py 2015-05-29 16:47:37 +0000 |
916 | +++ src/provisioningserver/boot/tests/test_windows.py 2015-07-03 11:18:28 +0000 |
917 | @@ -41,9 +41,9 @@ |
918 | Bcd, |
919 | WindowsPXEBootMethod, |
920 | ) |
921 | -from provisioningserver.config import Config |
922 | from provisioningserver.rpc.exceptions import NoSuchNode |
923 | from provisioningserver.rpc.region import RequestNodeInfoByMACAddress |
924 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
925 | from provisioningserver.tests.test_kernel_opts import make_kernel_parameters |
926 | from testtools.deferredruntest import extract_result |
927 | from testtools.matchers import Is |
928 | @@ -193,7 +193,6 @@ |
929 | run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) |
930 | |
931 | def setUp(self): |
932 | - self.patch(Config, 'load_from_cache') |
933 | self.patch(windows_module, 'get_hivex_module') |
934 | super(TestWindowsPXEBootMethod, self).setUp() |
935 | |
936 | @@ -372,13 +371,8 @@ |
937 | BootMethodError, method.compose_bcd, kernel_params, local_host) |
938 | |
939 | def test_get_resouce_path(self): |
940 | - fake_tftproot = factory.make_name('tftproot') |
941 | - mock_config = self.patch(windows_module, 'Config') |
942 | - mock_config.load_from_cache.return_value = { |
943 | - 'tftp': { |
944 | - 'resource_root': fake_tftproot, |
945 | - }, |
946 | - } |
947 | + fake_tftproot = self.make_dir() |
948 | + self.useFixture(ClusterConfigurationFixture(tftp_root=fake_tftproot)) |
949 | method = WindowsPXEBootMethod() |
950 | fake_path = factory.make_name('path') |
951 | fake_kernelparams = make_kernel_parameters() |
952 | |
953 | === modified file 'src/provisioningserver/boot/tftppath.py' |
954 | --- src/provisioningserver/boot/tftppath.py 2015-05-29 16:47:37 +0000 |
955 | +++ src/provisioningserver/boot/tftppath.py 2015-07-03 11:18:28 +0000 |
956 | @@ -23,7 +23,6 @@ |
957 | from itertools import chain |
958 | import os.path |
959 | |
960 | -from provisioningserver import config |
961 | from provisioningserver.drivers.osystem import ( |
962 | BOOT_IMAGE_PURPOSE, |
963 | OperatingSystemRegistry, |
964 | @@ -199,17 +198,14 @@ |
965 | return os.path.join(tftproot, 'maas.meta') |
966 | |
967 | |
968 | -def maas_meta_last_modified(tftproot=None): |
969 | +def maas_meta_last_modified(tftproot): |
970 | """Return time of last modification of maas.meta. |
971 | |
972 | The time is the same as returned from getmtime() (seconds since epoch), |
973 | or None if the file doesn't exist. |
974 | |
975 | - :param tftproot: Optional tftp root dir, defaults to |
976 | - provisioningserver.config.BOOT_RESOURCES_STORAGE |
977 | + :param tftproot: The TFTP root path. |
978 | """ |
979 | - if tftproot is None: |
980 | - tftproot = os.path.join(config.BOOT_RESOURCES_STORAGE, 'current') |
981 | meta_file = maas_meta_file_path(tftproot) |
982 | try: |
983 | return os.path.getmtime(meta_file) |
984 | |
985 | === modified file 'src/provisioningserver/boot/windows.py' |
986 | --- src/provisioningserver/boot/windows.py 2015-05-29 16:47:37 +0000 |
987 | +++ src/provisioningserver/boot/windows.py 2015-07-03 11:18:28 +0000 |
988 | @@ -28,7 +28,7 @@ |
989 | BytesReader, |
990 | get_remote_mac, |
991 | ) |
992 | -from provisioningserver.config import Config |
993 | +from provisioningserver.config import ClusterConfiguration |
994 | from provisioningserver.logger.log import get_maas_logger |
995 | from provisioningserver.rpc import getRegionClient |
996 | from provisioningserver.rpc.exceptions import NoSuchNode |
997 | @@ -300,7 +300,8 @@ |
998 | |
999 | def get_resource_path(self, kernel_params, path): |
1000 | """Gets the resource path from the kernel param.""" |
1001 | - resources = Config.load_from_cache()['tftp']['resource_root'] |
1002 | + with ClusterConfiguration.open() as config: |
1003 | + resources = config.tftp_root |
1004 | return os.path.join( |
1005 | resources, 'windows', kernel_params.arch, kernel_params.subarch, |
1006 | kernel_params.release, kernel_params.label, path) |
1007 | |
1008 | === removed file 'src/provisioningserver/cluster_config.py' |
1009 | --- src/provisioningserver/cluster_config.py 2015-04-09 16:21:27 +0000 |
1010 | +++ src/provisioningserver/cluster_config.py 1970-01-01 00:00:00 +0000 |
1011 | @@ -1,88 +0,0 @@ |
1012 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
1013 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1014 | - |
1015 | -"""Accessors for cluster configuration as set in `maas_cluster.conf`.""" |
1016 | - |
1017 | -from __future__ import ( |
1018 | - absolute_import, |
1019 | - print_function, |
1020 | - unicode_literals, |
1021 | -) |
1022 | - |
1023 | -str = None |
1024 | - |
1025 | -__metaclass__ = type |
1026 | -__all__ = [ |
1027 | - 'get_cluster_uuid', |
1028 | - 'get_maas_url', |
1029 | - "get_cluster_variable", |
1030 | - "get_config_cluster_variable", |
1031 | - "set_config_cluster_variable", ] |
1032 | - |
1033 | -from os import environ |
1034 | - |
1035 | -from provisioningserver.config import ClusterConfiguration |
1036 | - |
1037 | -# List of configuration keys |
1038 | - |
1039 | - |
1040 | -class CLUSTER_CONFIG: |
1041 | - DB_cluster_uuid = 'cluster_uuid' |
1042 | - DB_maas_url = 'maas_url' |
1043 | - DB_tftp_resource_root = "tftp_root" |
1044 | - DB_boot_resources_storage = "tftp_root" |
1045 | - DB_tftpport = 'tftp_port' |
1046 | - |
1047 | - |
1048 | -# New get function for ClusterConfiguration backend |
1049 | -def get_config_cluster_variable(var): |
1050 | - """Obtain the given environment variable from clusterd config""" |
1051 | - with ClusterConfiguration.open() as config: |
1052 | - return getattr(config, var) |
1053 | - |
1054 | - |
1055 | -# Set function for ClusterConfiguration backend |
1056 | -def set_config_cluster_variable(var, value): |
1057 | - """Set the given environment variable in clusterd config""" |
1058 | - with ClusterConfiguration.open() as config: |
1059 | - setattr(config, var, value) |
1060 | - |
1061 | - |
1062 | -def get_tftp_generator(): |
1063 | - """Return the `tftp_generator` setting, which is |
1064 | - <maas url>/api/1.0/pxeconfig/ |
1065 | - """ |
1066 | - return '/'.join(get_cluster_variable(CLUSTER_CONFIG.DB_maas_url), |
1067 | - 'api', '1.0', 'pxeconfig') |
1068 | - |
1069 | - |
1070 | -# Old get function for config file (via env variables) backend, |
1071 | -# to be removed in follow-up branch. This branch is a prerequisite branch for |
1072 | -# a follow-up branch, where this function will be removed. Both |
1073 | -# the new and old versions of this function have been included |
1074 | -# in this intermediate branch at the request of the package |
1075 | -# maintainers. |
1076 | -def get_cluster_variable(var): |
1077 | - """Obtain the given environment variable from maas_cluster.conf. |
1078 | - |
1079 | - If the variable is not set, it probably means that whatever script |
1080 | - started the current process neglected to run maas_cluster.conf. |
1081 | - In that case, fail helpfully but utterly. |
1082 | - """ |
1083 | - value = environ.get(var) |
1084 | - if value is None: |
1085 | - raise AssertionError( |
1086 | - "%s is not set. This probably means that the script which " |
1087 | - "started this program failed to source maas_cluster.conf." |
1088 | - % var) |
1089 | - return value |
1090 | - |
1091 | - |
1092 | -def get_cluster_uuid(): |
1093 | - """Return the `CLUSTER_UUID` setting.""" |
1094 | - return get_cluster_variable('CLUSTER_UUID') |
1095 | - |
1096 | - |
1097 | -def get_maas_url(): |
1098 | - """Return the `MAAS_URL` setting.""" |
1099 | - return get_cluster_variable('MAAS_URL') |
1100 | |
1101 | === modified file 'src/provisioningserver/cluster_config_command.py' |
1102 | --- src/provisioningserver/cluster_config_command.py 2015-04-02 19:30:23 +0000 |
1103 | +++ src/provisioningserver/cluster_config_command.py 2015-07-03 11:18:28 +0000 |
1104 | @@ -21,12 +21,10 @@ |
1105 | |
1106 | from uuid import uuid4 |
1107 | |
1108 | -from provisioningserver.cluster_config import ( |
1109 | - CLUSTER_CONFIG, |
1110 | - get_config_cluster_variable, |
1111 | - set_config_cluster_variable, |
1112 | +from provisioningserver.config import ( |
1113 | + ClusterConfiguration, |
1114 | + UUID_NOT_SET, |
1115 | ) |
1116 | -from provisioningserver.config import UUID_NOT_SET |
1117 | |
1118 | |
1119 | def update_maas_cluster_conf( |
1120 | @@ -44,28 +42,20 @@ |
1121 | and init be passed at the same time, as these are mutually exclusive |
1122 | parameters. |
1123 | """ |
1124 | - |
1125 | - if url is not None: |
1126 | - set_config_cluster_variable(CLUSTER_CONFIG.DB_maas_url, url) |
1127 | - |
1128 | - if uuid is not None: |
1129 | - set_config_cluster_variable( |
1130 | - CLUSTER_CONFIG.DB_cluster_uuid, uuid) |
1131 | - |
1132 | - if init: |
1133 | - cur_uuid = get_config_cluster_variable(CLUSTER_CONFIG.DB_cluster_uuid) |
1134 | - if cur_uuid == UUID_NOT_SET: |
1135 | - set_config_cluster_variable( |
1136 | - CLUSTER_CONFIG.DB_cluster_uuid, |
1137 | - unicode(uuid4())) |
1138 | - |
1139 | - if tftp_port is not None: |
1140 | - set_config_cluster_variable(CLUSTER_CONFIG.DB_tftpport, tftp_port) |
1141 | - |
1142 | - if tftp_root is not None: |
1143 | - set_config_cluster_variable( |
1144 | - CLUSTER_CONFIG.DB_tftp_resource_root, |
1145 | - tftp_root) |
1146 | + with ClusterConfiguration.open() as config: |
1147 | + if url is not None: |
1148 | + config.maas_url = url |
1149 | + if uuid is not None: |
1150 | + config.cluster_uuid = uuid |
1151 | + if init: |
1152 | + cur_uuid = config.cluster_uuid |
1153 | + if cur_uuid == UUID_NOT_SET: |
1154 | + config.cluster_uuid = unicode(uuid4()) |
1155 | + if tftp_port is not None: |
1156 | + config.tftp_port = tftp_port |
1157 | + if tftp_root is not None: |
1158 | + config.tftp_root = tftp_root |
1159 | + |
1160 | |
1161 | all_arguments = ( |
1162 | '--region-url', |
1163 | @@ -101,11 +91,7 @@ |
1164 | |
1165 | |
1166 | def run(args): |
1167 | - """Update MAAS_URL setting in configuration files. |
1168 | - |
1169 | - For use by the MAAS packaging scripts. Updates configuration data |
1170 | - store to reflect provided settings. |
1171 | - """ |
1172 | + """Update configuration settings.""" |
1173 | params = vars(args).copy() |
1174 | url = params.pop('region_url', None) |
1175 | uuid = params.pop('uuid', None) |
1176 | |
1177 | === modified file 'src/provisioningserver/config.py' |
1178 | --- src/provisioningserver/config.py 2015-06-24 13:27:05 +0000 |
1179 | +++ src/provisioningserver/config.py 2015-07-03 11:18:28 +0000 |
1180 | @@ -1,45 +1,105 @@ |
1181 | # Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
1182 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1183 | |
1184 | -"""MAAS Provisioning Configuration. |
1185 | - |
1186 | -Configuration for most elements of a Cluster Controller can be obtained |
1187 | -through this module's `Config` validator class. At the time of writing the |
1188 | -exceptions are the `CLUSTER_UUID` and `MAAS_URL` environment variables (see |
1189 | -`provisioningserver.cluster_config`). |
1190 | - |
1191 | -It's pretty simple. Typical usage is:: |
1192 | - |
1193 | - >>> config = Config.load_from_cache() |
1194 | +"""Configuration for the MAAS cluster. |
1195 | + |
1196 | +This module also contains the common library code that's used for |
1197 | +configuration in both the region and the cluster. |
1198 | + |
1199 | +There are two styles of configuration object, one older and deprecated, and |
1200 | +one new. |
1201 | + |
1202 | + |
1203 | +The Old Way |
1204 | +----------- |
1205 | + |
1206 | +Configuration can be obtained through subclassing this module's `ConfigBase` |
1207 | +validator class. It's pretty simple. Typical usage is:: |
1208 | + |
1209 | + >>> config = MyConfig.load_from_cache() |
1210 | {...} |
1211 | |
1212 | -This reads in a configuration file from `Config.DEFAULT_FILENAME` (see a note |
1213 | +This reads in a configuration file from `MyConfig.DEFAULT_FILENAME` (see a note |
1214 | about that later). The configuration file is parsed as YAML, and a plain `dict` |
1215 | is returned with configuration nested within it. The configuration is validated |
1216 | at load time using `formencode`. The policy for validation is laid out in this |
1217 | module; see the various `formencode.Schema` subclasses. |
1218 | |
1219 | -All configuration is optional, and a sensible default is provided in every |
1220 | -instance. When adding or changing settings bear this policy in mind, and also |
1221 | -that the defaults should be geared towards a system in production, and not a |
1222 | -development environment. The defaults can be obtained by calling |
1223 | -`Config.get_defaults()`. |
1224 | - |
1225 | -An alternative to `Config.load_from_cache()` is `Config.load()`, which loads |
1226 | -and validates a configuration file while bypassing the cache. See `Config` for |
1227 | -other useful functions. |
1228 | - |
1229 | -`Config.DEFAULT_FILENAME` is a class property, so does not need to be |
1230 | -referenced via an instance of `Config`. It refers to the |
1231 | -``MAAS_PROVISIONING_SETTINGS`` environment variable in the first instance, but |
1232 | -has a sensible default too. You can write to this property and it will update |
1233 | -the environment so that child processes will also use the same configuration |
1234 | -filename. To revert to the default - i.e. erase the environment variable - you |
1235 | -can `del Config.DEFAULT_FILENAME`. |
1236 | - |
1237 | -When testing, see `provisioningserver.testing.config.ConfigFixture` to |
1238 | +Configuration should be optional, and a sensible default should be provided in |
1239 | +every instance. The defaults can be obtained from `MyConfig.get_defaults()`. |
1240 | + |
1241 | +An alternative to `MyConfig.load_from_cache()` is `MyConfig.load()`, which |
1242 | +loads and validates a configuration file while bypassing the cache. See |
1243 | +`ConfigBase` for other useful functions. |
1244 | + |
1245 | +`MyConfig.DEFAULT_FILENAME` is a class property, so does not need to be |
1246 | +referenced via an instance of `MyConfig`. It refers to an environment variable |
1247 | +named by `MyConfig.envvar` in the first instance, but should have a sensible |
1248 | +default too. You can write to this property and it will update the environment |
1249 | +so that child processes will also use the same configuration filename. To |
1250 | +revert to the default - i.e. erase the environment variable - you can `del |
1251 | +MyConfig.DEFAULT_FILENAME`. |
1252 | + |
1253 | +When testing, see `provisioningserver.testing.config.ConfigFixtureBase` to |
1254 | temporarily use a different configuration. |
1255 | |
1256 | + |
1257 | +The New Way |
1258 | +----------- |
1259 | + |
1260 | +There are two subclasses of this module's `Configuration` class, one for the |
1261 | +region (`RegionConfiguration`) and for the cluster (`ClusterConfiguration`). |
1262 | +Each defines a set of attributes which are the configuration variables: |
1263 | + |
1264 | +* If an attribute is declared as a `ConfigurationOption` then it's a |
1265 | + read-write configuration option, and should have a sensible default if |
1266 | + possible. |
1267 | + |
1268 | +* If an attribute is declared as a standard Python `property` then it's a |
1269 | + read-only configuration option. |
1270 | + |
1271 | +A metaclass is also defined, which must inherit from `ConfigurationMeta`, to |
1272 | +define a few other important options: |
1273 | + |
1274 | +* ``default`` is the default filename for the configuration database. |
1275 | + |
1276 | +* ``envvar`` is the name of an environment variable that, if defined, provides |
1277 | + the filename for the configuration database. This is used in preference to |
1278 | + ``default``. |
1279 | + |
1280 | +* ``backend`` is a factory that provides the storage mechanism. Currently you |
1281 | + can choose from `ConfigurationFile` or `ConfigurationDatabase`. The latter |
1282 | + is strongly recommended in preference to the former. |
1283 | + |
1284 | +An example:: |
1285 | + |
1286 | + class MyConfiguration(Configuration): |
1287 | + |
1288 | + class __metaclass__(ConfigurationMeta): |
1289 | + envvar = "CONFIG_FILE" |
1290 | + default = "/etc/myapp.conf" |
1291 | + backend = ConfigurationDatabase |
1292 | + |
1293 | + images_dir = ConfigurationOption( |
1294 | + "images_dir", "The directory in which to store images.", |
1295 | + Directory(if_missing="/var/lib/myapp/images")) |
1296 | + |
1297 | + @property |
1298 | + def png_dir(self): |
1299 | + "The directory in which to store PNGs." |
1300 | + return os.path.join(self.images_dir, "png") |
1301 | + |
1302 | + @property |
1303 | + def gif_dir(self): |
1304 | + "The directory in which to store GIFs." |
1305 | + return os.path.join(self.images_dir, "gif") |
1306 | + |
1307 | +It can be used like so:: |
1308 | + |
1309 | + with MyConfiguration.open() as config: |
1310 | + config.images_dir = "/var/www/example.com/images" |
1311 | + print(config.png_dir, config.gif_dir) |
1312 | + |
1313 | """ |
1314 | |
1315 | from __future__ import ( |
1316 | @@ -52,11 +112,12 @@ |
1317 | |
1318 | __metaclass__ = type |
1319 | __all__ = [ |
1320 | - "BOOT_RESOURCES_STORAGE", |
1321 | "BootSources", |
1322 | - "Config", |
1323 | + "ClusterConfiguration", |
1324 | "ConfigBase", |
1325 | "ConfigMeta", |
1326 | + "is_dev_environment", |
1327 | + "UUID_NOT_SET", |
1328 | ] |
1329 | |
1330 | from contextlib import ( |
1331 | @@ -64,7 +125,6 @@ |
1332 | contextmanager, |
1333 | ) |
1334 | from copy import deepcopy |
1335 | -from getpass import getuser |
1336 | import json |
1337 | import os |
1338 | from os import environ |
1339 | @@ -82,11 +142,9 @@ |
1340 | from formencode.api import NoDefault |
1341 | from formencode.declarative import DeclarativeMeta |
1342 | from formencode.validators import ( |
1343 | - Int, |
1344 | Invalid, |
1345 | is_validator, |
1346 | Number, |
1347 | - RequireIfPresent, |
1348 | Set, |
1349 | String, |
1350 | UnicodeString, |
1351 | @@ -100,11 +158,6 @@ |
1352 | ) |
1353 | import yaml |
1354 | |
1355 | -# Path to the directory on the cluster controller where boot resources are |
1356 | -# stored. This used to be configurable in bootresources.yaml, and may become |
1357 | -# configurable again in the future. |
1358 | -BOOT_RESOURCES_STORAGE = '/var/lib/maas/boot-resources/' |
1359 | - |
1360 | # Default result for cluster UUID if not set |
1361 | UUID_NOT_SET = '** UUID NOT SET **' |
1362 | |
1363 | @@ -170,93 +223,6 @@ |
1364 | ''', re.I | re.VERBOSE) |
1365 | |
1366 | |
1367 | -class ConfigOops(Schema): |
1368 | - """Configuration validator for OOPS options. |
1369 | - |
1370 | - Deprecated: MAAS no longer records OOPS reports. This remains here to |
1371 | - avoid validation failures when using old versions of the cluster's |
1372 | - configuration file. |
1373 | - """ |
1374 | - |
1375 | - if_key_missing = None |
1376 | - |
1377 | - directory = String(if_missing=b"") |
1378 | - reporter = String(if_missing=b"") |
1379 | - |
1380 | - chained_validators = ( |
1381 | - RequireIfPresent("reporter", present="directory"), |
1382 | - ) |
1383 | - |
1384 | - |
1385 | -class ConfigBroker(Schema): |
1386 | - """Configuration validator for message broker options. |
1387 | - |
1388 | - Deprecated: MAAS no longer uses a message broker. This remains here to |
1389 | - avoid validation failures when using old versions of the cluster's |
1390 | - configuration file. |
1391 | - """ |
1392 | - |
1393 | - if_key_missing = None |
1394 | - |
1395 | - host = String(if_missing=b"localhost") |
1396 | - port = Int(min=1, max=65535, if_missing=5673) |
1397 | - username = String(if_missing=getuser()) |
1398 | - password = String(if_missing=b"test") |
1399 | - vhost = String(if_missing="/") |
1400 | - |
1401 | - |
1402 | -class ConfigTFTP(Schema): |
1403 | - """Configuration validator for the TFTP service.""" |
1404 | - |
1405 | - if_key_missing = None |
1406 | - |
1407 | - # Obsolete: old TFTP root directory. This is retained for the purpose of |
1408 | - # deriving new, Simplestreams-based import configuration from previously |
1409 | - # imported boot images. |
1410 | - # The last time this is needed is for upgrading an older cluster |
1411 | - # controller to the Ubuntu 14.04 version of MAAS. After installation of |
1412 | - # the 14.04 version, this setting is never used. |
1413 | - root = String(if_missing="/var/lib/maas/tftp") |
1414 | - |
1415 | - # TFTP root directory, managed by the Simplestreams-based import script. |
1416 | - # The import script maintains "current" as a symlink pointing to the most |
1417 | - # recent images. |
1418 | - # XXX jtv 2014-05-22: Redundant with BOOT_RESOURCES_STORAGE. |
1419 | - resource_root = String( |
1420 | - if_missing=os.path.join(BOOT_RESOURCES_STORAGE, 'current/')) |
1421 | - |
1422 | - port = Int(min=1, max=65535, if_missing=69) |
1423 | - generator = String(if_missing=b"http://localhost/MAAS/api/1.0/pxeconfig/") |
1424 | - |
1425 | - |
1426 | -class ConfigLegacyEphemeral(Schema): |
1427 | - """Legacy `ephemeral` section in `pserv.yaml` prior to MAAS 1.5. |
1428 | - |
1429 | - This has been superseded by boot sources. |
1430 | - It is still accepted in `pserv.yaml`, but not used. |
1431 | - """ |
1432 | - if_key_missing = None |
1433 | - images_directory = String(if_missing=None) |
1434 | - releases = Set(if_missing=None) |
1435 | - |
1436 | - |
1437 | -class ConfigLegacyBoot(Schema): |
1438 | - """Legacy `boot` section in `pserv.yaml` prior to MAAS 1.5. |
1439 | - |
1440 | - This has been superseded by boot sources. |
1441 | - It is still accepted in `pserv.yaml`, but not used. |
1442 | - """ |
1443 | - if_key_missing = None |
1444 | - architectures = Set(if_missing=None) |
1445 | - ephemeral = ConfigLegacyEphemeral |
1446 | - |
1447 | - |
1448 | -class ConfigRPC(Schema): |
1449 | - """Configuration validator for the RPC service.""" |
1450 | - |
1451 | - if_key_missing = None |
1452 | - |
1453 | - |
1454 | class BootSourceSelection(Schema): |
1455 | """Configuration validator for boot source selection configuration.""" |
1456 | |
1457 | @@ -403,23 +369,6 @@ |
1458 | "`cls.envvar` in the environment.")) |
1459 | |
1460 | |
1461 | -class Config(ConfigBase, Schema): |
1462 | - """Configuration for the provisioning server.""" |
1463 | - |
1464 | - class __metaclass__(ConfigMeta): |
1465 | - envvar = "MAAS_PROVISIONING_SETTINGS" |
1466 | - default = "pserv.yaml" |
1467 | - |
1468 | - if_key_missing = None |
1469 | - |
1470 | - logfile = String(if_empty=b"pserv.log", if_missing=b"pserv.log") |
1471 | - oops = ConfigOops |
1472 | - broker = ConfigBroker |
1473 | - tftp = ConfigTFTP |
1474 | - rpc = ConfigRPC |
1475 | - boot = ConfigLegacyBoot |
1476 | - |
1477 | - |
1478 | class BootSources(ConfigBase, ForEach): |
1479 | """Configuration for boot sources.""" |
1480 | |
1481 | @@ -760,20 +709,42 @@ |
1482 | default = "/etc/maas/clusterd.conf" |
1483 | backend = ConfigurationFile |
1484 | |
1485 | - # MAAS URL options |
1486 | maas_url = ConfigurationOption( |
1487 | - "maas_url", "The HTTP URL for the MAAS region.", |
1488 | - ExtendedURL(require_tld=False, |
1489 | - if_missing="http://localhost:5240/MAAS")) |
1490 | + "maas_url", "The HTTP URL for the MAAS region.", ExtendedURL( |
1491 | + require_tld=False, if_missing="http://localhost:5240/MAAS")) |
1492 | + |
1493 | # TFTP options. |
1494 | tftp_port = ConfigurationOption( |
1495 | "tftp_port", "The UDP port on which to listen for TFTP requests.", |
1496 | Number(min=0, max=(2 ** 16) - 1, if_missing=69)) |
1497 | tftp_root = ConfigurationOption( |
1498 | "tftp_root", "The root directory for TFTP resources.", |
1499 | - Directory(if_missing="/var/lib/maas/boot-resources/current/")) |
1500 | + Directory(if_missing=get_tentative_path( |
1501 | + "/var/lib/maas/boot-resources/current"))) |
1502 | + |
1503 | + @property |
1504 | + def tftp_generator_url(self): |
1505 | + """The URL at which to obtain the TFTP options for a node.""" |
1506 | + return "%s/api/1.0/pxeconfig/" % self.maas_url.rstrip("/") |
1507 | + |
1508 | + # GRUB options. |
1509 | + |
1510 | + @property |
1511 | + def grub_root(self): |
1512 | + "The root directory for GRUB resources." |
1513 | + return os.path.join(self.tftp_root, "grub") |
1514 | |
1515 | # Cluster UUID Option |
1516 | cluster_uuid = ConfigurationOption( |
1517 | "cluster_uuid", "The UUID for this cluster controller", |
1518 | - UUID(if_missing=unicode(UUID_NOT_SET))) |
1519 | + UUID(if_missing=UUID_NOT_SET)) |
1520 | + |
1521 | + |
1522 | +def is_dev_environment(): |
1523 | + """Is this the development environment, or production?""" |
1524 | + try: |
1525 | + from maastesting import root # noqa |
1526 | + except: |
1527 | + return False |
1528 | + else: |
1529 | + return True |
1530 | |
1531 | === removed file 'src/provisioningserver/configure_maas_url.py' |
1532 | --- src/provisioningserver/configure_maas_url.py 2015-05-29 16:47:37 +0000 |
1533 | +++ src/provisioningserver/configure_maas_url.py 1970-01-01 00:00:00 +0000 |
1534 | @@ -1,123 +0,0 @@ |
1535 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
1536 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1537 | - |
1538 | -"""Management command: update `MAAS_URL`. |
1539 | - |
1540 | -The MAAS cluster controller packaging calls this in order to set a new |
1541 | -"MAAS URL" (the URL where nodes and cluster controllers can reach the |
1542 | -region controller) in the cluster controller's configuration files. |
1543 | -""" |
1544 | - |
1545 | -from __future__ import ( |
1546 | - absolute_import, |
1547 | - print_function, |
1548 | - unicode_literals, |
1549 | - ) |
1550 | - |
1551 | -str = None |
1552 | - |
1553 | -__metaclass__ = type |
1554 | -__all__ = [ |
1555 | - 'add_arguments', |
1556 | - 'run', |
1557 | - ] |
1558 | - |
1559 | -from functools import partial |
1560 | -import re |
1561 | -from urlparse import urlparse |
1562 | - |
1563 | -from provisioningserver.utils.fs import ( |
1564 | - atomic_write, |
1565 | - read_text_file, |
1566 | -) |
1567 | -from provisioningserver.utils.url import compose_URL |
1568 | - |
1569 | - |
1570 | -MAAS_CLUSTER_CONF = '/etc/maas/maas_cluster.conf' |
1571 | - |
1572 | -PSERV_YAML = '/etc/maas/pserv.yaml' |
1573 | - |
1574 | - |
1575 | -def rewrite_config_file(path, line_filter, mode=0600): |
1576 | - """Rewrite config file at `path` on a line-by-line basis. |
1577 | - |
1578 | - Reads the file at `path`, runs its lines through `line_filter`, and |
1579 | - writes the result back to `path`. |
1580 | - |
1581 | - Newlines may not be exactly as they were. A trailing newline is ensured. |
1582 | - |
1583 | - :param path: Path to the config file to be rewritten. |
1584 | - :param line_filter: A callable which accepts a line of input text (without |
1585 | - trailing newline), and returns the corresponding line of output text |
1586 | - (also without trailing newline). |
1587 | - :param mode: File access permissions for the newly written file. |
1588 | - """ |
1589 | - input_lines = read_text_file(path).splitlines() |
1590 | - output_lines = [line_filter(line) for line in input_lines] |
1591 | - result = '%s\n' % '\n'.join(output_lines) |
1592 | - atomic_write(result, path, mode=mode) |
1593 | - |
1594 | - |
1595 | -def update_maas_cluster_conf(url): |
1596 | - """Update `MAAS_URL` in `/etc/maas/maas_cluster.conf`. |
1597 | - |
1598 | - This file contains a shell-style assignment of the `MAAS_URL` |
1599 | - variable. Its assigned value will be changed to `url`. |
1600 | - """ |
1601 | - substitute_line = lambda line: ( |
1602 | - 'MAAS_URL="%s"' % url |
1603 | - if re.match('\s*MAAS_URL=', line) |
1604 | - else line) |
1605 | - rewrite_config_file(MAAS_CLUSTER_CONF, substitute_line, mode=0640) |
1606 | - |
1607 | - |
1608 | -def extract_host(url): |
1609 | - """Return just the host part of `url`.""" |
1610 | - return urlparse(url).hostname |
1611 | - |
1612 | - |
1613 | -def substitute_pserv_yaml_line(new_host, line): |
1614 | - match = re.match('(\s*generator:)\s+(\S*)(.*)$', line) |
1615 | - if match is None: |
1616 | - # Not the generator line. Keep as-is. |
1617 | - return line |
1618 | - [head, input_url, tail] = match.groups() |
1619 | - return "%s %s%s" % (head, compose_URL(input_url, new_host), tail) |
1620 | - |
1621 | - |
1622 | -def update_pserv_yaml(host): |
1623 | - """Update `generator` in `/etc/maas/pserv.yaml`. |
1624 | - |
1625 | - This file contains a YAML line defining a `generator` URL. The line must |
1626 | - look something like:: |
1627 | - |
1628 | - generator: http://10.9.8.7/MAAS/api/1.0/pxeconfig/ |
1629 | - |
1630 | - The host part of the URL (in this example, `10.9.8.7`) will be replaced |
1631 | - with the new `host`. If `host` is an IPv6 address, this function will |
1632 | - ensure that it is surrounded by square brackets. |
1633 | - """ |
1634 | - substitute_line = partial(substitute_pserv_yaml_line, host) |
1635 | - rewrite_config_file(PSERV_YAML, substitute_line, mode=0644) |
1636 | - |
1637 | - |
1638 | -def add_arguments(parser): |
1639 | - """Add this command's options to the `ArgumentParser`. |
1640 | - |
1641 | - Specified by the `ActionScript` interface. |
1642 | - """ |
1643 | - parser.add_argument( |
1644 | - 'maas_url', metavar='URL', |
1645 | - help=( |
1646 | - "URL where nodes and cluster controllers can reach the MAAS " |
1647 | - "region controller.")) |
1648 | - |
1649 | - |
1650 | -def run(args): |
1651 | - """Update MAAS_URL setting in configuration files. |
1652 | - |
1653 | - For use by the MAAS packaging scripts. Updates configuration files |
1654 | - to reflect a new MAAS_URL setting. |
1655 | - """ |
1656 | - update_maas_cluster_conf(args.maas_url) |
1657 | - update_pserv_yaml(extract_host(args.maas_url)) |
1658 | |
1659 | === modified file 'src/provisioningserver/dhcp/tests/test_config.py' |
1660 | --- src/provisioningserver/dhcp/tests/test_config.py 2015-05-07 18:14:38 +0000 |
1661 | +++ src/provisioningserver/dhcp/tests/test_config.py 2015-07-03 11:18:28 +0000 |
1662 | @@ -74,9 +74,9 @@ |
1663 | """ |
1664 | if name is None: |
1665 | name = 'dhcpd.conf.template' |
1666 | - fake_etc_maas = self.make_dir() |
1667 | - self.useFixture(EnvironmentVariableFixture( |
1668 | - 'MAAS_CONFIG_DIR', fake_etc_maas)) |
1669 | + fake_root = self.make_dir() |
1670 | + fake_etc_maas = path.join(fake_root, "etc", "maas") |
1671 | + self.useFixture(EnvironmentVariableFixture('MAAS_ROOT', fake_root)) |
1672 | template_dir = path.join(fake_etc_maas, 'templates', 'dhcp') |
1673 | makedirs(template_dir) |
1674 | template = factory.make_file( |
1675 | |
1676 | === modified file 'src/provisioningserver/dhcp/tests/test_detect.py' |
1677 | --- src/provisioningserver/dhcp/tests/test_detect.py 2015-05-07 18:14:38 +0000 |
1678 | +++ src/provisioningserver/dhcp/tests/test_detect.py 2015-07-03 11:18:28 +0000 |
1679 | @@ -23,10 +23,7 @@ |
1680 | |
1681 | from apiclient.maas_client import MAASClient |
1682 | from apiclient.testing.credentials import make_api_credentials |
1683 | -from fixtures import ( |
1684 | - EnvironmentVariable, |
1685 | - FakeLogger, |
1686 | -) |
1687 | +from fixtures import FakeLogger |
1688 | from maastesting.factory import factory |
1689 | from maastesting.matchers import MockCalledOnceWith |
1690 | from maastesting.testcase import MAASTestCase |
1691 | @@ -48,6 +45,7 @@ |
1692 | update_region_controller, |
1693 | ) |
1694 | import provisioningserver.dhcp.detect as detect_module |
1695 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
1696 | from provisioningserver.testing.testcase import PservTestCase |
1697 | |
1698 | |
1699 | @@ -343,7 +341,7 @@ |
1700 | factory.make_string(), |
1701 | ) |
1702 | api_credentials = make_api_credentials() |
1703 | - self.useFixture(EnvironmentVariable("MAAS_URL", maas_url)) |
1704 | + self.useFixture(ClusterConfigurationFixture(maas_url=maas_url)) |
1705 | self.knowledge = dict( |
1706 | nodegroup_uuid=uuid, |
1707 | api_credentials=api_credentials, |
1708 | |
1709 | === modified file 'src/provisioningserver/diskless.py' |
1710 | --- src/provisioningserver/diskless.py 2015-05-29 16:47:37 +0000 |
1711 | +++ src/provisioningserver/diskless.py 2015-07-03 11:18:28 +0000 |
1712 | @@ -20,12 +20,13 @@ |
1713 | import os |
1714 | from textwrap import dedent |
1715 | |
1716 | -from provisioningserver import config |
1717 | +from provisioningserver.config import ClusterConfiguration |
1718 | from provisioningserver.drivers.diskless import DisklessDriverRegistry |
1719 | from provisioningserver.drivers.osystem import ( |
1720 | BOOT_IMAGE_PURPOSE, |
1721 | OperatingSystemRegistry, |
1722 | ) |
1723 | +from provisioningserver.import_images.boot_resources import two_dir_levels_up |
1724 | from provisioningserver.logger import get_maas_logger |
1725 | from provisioningserver.utils.fs import ( |
1726 | atomic_symlink, |
1727 | @@ -47,8 +48,10 @@ |
1728 | This is the location that all diskless links exist. It holds all of the |
1729 | currently in use disk for diskless booting. |
1730 | """ |
1731 | - return os.path.join( |
1732 | - config.BOOT_RESOURCES_STORAGE, 'diskless', 'store') |
1733 | + with ClusterConfiguration.open() as config: |
1734 | + return os.path.join( |
1735 | + two_dir_levels_up(config.tftp_root), |
1736 | + 'diskless', 'store') |
1737 | |
1738 | |
1739 | def compose_diskless_link_path(system_id): |
1740 | @@ -99,8 +102,10 @@ |
1741 | |
1742 | def get_diskless_tgt_path(): |
1743 | """Return path to maas-diskless.tgt.""" |
1744 | - return os.path.join( |
1745 | - config.BOOT_RESOURCES_STORAGE, 'diskless', 'maas-diskless.tgt') |
1746 | + with ClusterConfiguration.open() as config: |
1747 | + return os.path.join( |
1748 | + two_dir_levels_up(config.tftp_root), |
1749 | + 'diskless', 'maas-diskless.tgt') |
1750 | |
1751 | |
1752 | def tgt_entry(system_id, image): |
1753 | @@ -153,7 +158,7 @@ |
1754 | symlinks in the diskless store. Reloads the tgt config.""" |
1755 | tgt_path = get_diskless_tgt_path() |
1756 | tgt_config = compose_diskless_tgt_config() |
1757 | - atomic_write(tgt_config, tgt_path, mode=0644) |
1758 | + atomic_write(tgt_config, tgt_path, mode=0o644) |
1759 | reload_diskless_tgt() |
1760 | |
1761 | |
1762 | @@ -183,9 +188,10 @@ |
1763 | raise DisklessError( |
1764 | "OS doesn't support diskless booting: %s" % osystem_name) |
1765 | root_path, _ = osystem.get_xinstall_parameters() |
1766 | - return os.path.join( |
1767 | - config.BOOT_RESOURCES_STORAGE, 'current', |
1768 | - osystem_name, arch, subarch, release, label, root_path) |
1769 | + with ClusterConfiguration.open() as config: |
1770 | + return os.path.join( |
1771 | + config.tftp_root, osystem_name, arch, subarch, release, label, |
1772 | + root_path) |
1773 | |
1774 | |
1775 | def create_diskless_disk(driver, driver_options, system_id, |
1776 | |
1777 | === modified file 'src/provisioningserver/drivers/osystem/custom.py' |
1778 | --- src/provisioningserver/drivers/osystem/custom.py 2015-05-29 16:47:37 +0000 |
1779 | +++ src/provisioningserver/drivers/osystem/custom.py 2015-07-03 11:18:28 +0000 |
1780 | @@ -18,7 +18,7 @@ |
1781 | |
1782 | import os |
1783 | |
1784 | -from provisioningserver.config import BOOT_RESOURCES_STORAGE |
1785 | +from provisioningserver.config import ClusterConfiguration |
1786 | from provisioningserver.drivers.osystem import ( |
1787 | BOOT_IMAGE_PURPOSE, |
1788 | OperatingSystem, |
1789 | @@ -55,10 +55,11 @@ |
1790 | |
1791 | def get_xinstall_parameters(self, arch, subarch, release, label): |
1792 | """Returns the xinstall image name and type for given image.""" |
1793 | - path = os.path.join( |
1794 | - BOOT_RESOURCES_STORAGE, 'current', 'custom', |
1795 | - arch, subarch, release, label) |
1796 | - if os.path.exists(os.path.join(path, 'root-dd')): |
1797 | + with ClusterConfiguration.open() as config: |
1798 | + dd_path = os.path.join( |
1799 | + config.tftp_root, 'custom', arch, |
1800 | + subarch, release, label, 'root-dd') |
1801 | + if os.path.exists(dd_path): |
1802 | return "root-dd", "dd-tgz" |
1803 | else: |
1804 | return "root-tgz", "tgz" |
1805 | |
1806 | === modified file 'src/provisioningserver/drivers/osystem/tests/test_custom.py' |
1807 | --- src/provisioningserver/drivers/osystem/tests/test_custom.py 2015-05-29 16:47:37 +0000 |
1808 | +++ src/provisioningserver/drivers/osystem/tests/test_custom.py 2015-07-03 11:18:28 +0000 |
1809 | @@ -19,26 +19,30 @@ |
1810 | |
1811 | from maastesting.factory import factory |
1812 | from maastesting.testcase import MAASTestCase |
1813 | -from provisioningserver.drivers.osystem import custom |
1814 | +from provisioningserver.config import ClusterConfiguration |
1815 | from provisioningserver.drivers.osystem.custom import ( |
1816 | BOOT_IMAGE_PURPOSE, |
1817 | CustomOS, |
1818 | ) |
1819 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
1820 | |
1821 | |
1822 | class TestCustomOS(MAASTestCase): |
1823 | |
1824 | def make_resource_path(self, filename): |
1825 | + self.useFixture(ClusterConfigurationFixture()) |
1826 | tmpdir = self.make_dir() |
1827 | arch = factory.make_name('arch') |
1828 | subarch = factory.make_name('subarch') |
1829 | release = factory.make_name('release') |
1830 | label = factory.make_name('label') |
1831 | + current_dir = os.path.join(tmpdir, 'current') + '/' |
1832 | dirpath = os.path.join( |
1833 | - tmpdir, 'current', 'custom', arch, subarch, release, label) |
1834 | + current_dir, 'custom', arch, subarch, release, label) |
1835 | os.makedirs(dirpath) |
1836 | factory.make_file(dirpath, filename) |
1837 | - self.patch(custom, 'BOOT_RESOURCES_STORAGE', tmpdir) |
1838 | + with ClusterConfiguration.open() as config: |
1839 | + config.tftp_root = current_dir |
1840 | return arch, subarch, release, label |
1841 | |
1842 | def test_get_boot_image_purposes(self): |
1843 | |
1844 | === modified file 'src/provisioningserver/drivers/osystem/tests/test_windows.py' |
1845 | --- src/provisioningserver/drivers/osystem/tests/test_windows.py 2015-05-29 16:47:37 +0000 |
1846 | +++ src/provisioningserver/drivers/osystem/tests/test_windows.py 2015-07-03 11:18:28 +0000 |
1847 | @@ -25,12 +25,12 @@ |
1848 | ) |
1849 | from provisioningserver.drivers.osystem.windows import ( |
1850 | BOOT_IMAGE_PURPOSE, |
1851 | - Config, |
1852 | REQUIRE_LICENSE_KEY, |
1853 | WINDOWS_CHOICES, |
1854 | WINDOWS_DEFAULT, |
1855 | WindowsOS, |
1856 | ) |
1857 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
1858 | |
1859 | |
1860 | class TestWindowsOS(MAASTestCase): |
1861 | @@ -46,11 +46,7 @@ |
1862 | os.makedirs(dirpath) |
1863 | for fname in files: |
1864 | factory.make_file(dirpath, fname) |
1865 | - self.patch(Config, 'load_from_cache').return_value = { |
1866 | - 'tftp': { |
1867 | - 'resource_root': tmpdir, |
1868 | - }, |
1869 | - } |
1870 | + self.useFixture(ClusterConfigurationFixture(tftp_root=tmpdir)) |
1871 | return arch, subarch, release, label |
1872 | |
1873 | def test_get_boot_image_purposes_neither(self): |
1874 | |
1875 | === modified file 'src/provisioningserver/drivers/osystem/windows.py' |
1876 | --- src/provisioningserver/drivers/osystem/windows.py 2015-05-29 16:47:37 +0000 |
1877 | +++ src/provisioningserver/drivers/osystem/windows.py 2015-07-03 11:18:28 +0000 |
1878 | @@ -19,7 +19,7 @@ |
1879 | import os |
1880 | import re |
1881 | |
1882 | -from provisioningserver.config import Config |
1883 | +from provisioningserver.config import ClusterConfiguration |
1884 | from provisioningserver.drivers.osystem import ( |
1885 | BOOT_IMAGE_PURPOSE, |
1886 | OperatingSystem, |
1887 | @@ -51,7 +51,8 @@ |
1888 | # is available the node will boot correctly, even if fast-path |
1889 | # installer is not selected. |
1890 | purposes = [] |
1891 | - resources = Config.load_from_cache()['tftp']['resource_root'] |
1892 | + with ClusterConfiguration.open() as config: |
1893 | + resources = config.tftp_root |
1894 | path = os.path.join( |
1895 | resources, 'windows', arch, subarch, release, label) |
1896 | if os.path.exists(os.path.join(path, 'root-dd')): |
1897 | |
1898 | === modified file 'src/provisioningserver/import_images/boot_resources.py' |
1899 | --- src/provisioningserver/import_images/boot_resources.py 2015-05-29 16:47:37 +0000 |
1900 | +++ src/provisioningserver/import_images/boot_resources.py 2015-07-03 11:18:28 +0000 |
1901 | @@ -22,10 +22,12 @@ |
1902 | import os |
1903 | from textwrap import dedent |
1904 | |
1905 | -import provisioningserver |
1906 | from provisioningserver.boot import BootMethodRegistry |
1907 | from provisioningserver.boot.tftppath import list_boot_images |
1908 | -from provisioningserver.config import BootSources |
1909 | +from provisioningserver.config import ( |
1910 | + BootSources, |
1911 | + ClusterConfiguration, |
1912 | +) |
1913 | from provisioningserver.import_images.cleanup import ( |
1914 | cleanup_snapshots_and_cache, |
1915 | ) |
1916 | @@ -39,7 +41,6 @@ |
1917 | from provisioningserver.import_images.keyrings import write_all_keyrings |
1918 | from provisioningserver.import_images.product_mapping import map_products |
1919 | from provisioningserver.service_monitor import service_monitor |
1920 | -from provisioningserver.utils import get_cluster_config |
1921 | from provisioningserver.utils.fs import ( |
1922 | atomic_symlink, |
1923 | atomic_write, |
1924 | @@ -49,6 +50,16 @@ |
1925 | from provisioningserver.utils.shell import call_and_check |
1926 | |
1927 | |
1928 | +def two_dir_levels_up(path): |
1929 | + # NOTE: Syntax along the lines of: |
1930 | + # os.path.normpath(os.path.join( |
1931 | + # path, os.path.pardir, os.path.pardir)) |
1932 | + # will NOT work here, as the classes that use these file paths |
1933 | + # to NOT support paths with 'os.path.pardir' segments. Thus |
1934 | + # we must manually resolve the path here. |
1935 | + return os.path.split(os.path.split(path)[0])[0] |
1936 | + |
1937 | + |
1938 | class NoConfigFile(Exception): |
1939 | """Raised when the config file for the script doesn't exist.""" |
1940 | |
1941 | @@ -250,7 +261,8 @@ |
1942 | "any boot images available.") |
1943 | return |
1944 | |
1945 | - storage = provisioningserver.config.BOOT_RESOURCES_STORAGE |
1946 | + with ClusterConfiguration.open() as config: |
1947 | + storage = two_dir_levels_up(config.tftp_root) |
1948 | meta_file_content = image_descriptions.dump_json() |
1949 | if meta_contains(storage, meta_file_content): |
1950 | maaslog.info( |
1951 | @@ -359,10 +371,6 @@ |
1952 | finally: |
1953 | reactor.callLater(0, reactor.stop) |
1954 | |
1955 | - cluster_config = get_cluster_config('/etc/maas/maas_cluster.conf') |
1956 | - os.environ['MAAS_URL'] = cluster_config['MAAS_URL'] |
1957 | - os.environ['CLUSTER_UUID'] = cluster_config['CLUSTER_UUID'] |
1958 | - |
1959 | reactor.callWhenRunning(run_main) |
1960 | reactor.run() |
1961 | |
1962 | |
1963 | === modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py' |
1964 | --- src/provisioningserver/import_images/tests/test_boot_resources.py 2015-05-29 16:47:37 +0000 |
1965 | +++ src/provisioningserver/import_images/tests/test_boot_resources.py 2015-07-03 11:18:28 +0000 |
1966 | @@ -40,14 +40,19 @@ |
1967 | import mock |
1968 | from mock import call |
1969 | from provisioningserver.boot import BootMethodRegistry |
1970 | -import provisioningserver.config |
1971 | -from provisioningserver.config import BootSources |
1972 | +from provisioningserver.config import ( |
1973 | + BootSources, |
1974 | + ClusterConfiguration, |
1975 | +) |
1976 | from provisioningserver.import_images import boot_resources |
1977 | from provisioningserver.import_images.boot_image_mapping import ( |
1978 | BootImageMapping, |
1979 | ) |
1980 | from provisioningserver.import_images.testing.factory import make_image_spec |
1981 | -from provisioningserver.testing.config import BootSourcesFixture |
1982 | +from provisioningserver.testing.config import ( |
1983 | + BootSourcesFixture, |
1984 | + ClusterConfigurationFixture, |
1985 | +) |
1986 | from provisioningserver.utils.fs import write_text_file |
1987 | from testtools.content import Content |
1988 | from testtools.content_type import UTF8_TEXT |
1989 | @@ -196,9 +201,13 @@ |
1990 | |
1991 | def setUp(self): |
1992 | super(TestMain, self).setUp() |
1993 | + self.useFixture(ClusterConfigurationFixture()) |
1994 | self.storage = self.make_dir() |
1995 | - self.patch( |
1996 | - provisioningserver.config, 'BOOT_RESOURCES_STORAGE', self.storage) |
1997 | + current_dir = os.path.join(self.storage, 'current') + os.sep |
1998 | + os.makedirs(current_dir) |
1999 | + with ClusterConfiguration.open() as config: |
2000 | + config.tftp_root = current_dir |
2001 | + os.rmdir(current_dir) |
2002 | # Forcing arch to amd64 causes pxelinux.0 to be installed, giving more |
2003 | # test coverage. |
2004 | self.image = make_image_spec(arch='amd64') |
2005 | |
2006 | === modified file 'src/provisioningserver/path.py' |
2007 | --- src/provisioningserver/path.py 2015-05-07 18:14:38 +0000 |
2008 | +++ src/provisioningserver/path.py 2015-07-03 11:18:28 +0000 |
2009 | @@ -22,6 +22,17 @@ |
2010 | from provisioningserver.utils.fs import ensure_dir |
2011 | |
2012 | |
2013 | +def get_root(): |
2014 | + """Return ``MAAS_ROOT`` if set, else "/".""" |
2015 | + root = os.getenv('MAAS_ROOT') |
2016 | + if root is None: |
2017 | + return "/" |
2018 | + elif len(root) == 0: |
2019 | + return "/" |
2020 | + else: |
2021 | + return root |
2022 | + |
2023 | + |
2024 | def get_tentative_path(*path_elements): |
2025 | """Return an absolute path based on the `MAAS_ROOT` environment variable. |
2026 | |
2027 | @@ -44,12 +55,12 @@ |
2028 | holds your path, export a getter function that returns your path. Add |
2029 | caching if it becomes a performance problem. |
2030 | """ |
2031 | - maas_root = os.getenv('MAAS_ROOT', '/') |
2032 | - # Strip off a leading slash, if any. If left in, it would override any |
2033 | - # preceding path elements. The MAAS_ROOT would be ignored. |
2034 | - # The dot is there to make the call work even with zero path elements. |
2035 | + # Strip off a leading slash from the given path, if any. If it were left |
2036 | + # in, it would override preceding path elements and MAAS_ROOT would be |
2037 | + # ignored later on. The dot is there to make the call work even with zero |
2038 | + # path elements. |
2039 | path = os.path.join('.', *path_elements).lstrip('/') |
2040 | - path = os.path.join(maas_root, path) |
2041 | + path = os.path.join(get_root(), path) |
2042 | return os.path.abspath(path) |
2043 | |
2044 | |
2045 | |
2046 | === modified file 'src/provisioningserver/plugin.py' |
2047 | --- src/provisioningserver/plugin.py 2015-06-25 14:48:14 +0000 |
2048 | +++ src/provisioningserver/plugin.py 2015-07-03 11:18:28 +0000 |
2049 | @@ -17,10 +17,10 @@ |
2050 | ] |
2051 | |
2052 | from errno import ENOPROTOOPT |
2053 | -import os |
2054 | import socket |
2055 | from socket import error as socket_error |
2056 | |
2057 | +from provisioningserver.config import ClusterConfiguration |
2058 | from provisioningserver.monkey import ( |
2059 | add_term_error_code_to_tftp, |
2060 | force_simplestreams_to_use_urllib2, |
2061 | @@ -48,7 +48,6 @@ |
2062 | """Command line options for the provisioning server.""" |
2063 | |
2064 | optParameters = [ |
2065 | - ["config-file", "c", "pserv.yaml", "Configuration file to load."], |
2066 | ["introspect", None, None, |
2067 | ("Allow introspection, allowing unhindered access to the internals " |
2068 | "of MAAS. This should probably only be used for debugging. Supply " |
2069 | @@ -79,12 +78,10 @@ |
2070 | site_service.setName("site") |
2071 | return site_service |
2072 | |
2073 | - def _makeImageService(self): |
2074 | + def _makeImageService(self, resource_root): |
2075 | from provisioningserver.pserv_services.image import ( |
2076 | BootImageEndpointService) |
2077 | from twisted.internet.endpoints import AdoptedStreamServerEndpoint |
2078 | - from provisioningserver import config |
2079 | - |
2080 | port = 5248 # config["port"] |
2081 | # Make a socket with SO_REUSEPORT set so that we can run multiple we |
2082 | # applications. This is easier to do from outside of Twisted as there's |
2083 | @@ -112,59 +109,54 @@ |
2084 | site_endpoint.socket = s # Prevent garbage collection. |
2085 | |
2086 | image_service = BootImageEndpointService( |
2087 | - resource_root=os.path.join( |
2088 | - config.BOOT_RESOURCES_STORAGE, "current"), |
2089 | - endpoint=site_endpoint) |
2090 | + resource_root=resource_root, endpoint=site_endpoint) |
2091 | image_service.setName("image_service") |
2092 | return image_service |
2093 | |
2094 | - def _makeTFTPService(self, tftp_config): |
2095 | + def _makeTFTPService( |
2096 | + self, cluster_uuid, tftp_root, tftp_port, tftp_generator): |
2097 | """Create the dynamic TFTP service.""" |
2098 | from provisioningserver.pserv_services.tftp import TFTPService |
2099 | tftp_service = TFTPService( |
2100 | - resource_root=tftp_config['resource_root'], |
2101 | - port=tftp_config['port'], generator=tftp_config['generator']) |
2102 | + resource_root=tftp_root, port=tftp_port, generator=tftp_generator, |
2103 | + uuid=cluster_uuid) |
2104 | tftp_service.setName("tftp") |
2105 | return tftp_service |
2106 | |
2107 | - def _makeImageDownloadService(self, rpc_service): |
2108 | - from provisioningserver.cluster_config import get_cluster_uuid |
2109 | + def _makeImageDownloadService(self, rpc_service, cluster_uuid, tftp_root): |
2110 | from provisioningserver.pserv_services.image_download_service \ |
2111 | import ImageDownloadService |
2112 | image_download_service = ImageDownloadService( |
2113 | - rpc_service, reactor, get_cluster_uuid()) |
2114 | + rpc_service, cluster_uuid, tftp_root, reactor) |
2115 | image_download_service.setName("image_download") |
2116 | return image_download_service |
2117 | |
2118 | - def _makeLeaseUploadService(self, rpc_service): |
2119 | - from provisioningserver.cluster_config import get_cluster_uuid |
2120 | + def _makeLeaseUploadService(self, rpc_service, cluster_uuid): |
2121 | from provisioningserver.pserv_services.lease_upload_service \ |
2122 | import LeaseUploadService |
2123 | lease_upload_service = LeaseUploadService( |
2124 | - rpc_service, reactor, get_cluster_uuid()) |
2125 | + rpc_service, reactor, cluster_uuid) |
2126 | lease_upload_service.setName("lease_upload") |
2127 | return lease_upload_service |
2128 | |
2129 | - def _makeNodePowerMonitorService(self): |
2130 | - from provisioningserver.cluster_config import get_cluster_uuid |
2131 | + def _makeNodePowerMonitorService(self, cluster_uuid): |
2132 | from provisioningserver.pserv_services.node_power_monitor_service \ |
2133 | import NodePowerMonitorService |
2134 | - node_monitor = NodePowerMonitorService(get_cluster_uuid(), reactor) |
2135 | + node_monitor = NodePowerMonitorService(cluster_uuid, reactor) |
2136 | node_monitor.setName("node_monitor") |
2137 | return node_monitor |
2138 | |
2139 | - def _makeRPCService(self, rpc_config): |
2140 | + def _makeRPCService(self): |
2141 | from provisioningserver.rpc.clusterservice import ClusterClientService |
2142 | rpc_service = ClusterClientService(reactor) |
2143 | rpc_service.setName("rpc") |
2144 | return rpc_service |
2145 | |
2146 | - def _makeDHCPProbeService(self, rpc_service): |
2147 | - from provisioningserver.cluster_config import get_cluster_uuid |
2148 | + def _makeDHCPProbeService(self, rpc_service, cluster_uuid): |
2149 | from provisioningserver.pserv_services.dhcp_probe_service \ |
2150 | import DHCPProbeService |
2151 | dhcp_probe_service = DHCPProbeService( |
2152 | - rpc_service, reactor, get_cluster_uuid()) |
2153 | + rpc_service, reactor, cluster_uuid) |
2154 | dhcp_probe_service.setName("dhcp_probe") |
2155 | return dhcp_probe_service |
2156 | |
2157 | @@ -183,6 +175,23 @@ |
2158 | introspect_service.setName("introspect") |
2159 | return introspect_service |
2160 | |
2161 | + def _makeServices(self, config): |
2162 | + # Several services need to make use of the RPC service. |
2163 | + rpc_service = self._makeRPCService() |
2164 | + yield rpc_service |
2165 | + # Other services that make up the MAAS Region Controller. |
2166 | + yield self._makeDHCPProbeService(rpc_service, config.cluster_uuid) |
2167 | + yield self._makeLeaseUploadService(rpc_service, config.cluster_uuid) |
2168 | + yield self._makeNodePowerMonitorService(config.cluster_uuid) |
2169 | + yield self._makeServiceMonitorService() |
2170 | + yield self._makeImageDownloadService( |
2171 | + rpc_service, config.cluster_uuid, config.tftp_root) |
2172 | + # The following are network-accessible services. |
2173 | + yield self._makeImageService(config.tftp_root) |
2174 | + yield self._makeTFTPService( |
2175 | + config.cluster_uuid, config.tftp_root, config.tftp_port, |
2176 | + config.tftp_generator_url) |
2177 | + |
2178 | def makeService(self, options): |
2179 | """Construct the MAAS Cluster service.""" |
2180 | register_sigusr2_thread_dump_handler() |
2181 | @@ -190,33 +199,9 @@ |
2182 | add_term_error_code_to_tftp() |
2183 | |
2184 | from provisioningserver import services |
2185 | - from provisioningserver.config import Config |
2186 | - |
2187 | - config = Config.load(options["config-file"]) |
2188 | - |
2189 | - image_service = self._makeImageService() |
2190 | - image_service.setServiceParent(services) |
2191 | - |
2192 | - tftp_service = self._makeTFTPService(config["tftp"]) |
2193 | - tftp_service.setServiceParent(services) |
2194 | - |
2195 | - rpc_service = self._makeRPCService(config["rpc"]) |
2196 | - rpc_service.setServiceParent(services) |
2197 | - |
2198 | - node_monitor = self._makeNodePowerMonitorService() |
2199 | - node_monitor.setServiceParent(services) |
2200 | - |
2201 | - image_download_service = self._makeImageDownloadService(rpc_service) |
2202 | - image_download_service.setServiceParent(services) |
2203 | - |
2204 | - dhcp_probe_service = self._makeDHCPProbeService(rpc_service) |
2205 | - dhcp_probe_service.setServiceParent(services) |
2206 | - |
2207 | - lease_upload_service = self._makeLeaseUploadService(rpc_service) |
2208 | - lease_upload_service.setServiceParent(services) |
2209 | - |
2210 | - service_monitor_service = self._makeServiceMonitorService() |
2211 | - service_monitor_service.setServiceParent(services) |
2212 | + with ClusterConfiguration.open() as config: |
2213 | + for service in self._makeServices(config): |
2214 | + service.setServiceParent(services) |
2215 | |
2216 | if options["introspect"] is not None: |
2217 | introspect = self._makeIntrospectionService(options["introspect"]) |
2218 | |
2219 | === modified file 'src/provisioningserver/pserv_services/image_download_service.py' |
2220 | --- src/provisioningserver/pserv_services/image_download_service.py 2015-05-07 18:14:38 +0000 |
2221 | +++ src/provisioningserver/pserv_services/image_download_service.py 2015-07-03 11:18:28 +0000 |
2222 | @@ -45,22 +45,24 @@ |
2223 | |
2224 | |
2225 | class ImageDownloadService(TimerService, object): |
2226 | - """Twisted service to periodically refresh ephemeral images. |
2227 | - |
2228 | - :param client_service: A `ClusterClientService` instance for talking |
2229 | - to the region controller. |
2230 | - :param reactor: An `IReactor` instance. |
2231 | - """ |
2232 | + """Twisted service to periodically refresh ephemeral images.""" |
2233 | |
2234 | check_interval = timedelta(minutes=5).total_seconds() |
2235 | |
2236 | - def __init__(self, client_service, reactor, cluster_uuid): |
2237 | - # Call self.check() every self.check_interval. |
2238 | + def __init__(self, client_service, cluster_uuid, tftp_root, reactor): |
2239 | + """Twisted service to periodically refresh ephemeral images. |
2240 | + |
2241 | + :param client_service: A `ClusterClientService` instance. |
2242 | + :param cluster_uuid: The UUID for this cluster, as a string. |
2243 | + :param tftp_root: The path to the TFTP root directory. |
2244 | + :param reactor: An `IReactor` instance. |
2245 | + """ |
2246 | super(ImageDownloadService, self).__init__( |
2247 | self.check_interval, self.try_download) |
2248 | - self.clock = reactor |
2249 | self.client_service = client_service |
2250 | self.uuid = cluster_uuid |
2251 | + self.tftp_root = tftp_root |
2252 | + self.clock = reactor |
2253 | |
2254 | def try_download(self): |
2255 | """Wrap download attempts in something that catches Failures. |
2256 | @@ -125,11 +127,10 @@ |
2257 | """Check the time the last image refresh happened and initiate a new |
2258 | one if older than 15 minutes. |
2259 | """ |
2260 | - last_modified = tftppath.maas_meta_last_modified() |
2261 | + last_modified = tftppath.maas_meta_last_modified(self.tftp_root) |
2262 | if last_modified is None: |
2263 | yield self._start_download() |
2264 | - return |
2265 | - |
2266 | - age_in_seconds = self.clock.seconds() - last_modified |
2267 | - if age_in_seconds >= timedelta(minutes=15).total_seconds(): |
2268 | - yield self._start_download() |
2269 | + else: |
2270 | + age_in_seconds = self.clock.seconds() - last_modified |
2271 | + if age_in_seconds >= timedelta(minutes=15).total_seconds(): |
2272 | + yield self._start_download() |
2273 | |
2274 | === modified file 'src/provisioningserver/pserv_services/service_monitor_service.py' |
2275 | --- src/provisioningserver/pserv_services/service_monitor_service.py 2015-05-19 19:02:22 +0000 |
2276 | +++ src/provisioningserver/pserv_services/service_monitor_service.py 2015-07-03 11:18:28 +0000 |
2277 | @@ -20,6 +20,7 @@ |
2278 | |
2279 | from datetime import timedelta |
2280 | |
2281 | +from provisioningserver.config import is_dev_environment |
2282 | from provisioningserver.service_monitor import service_monitor |
2283 | from twisted.application.internet import TimerService |
2284 | from twisted.internet.threads import deferToThread |
2285 | @@ -41,6 +42,11 @@ |
2286 | """Monitors all of the external services and makes sure they |
2287 | stay running. |
2288 | """ |
2289 | - d = deferToThread(service_monitor.ensure_all_services) |
2290 | - d.addErrback(log.err, "Failed to monitor services.") |
2291 | - return d |
2292 | + if is_dev_environment(): |
2293 | + log.msg( |
2294 | + "Skipping check of services; they're not running under " |
2295 | + "the supervision of Upstart or systemd.") |
2296 | + else: |
2297 | + d = deferToThread(service_monitor.ensure_all_services) |
2298 | + d.addErrback(log.err, "Failed to monitor services.") |
2299 | + return d |
2300 | |
2301 | === modified file 'src/provisioningserver/pserv_services/tests/test_image_download_service.py' |
2302 | --- src/provisioningserver/pserv_services/tests/test_image_download_service.py 2015-05-22 15:52:13 +0000 |
2303 | +++ src/provisioningserver/pserv_services/tests/test_image_download_service.py 2015-07-03 11:18:28 +0000 |
2304 | @@ -57,11 +57,13 @@ |
2305 | |
2306 | def test_init(self): |
2307 | service = ImageDownloadService( |
2308 | - sentinel.service, sentinel.clock, sentinel.uuid) |
2309 | + sentinel.service, sentinel.uuid, sentinel.tftp_root, |
2310 | + sentinel.clock) |
2311 | self.assertIsInstance(service, TimerService) |
2312 | self.assertIs(service.clock, sentinel.clock) |
2313 | self.assertIs(service.uuid, sentinel.uuid) |
2314 | self.assertIs(service.client_service, sentinel.service) |
2315 | + self.assertIs(service.tftp_root, sentinel.tftp_root) |
2316 | |
2317 | def patch_download(self, service, return_value): |
2318 | patched = self.patch(service, '_start_download') |
2319 | @@ -71,7 +73,7 @@ |
2320 | def test_is_called_every_interval(self): |
2321 | clock = Clock() |
2322 | service = ImageDownloadService( |
2323 | - sentinel.service, clock, sentinel.uuid) |
2324 | + sentinel.service, sentinel.uuid, sentinel.tftp_root, clock) |
2325 | # Avoid actual downloads: |
2326 | self.patch_download(service, None) |
2327 | maas_meta_last_modified = self.patch( |
2328 | @@ -100,7 +102,7 @@ |
2329 | def test_initiates_download_if_no_meta_file(self): |
2330 | clock = Clock() |
2331 | service = ImageDownloadService( |
2332 | - sentinel.service, clock, sentinel.uuid) |
2333 | + sentinel.service, sentinel.uuid, sentinel.tftp_root, clock) |
2334 | _start_download = self.patch_download(service, None) |
2335 | self.patch( |
2336 | tftppath, |
2337 | @@ -111,7 +113,7 @@ |
2338 | def test_initiates_download_if_15_minutes_has_passed(self): |
2339 | clock = Clock() |
2340 | service = ImageDownloadService( |
2341 | - sentinel.service, clock, sentinel.uuid) |
2342 | + sentinel.service, sentinel.uuid, sentinel.tftp_root, clock) |
2343 | _start_download = self.patch_download(service, None) |
2344 | one_week_ago = clock.seconds() - timedelta(minutes=15).total_seconds() |
2345 | self.patch( |
2346 | @@ -123,7 +125,7 @@ |
2347 | def test_no_download_if_15_minutes_has_not_passed(self): |
2348 | clock = Clock() |
2349 | service = ImageDownloadService( |
2350 | - sentinel.service, clock, sentinel.uuid) |
2351 | + sentinel.service, sentinel.uuid, sentinel.tftp_root, clock) |
2352 | _start_download = self.patch_download(service, None) |
2353 | one_week = timedelta(minutes=15).total_seconds() |
2354 | self.patch( |
2355 | @@ -159,7 +161,7 @@ |
2356 | deferToThread = self.patch(boot_images, 'deferToThread') |
2357 | deferToThread.return_value = defer.succeed(None) |
2358 | service = ImageDownloadService( |
2359 | - rpc_client, clock, sentinel.uuid) |
2360 | + rpc_client, sentinel.uuid, sentinel.tftp_root, clock) |
2361 | service.startService() |
2362 | self.assertThat( |
2363 | deferToThread, MockCalledOnceWith( |
2364 | @@ -173,13 +175,13 @@ |
2365 | |
2366 | deferToThread = self.patch(boot_images, 'deferToThread') |
2367 | service = ImageDownloadService( |
2368 | - rpc_client, Clock(), sentinel.uuid) |
2369 | + rpc_client, sentinel.uuid, self.make_dir(), Clock()) |
2370 | service.startService() |
2371 | self.assertThat(deferToThread, MockNotCalled()) |
2372 | |
2373 | def test_logs_other_errors(self): |
2374 | service = ImageDownloadService( |
2375 | - sentinel.rpc, Clock(), sentinel.uuid) |
2376 | + sentinel.rpc, sentinel.uuid, sentinel.tftp_root, Clock()) |
2377 | |
2378 | maybe_start_download = self.patch(service, "maybe_start_download") |
2379 | maybe_start_download.return_value = defer.fail( |
2380 | @@ -215,7 +217,7 @@ |
2381 | ] |
2382 | |
2383 | service = ImageDownloadService( |
2384 | - sentinel.rpc, clock, sentinel.uuid) |
2385 | + sentinel.rpc, sentinel.uuid, sentinel.tftp_root, clock) |
2386 | sources = yield service._get_boot_sources(client_call) |
2387 | self.assertEqual(sources.get('sources'), sentinel.sources) |
2388 | self.assertThat( |
2389 | @@ -232,7 +234,7 @@ |
2390 | ] |
2391 | |
2392 | service = ImageDownloadService( |
2393 | - sentinel.rpc, clock, sentinel.uuid) |
2394 | + sentinel.rpc, sentinel.uuid, sentinel.tftp_root, clock) |
2395 | yield service._get_boot_sources(client_call) |
2396 | self.assertThat( |
2397 | client_call, |
2398 | @@ -270,7 +272,7 @@ |
2399 | ] |
2400 | |
2401 | service = ImageDownloadService( |
2402 | - sentinel.rpc, clock, sentinel.uuid) |
2403 | + sentinel.rpc, sentinel.uuid, sentinel.tftp_root, clock) |
2404 | sources = yield service._get_boot_sources(client_call) |
2405 | os_selections = [ |
2406 | selection.get('os') |
2407 | |
2408 | === modified file 'src/provisioningserver/pserv_services/tests/test_service_monitor_service.py' |
2409 | --- src/provisioningserver/pserv_services/tests/test_service_monitor_service.py 2015-06-10 11:35:04 +0000 |
2410 | +++ src/provisioningserver/pserv_services/tests/test_service_monitor_service.py 2015-07-03 11:18:28 +0000 |
2411 | @@ -15,11 +15,15 @@ |
2412 | __metaclass__ = type |
2413 | __all__ = [] |
2414 | |
2415 | -from maastesting.matchers import MockCalledOnceWith |
2416 | +from maastesting.matchers import ( |
2417 | + MockCalledOnceWith, |
2418 | + MockNotCalled, |
2419 | +) |
2420 | from maastesting.testcase import ( |
2421 | MAASTestCase, |
2422 | MAASTwistedRunTest, |
2423 | ) |
2424 | +from maastesting.twisted import TwistedLoggerFixture |
2425 | from provisioningserver.pserv_services import service_monitor_service as sms |
2426 | from provisioningserver.service_monitor import service_monitor |
2427 | from testtools.matchers import MatchesStructure |
2428 | @@ -40,7 +44,23 @@ |
2429 | service = sms.ServiceMonitorService(Clock()) |
2430 | return service |
2431 | |
2432 | + def test_monitor_services_does_not_do_anything_in_dev_environment(self): |
2433 | + # Belt-n-braces make sure we're in a development environment. |
2434 | + self.assertTrue(sms.is_dev_environment()) |
2435 | + |
2436 | + service = self.make_monitor_service() |
2437 | + mock_deferToThread = self.patch(sms, "deferToThread") |
2438 | + with TwistedLoggerFixture() as logger: |
2439 | + service.monitor_services() |
2440 | + self.assertThat(mock_deferToThread, MockNotCalled()) |
2441 | + self.assertDocTestMatches( |
2442 | + "Skipping check of services; they're not running under the " |
2443 | + "supervision of Upstart or systemd.", logger.output) |
2444 | + |
2445 | def test_monitor_services_defers_ensure_all_services_to_thread(self): |
2446 | + # Pretend we're in a production environment. |
2447 | + self.patch(sms, "is_dev_environment").return_value = False |
2448 | + |
2449 | service = self.make_monitor_service() |
2450 | mock_deferToThread = self.patch(sms, "deferToThread") |
2451 | service.monitor_services() |
2452 | |
2453 | === modified file 'src/provisioningserver/pserv_services/tests/test_tftp.py' |
2454 | --- src/provisioningserver/pserv_services/tests/test_tftp.py 2015-05-29 16:47:37 +0000 |
2455 | +++ src/provisioningserver/pserv_services/tests/test_tftp.py 2015-07-03 11:18:28 +0000 |
2456 | @@ -30,7 +30,6 @@ |
2457 | urlparse, |
2458 | ) |
2459 | |
2460 | -from fixtures import EnvironmentVariable |
2461 | from maastesting.factory import factory |
2462 | from maastesting.matchers import ( |
2463 | MockCalledOnceWith, |
2464 | @@ -54,6 +53,7 @@ |
2465 | from provisioningserver.boot import BytesReader |
2466 | from provisioningserver.boot.pxe import PXEBootMethod |
2467 | from provisioningserver.boot.tests.test_pxe import compose_config_path |
2468 | +from provisioningserver.config import ClusterConfiguration |
2469 | from provisioningserver.events import EVENT_TYPES |
2470 | from provisioningserver.pserv_services import tftp as tftp_module |
2471 | from provisioningserver.pserv_services.tftp import ( |
2472 | @@ -63,6 +63,7 @@ |
2473 | TFTPService, |
2474 | UDPServer, |
2475 | ) |
2476 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
2477 | from provisioningserver.tests.test_kernel_opts import make_kernel_parameters |
2478 | from testtools import ExpectedException |
2479 | from testtools.matchers import ( |
2480 | @@ -70,6 +71,7 @@ |
2481 | AllMatch, |
2482 | Equals, |
2483 | HasLength, |
2484 | + Is, |
2485 | IsInstance, |
2486 | MatchesAll, |
2487 | MatchesStructure, |
2488 | @@ -127,6 +129,7 @@ |
2489 | |
2490 | def setUp(self): |
2491 | super(TestTFTPBackend, self).setUp() |
2492 | + self.useFixture(ClusterConfigurationFixture()) |
2493 | from provisioningserver import boot |
2494 | self.patch(boot, "find_mac_via_arp") |
2495 | self.patch(tftp_module, 'log_request') |
2496 | @@ -135,10 +138,11 @@ |
2497 | temp_dir = self.make_dir() |
2498 | generator_url = "http://%s.example.com/%s" % ( |
2499 | factory.make_name("domain"), factory.make_name("path")) |
2500 | - backend = TFTPBackend(temp_dir, generator_url) |
2501 | + backend = TFTPBackend(temp_dir, generator_url, sentinel.uuid) |
2502 | self.assertEqual((True, False), (backend.can_read, backend.can_write)) |
2503 | self.assertEqual(temp_dir, backend.base.path) |
2504 | self.assertEqual(generator_url, backend.generator_url.geturl()) |
2505 | + self.assertIs(backend.cluster_uuid, sentinel.uuid) |
2506 | |
2507 | def test_get_generator_url(self): |
2508 | # get_generator_url() merges the parameters obtained from the request |
2509 | @@ -146,7 +150,7 @@ |
2510 | mac = factory.make_mac_address("-") |
2511 | dummy = factory.make_name("dummy").encode("ascii") |
2512 | backend_url = b"http://example.com/?" + urlencode({b"dummy": dummy}) |
2513 | - backend = TFTPBackend(self.make_dir(), backend_url) |
2514 | + backend = TFTPBackend(self.make_dir(), backend_url, sentinel.uuid) |
2515 | # params is an example of the parameters obtained from a request. |
2516 | params = {"mac": mac} |
2517 | generator_url = urlparse(backend.get_generator_url(params)) |
2518 | @@ -161,7 +165,8 @@ |
2519 | def get_reader(self, data): |
2520 | temp_file = self.make_file(name="example", contents=data) |
2521 | temp_dir = os.path.dirname(temp_file) |
2522 | - backend = TFTPBackend(temp_dir, "http://nowhere.example.com/") |
2523 | + backend = TFTPBackend( |
2524 | + temp_dir, "http://nowhere.example.com/", sentinel.uuid) |
2525 | return backend.get_reader("example") |
2526 | |
2527 | @inlineCallbacks |
2528 | @@ -188,7 +193,8 @@ |
2529 | factory.make_file(os.path.join(temp_dir, subdir), filename, data) |
2530 | |
2531 | path = '\\%s\\%s' % (subdir, filename) |
2532 | - backend = TFTPBackend(temp_dir, "http://nowhere.example.com/") |
2533 | + backend = TFTPBackend( |
2534 | + temp_dir, "http://nowhere.example.com/", sentinel.uuid) |
2535 | reader = yield backend.get_reader(path) |
2536 | |
2537 | self.addCleanup(reader.finish) |
2538 | @@ -219,9 +225,11 @@ |
2539 | |
2540 | @inlineCallbacks |
2541 | def test_get_reader_converts_404s_to_tftp_error(self): |
2542 | - self.useFixture(EnvironmentVariable("CLUSTER_UUID", "foobar")) |
2543 | + with ClusterConfiguration.open() as config: |
2544 | + config.cluster_uuid = factory.make_UUID() |
2545 | |
2546 | - backend = TFTPBackend(self.make_dir(), "http://example.com/") |
2547 | + backend = TFTPBackend( |
2548 | + self.make_dir(), "http://example.com/", sentinel.uuid) |
2549 | get_page = self.patch(backend, 'get_page') |
2550 | get_page.side_effect = twisted.web.error.Error(httplib.NOT_FOUND) |
2551 | |
2552 | @@ -230,11 +238,13 @@ |
2553 | |
2554 | @inlineCallbacks |
2555 | def test_get_reader_converts_other_exceptions_to_tftp_error(self): |
2556 | - self.useFixture(EnvironmentVariable("CLUSTER_UUID", "foobar")) |
2557 | + with ClusterConfiguration.open() as config: |
2558 | + config.cluster_uuid = factory.make_UUID() |
2559 | |
2560 | exception_type = factory.make_exception_type() |
2561 | exception_message = factory.make_string() |
2562 | - backend = TFTPBackend(self.make_dir(), "http://example.com/") |
2563 | + backend = TFTPBackend( |
2564 | + self.make_dir(), "http://example.com/", sentinel.uuid) |
2565 | get_page = self.patch(backend, 'get_page') |
2566 | get_page.side_effect = exception_type(exception_message) |
2567 | |
2568 | @@ -257,11 +267,12 @@ |
2569 | # For paths matching PXEBootMethod.match_path, TFTPBackend.get_reader() |
2570 | # returns a Deferred that will yield a BytesReader. |
2571 | cluster_uuid = factory.make_UUID() |
2572 | - get_cluster_uuid = self.patch(tftp_module, 'get_cluster_uuid') |
2573 | - get_cluster_uuid.return_value = cluster_uuid |
2574 | + with ClusterConfiguration.open() as config: |
2575 | + config.cluster_uuid = cluster_uuid |
2576 | mac = factory.make_mac_address("-") |
2577 | config_path = compose_config_path(mac) |
2578 | - backend = TFTPBackend(self.make_dir(), b"http://example.com/") |
2579 | + backend = TFTPBackend( |
2580 | + self.make_dir(), b"http://example.com/", cluster_uuid) |
2581 | # python-tx-tftp sets up call context so that backends can discover |
2582 | # more about the environment in which they're running. |
2583 | call_context = {"local": local, "remote": remote} |
2584 | @@ -318,7 +329,8 @@ |
2585 | # get_boot_method_reader() takes a dict() of parameters and returns an |
2586 | # `IReader` of a PXE configuration, rendered by |
2587 | # `PXEBootMethod.get_reader`. |
2588 | - backend = TFTPBackend(self.make_dir(), b"http://example.com/") |
2589 | + backend = TFTPBackend( |
2590 | + self.make_dir(), b"http://example.com/", sentinel.uuid) |
2591 | # Fake configuration parameters, as discovered from the file path. |
2592 | fake_params = {"mac": factory.make_mac_address("-")} |
2593 | # Fake kernel configuration parameters, as returned from the API call. |
2594 | @@ -356,10 +368,11 @@ |
2595 | # arch field of the parameters (mapping from pxe to maas |
2596 | # namespace). |
2597 | cluster_uuid = factory.make_UUID() |
2598 | - get_cluster_uuid = self.patch(tftp_module, 'get_cluster_uuid') |
2599 | - get_cluster_uuid.return_value = cluster_uuid |
2600 | + with ClusterConfiguration.open() as config: |
2601 | + config.cluster_uuid = cluster_uuid |
2602 | config_path = "pxelinux.cfg/default-arm" |
2603 | - backend = TFTPBackend(self.make_dir(), b"http://example.com/") |
2604 | + backend = TFTPBackend( |
2605 | + self.make_dir(), b"http://example.com/", cluster_uuid) |
2606 | # python-tx-tftp sets up call context so that backends can discover |
2607 | # more about the environment in which they're running. |
2608 | call_context = { |
2609 | @@ -400,7 +413,7 @@ |
2610 | example_port = factory.pick_port() |
2611 | tftp_service = TFTPService( |
2612 | resource_root=example_root, generator=example_generator, |
2613 | - port=example_port) |
2614 | + port=example_port, uuid=sentinel.uuid) |
2615 | tftp_service.updateServers() |
2616 | # The "tftp" service is a multi-service containing UDP servers for |
2617 | # each interface defined by get_all_interface_addresses(). |
2618 | @@ -418,7 +431,10 @@ |
2619 | Equals(example_root)), |
2620 | AfterPreprocessing( |
2621 | lambda backend: backend.generator_url.geturl(), |
2622 | - Equals(example_generator))) |
2623 | + Equals(example_generator)), |
2624 | + AfterPreprocessing( |
2625 | + lambda backend: backend.cluster_uuid, |
2626 | + Is(sentinel.uuid))) |
2627 | expected_protocol = MatchesAll( |
2628 | IsInstance(TFTP), |
2629 | AfterPreprocessing( |
2630 | @@ -452,7 +468,7 @@ |
2631 | |
2632 | tftp_service = TFTPService( |
2633 | resource_root=self.make_dir(), generator="http://mighty/wind", |
2634 | - port=factory.pick_port()) |
2635 | + port=factory.pick_port(), uuid=sentinel.uuid) |
2636 | tftp_service.updateServers() |
2637 | |
2638 | # The child services of tftp_services are named after the |
2639 | @@ -494,7 +510,7 @@ |
2640 | |
2641 | tftp_service = TFTPService( |
2642 | resource_root=self.make_dir(), generator="http://mighty/wind", |
2643 | - port=factory.pick_port()) |
2644 | + port=factory.pick_port(), uuid=sentinel.uuid) |
2645 | tftp_service.updateServers() |
2646 | |
2647 | # Only the "normal" addresses have been used. |
2648 | |
2649 | === modified file 'src/provisioningserver/pserv_services/tftp.py' |
2650 | --- src/provisioningserver/pserv_services/tftp.py 2015-05-01 13:30:13 +0000 |
2651 | +++ src/provisioningserver/pserv_services/tftp.py 2015-07-03 11:18:28 +0000 |
2652 | @@ -35,7 +35,6 @@ |
2653 | BootMethodRegistry, |
2654 | get_remote_mac, |
2655 | ) |
2656 | -from provisioningserver.cluster_config import get_cluster_uuid |
2657 | from provisioningserver.drivers import ArchitectureRegistry |
2658 | from provisioningserver.events import ( |
2659 | EVENT_TYPES, |
2660 | @@ -109,16 +108,18 @@ |
2661 | fetch files at many similar paths which must not be passed on. |
2662 | """ |
2663 | |
2664 | - def __init__(self, base_path, generator_url): |
2665 | + def __init__(self, base_path, generator_url, cluster_uuid): |
2666 | """ |
2667 | :param base_path: The root directory for this TFTP server. |
2668 | :param generator_url: The URL which can be queried for the PXE |
2669 | config. See `get_generator_url` for the types of queries it is |
2670 | expected to accept. |
2671 | + :param cluster_uuid: The cluster's UUID, as a string. |
2672 | """ |
2673 | super(TFTPBackend, self).__init__( |
2674 | base_path, can_read=True, can_write=False) |
2675 | self.generator_url = urlparse(generator_url) |
2676 | + self.cluster_uuid = cluster_uuid |
2677 | self.fetcher = PageFetcher(agent=self.__class__) |
2678 | self.get_page = self.fetcher.get |
2679 | |
2680 | @@ -223,7 +224,7 @@ |
2681 | params["local"] = local_host |
2682 | remote_host, remote_port = tftp.get_remote_address() |
2683 | params["remote"] = remote_host |
2684 | - params["cluster_uuid"] = get_cluster_uuid() |
2685 | + params["cluster_uuid"] = self.cluster_uuid |
2686 | d = self.get_boot_method_reader(boot_method, params) |
2687 | return d |
2688 | |
2689 | @@ -314,16 +315,17 @@ |
2690 | |
2691 | """ |
2692 | |
2693 | - def __init__(self, resource_root, port, generator): |
2694 | + def __init__(self, resource_root, port, generator, uuid): |
2695 | """ |
2696 | :param resource_root: The root directory for this TFTP server. |
2697 | :param port: The port on which each server should be started. |
2698 | :param generator: The URL to be queried for PXE configuration. |
2699 | This will normally point to the `pxeconfig` endpoint on the |
2700 | region-controller API. |
2701 | + :param uuid: The cluster's UUID, as a string. |
2702 | """ |
2703 | super(TFTPService, self).__init__() |
2704 | - self.backend = TFTPBackend(resource_root, generator) |
2705 | + self.backend = TFTPBackend(resource_root, generator, uuid) |
2706 | self.port = port |
2707 | # Establish a periodic call to self.updateServers() every 45 |
2708 | # seconds, so that this service eventually converges on truth. |
2709 | |
2710 | === modified file 'src/provisioningserver/rpc/boot_images.py' |
2711 | --- src/provisioningserver/rpc/boot_images.py 2015-05-29 16:47:37 +0000 |
2712 | +++ src/provisioningserver/rpc/boot_images.py 2015-07-03 11:18:28 +0000 |
2713 | @@ -18,13 +18,12 @@ |
2714 | "is_import_boot_images_running", |
2715 | ] |
2716 | |
2717 | -import os |
2718 | from urlparse import urlparse |
2719 | |
2720 | from provisioningserver import concurrency |
2721 | from provisioningserver.auth import get_maas_user_gpghome |
2722 | from provisioningserver.boot import tftppath |
2723 | -from provisioningserver.config import BOOT_RESOURCES_STORAGE |
2724 | +from provisioningserver.config import ClusterConfiguration |
2725 | from provisioningserver.import_images import boot_resources |
2726 | from provisioningserver.utils.env import environment_variables |
2727 | from provisioningserver.utils.twisted import synchronous |
2728 | @@ -41,10 +40,11 @@ |
2729 | of IO, as this function is called often. To update the cache call |
2730 | `reload_boot_images`. |
2731 | """ |
2732 | + |
2733 | global CACHED_BOOT_IMAGES |
2734 | if CACHED_BOOT_IMAGES is None: |
2735 | - CACHED_BOOT_IMAGES = tftppath.list_boot_images( |
2736 | - os.path.join(BOOT_RESOURCES_STORAGE, 'current')) |
2737 | + with ClusterConfiguration.open() as config: |
2738 | + CACHED_BOOT_IMAGES = tftppath.list_boot_images(config.tftp_root) |
2739 | return CACHED_BOOT_IMAGES |
2740 | |
2741 | |
2742 | @@ -52,8 +52,8 @@ |
2743 | """Update the cached boot images so `list_boot_images` returns the |
2744 | most up-to-date boot images list.""" |
2745 | global CACHED_BOOT_IMAGES |
2746 | - CACHED_BOOT_IMAGES = tftppath.list_boot_images( |
2747 | - os.path.join(BOOT_RESOURCES_STORAGE, 'current')) |
2748 | + with ClusterConfiguration.open() as config: |
2749 | + CACHED_BOOT_IMAGES = tftppath.list_boot_images(config.tftp_root) |
2750 | |
2751 | |
2752 | def get_hosts_from_sources(sources): |
2753 | |
2754 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
2755 | --- src/provisioningserver/rpc/clusterservice.py 2015-05-29 16:47:37 +0000 |
2756 | +++ src/provisioningserver/rpc/clusterservice.py 2015-07-03 11:18:28 +0000 |
2757 | @@ -26,10 +26,7 @@ |
2758 | from apiclient.creds import convert_string_to_tuple |
2759 | from apiclient.utils import ascii_url |
2760 | from provisioningserver import concurrency |
2761 | -from provisioningserver.cluster_config import ( |
2762 | - get_cluster_uuid, |
2763 | - get_maas_url, |
2764 | -) |
2765 | +from provisioningserver.config import ClusterConfiguration |
2766 | from provisioningserver.drivers import ( |
2767 | ArchitectureRegistry, |
2768 | PowerTypeRegistry, |
2769 | @@ -135,7 +132,8 @@ |
2770 | Implementation of |
2771 | :py:class:`~provisioningserver.rpc.cluster.Identify`. |
2772 | """ |
2773 | - return {b"ident": get_cluster_uuid().decode("ascii")} |
2774 | + with ClusterConfiguration.open() as config: |
2775 | + return {b"ident": config.cluster_uuid} |
2776 | |
2777 | @cluster.Authenticate.responder |
2778 | def authenticate(self, message): |
2779 | @@ -508,9 +506,11 @@ |
2780 | returnValue(digest == digest_local) |
2781 | |
2782 | def registerWithRegion(self): |
2783 | - uuid = get_cluster_uuid() |
2784 | + with ClusterConfiguration.open() as config: |
2785 | + uuid = config.cluster_uuid |
2786 | + url = config.maas_url |
2787 | + |
2788 | networks = discover_networks() |
2789 | - url = get_maas_url() |
2790 | |
2791 | def cb_register(_): |
2792 | log.msg( |
2793 | @@ -760,9 +760,10 @@ |
2794 | @staticmethod |
2795 | def _get_rpc_info_url(): |
2796 | """Return the URL to the RPC infomation page on the region.""" |
2797 | - url = urlparse(get_maas_url()) |
2798 | - url = url._replace(path="%s/rpc/" % url.path.rstrip("/")) |
2799 | - url = url.geturl() |
2800 | + with ClusterConfiguration.open() as config: |
2801 | + url = urlparse(config.maas_url) |
2802 | + url = url._replace(path="%s/rpc/" % url.path.rstrip("/")) |
2803 | + url = url.geturl() |
2804 | return ascii_url(url) |
2805 | |
2806 | @classmethod |
2807 | |
2808 | === modified file 'src/provisioningserver/rpc/tags.py' |
2809 | --- src/provisioningserver/rpc/tags.py 2015-05-29 16:47:37 +0000 |
2810 | +++ src/provisioningserver/rpc/tags.py 2015-07-03 11:18:28 +0000 |
2811 | @@ -21,10 +21,7 @@ |
2812 | MAASDispatcher, |
2813 | MAASOAuth, |
2814 | ) |
2815 | -from provisioningserver.cluster_config import ( |
2816 | - get_cluster_uuid, |
2817 | - get_maas_url, |
2818 | -) |
2819 | +from provisioningserver.config import ClusterConfiguration |
2820 | from provisioningserver.tags import process_node_tags |
2821 | from provisioningserver.utils.twisted import synchronous |
2822 | |
2823 | @@ -38,9 +35,12 @@ |
2824 | :param tag_nsmap: The namespace map as used by LXML's ETree library. |
2825 | :param credentials: A 3-tuple of OAuth credentials. |
2826 | """ |
2827 | + with ClusterConfiguration.open() as config: |
2828 | + cluster_uuid = config.cluster_uuid |
2829 | + maas_url = config.maas_url |
2830 | client = MAASClient( |
2831 | auth=MAASOAuth(*credentials), dispatcher=MAASDispatcher(), |
2832 | - base_url=get_maas_url()) |
2833 | + base_url=maas_url) |
2834 | process_node_tags( |
2835 | tag_name=tag_name, tag_definition=tag_definition, tag_nsmap=tag_nsmap, |
2836 | - client=client, nodegroup_uuid=get_cluster_uuid()) |
2837 | + client=client, nodegroup_uuid=cluster_uuid) |
2838 | |
2839 | === modified file 'src/provisioningserver/rpc/testing/__init__.py' |
2840 | --- src/provisioningserver/rpc/testing/__init__.py 2015-05-22 15:36:53 +0000 |
2841 | +++ src/provisioningserver/rpc/testing/__init__.py 2015-07-03 11:18:28 +0000 |
2842 | @@ -29,7 +29,6 @@ |
2843 | from os import path |
2844 | |
2845 | import fixtures |
2846 | -from fixtures import EnvironmentVariable |
2847 | from maastesting.factory import factory |
2848 | from maastesting.fixtures import TempDirectory |
2849 | from maastesting.twisted import always_succeed_with |
2850 | @@ -176,11 +175,6 @@ |
2851 | @asynchronous(timeout=15) |
2852 | def setUp(self): |
2853 | super(MockClusterToRegionRPCFixtureBase, self).setUp() |
2854 | - # Ensure that we have MAAS_URL and CLUSTER_UUID set. |
2855 | - self.useFixture(EnvironmentVariable( |
2856 | - "MAAS_URL", factory.make_simple_http_url())) |
2857 | - self.useFixture(EnvironmentVariable( |
2858 | - "CLUSTER_UUID", factory.make_UUID().encode("ascii"))) |
2859 | # Use an inert clock with ClusterClientService so it doesn't update |
2860 | # itself except when we ask it to. |
2861 | self.rpc_service = ClusterClientService(Clock()) |
2862 | |
2863 | === modified file 'src/provisioningserver/rpc/tests/test_boot_images.py' |
2864 | --- src/provisioningserver/rpc/tests/test_boot_images.py 2015-05-29 16:47:37 +0000 |
2865 | +++ src/provisioningserver/rpc/tests/test_boot_images.py 2015-07-03 11:18:28 +0000 |
2866 | @@ -29,7 +29,6 @@ |
2867 | ) |
2868 | from provisioningserver import concurrency |
2869 | from provisioningserver.boot import tftppath |
2870 | -from provisioningserver.config import BOOT_RESOURCES_STORAGE |
2871 | from provisioningserver.import_images import boot_resources |
2872 | from provisioningserver.rpc import boot_images |
2873 | from provisioningserver.rpc.boot_images import ( |
2874 | @@ -40,7 +39,10 @@ |
2875 | list_boot_images, |
2876 | reload_boot_images, |
2877 | ) |
2878 | -from provisioningserver.testing.config import BootSourcesFixture |
2879 | +from provisioningserver.testing.config import ( |
2880 | + BootSourcesFixture, |
2881 | + ClusterConfigurationFixture, |
2882 | +) |
2883 | from provisioningserver.testing.testcase import PservTestCase |
2884 | from provisioningserver.utils.twisted import pause |
2885 | from testtools.matchers import Equals |
2886 | @@ -64,14 +66,18 @@ |
2887 | |
2888 | class TestListBootImages(PservTestCase): |
2889 | |
2890 | + def setUp(self): |
2891 | + super(TestListBootImages, self).setUp() |
2892 | + self.tftp_root = self.make_dir() |
2893 | + self.useFixture(ClusterConfigurationFixture(tftp_root=self.tftp_root)) |
2894 | + |
2895 | def test__calls_list_boot_images_with_boot_resource_storage(self): |
2896 | self.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
2897 | mock_list_boot_images = self.patch(tftppath, 'list_boot_images') |
2898 | list_boot_images() |
2899 | self.assertThat( |
2900 | mock_list_boot_images, |
2901 | - MockCalledOnceWith( |
2902 | - os.path.join(BOOT_RESOURCES_STORAGE, "current"))) |
2903 | + MockCalledOnceWith(self.tftp_root)) |
2904 | |
2905 | def test__calls_list_boot_images_when_cache_is_None(self): |
2906 | self.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
2907 | |
2908 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' |
2909 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2015-06-16 21:10:26 +0000 |
2910 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2015-07-03 11:18:28 +0000 |
2911 | @@ -29,7 +29,6 @@ |
2912 | from urlparse import urlparse |
2913 | |
2914 | from apiclient.creds import convert_tuple_to_string |
2915 | -from fixtures import EnvironmentVariable |
2916 | from maastesting.factory import factory |
2917 | from maastesting.matchers import ( |
2918 | IsUnfiredDeferred, |
2919 | @@ -57,10 +56,6 @@ |
2920 | from provisioningserver import concurrency |
2921 | from provisioningserver.boot import tftppath |
2922 | from provisioningserver.boot.tests.test_tftppath import make_osystem |
2923 | -from provisioningserver.cluster_config import ( |
2924 | - get_cluster_uuid, |
2925 | - get_maas_url, |
2926 | -) |
2927 | from provisioningserver.dhcp.testing.config import make_subnet_config |
2928 | from provisioningserver.drivers.osystem import ( |
2929 | OperatingSystem, |
2930 | @@ -108,6 +103,7 @@ |
2931 | StubOS, |
2932 | ) |
2933 | from provisioningserver.security import set_shared_secret_on_filesystem |
2934 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
2935 | from testtools import ExpectedException |
2936 | from testtools.deferredruntest import extract_result |
2937 | from testtools.matchers import ( |
2938 | @@ -154,9 +150,8 @@ |
2939 | |
2940 | def test_identify_reports_cluster_uuid(self): |
2941 | example_uuid = factory.make_UUID() |
2942 | - |
2943 | - get_cluster_uuid = self.patch(clusterservice, "get_cluster_uuid") |
2944 | - get_cluster_uuid.return_value = example_uuid |
2945 | + self.useFixture(ClusterConfigurationFixture( |
2946 | + cluster_uuid=example_uuid)) |
2947 | |
2948 | d = call_responder(Cluster(), cluster.Identify, {}) |
2949 | |
2950 | @@ -232,6 +227,7 @@ |
2951 | |
2952 | @inlineCallbacks |
2953 | def test_list_boot_images_can_be_called(self): |
2954 | + self.useFixture(ClusterConfigurationFixture()) |
2955 | self.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
2956 | list_boot_images = self.patch(tftppath, "list_boot_images") |
2957 | list_boot_images.return_value = [] |
2958 | @@ -254,14 +250,16 @@ |
2959 | labels = "beta-1", "release" |
2960 | purposes = "commissioning", "install", "xinstall" |
2961 | |
2962 | - # Create a TFTP file tree with a variety of subdirectories. |
2963 | + # Populate a TFTP file tree with a variety of subdirectories. |
2964 | tftpdir = self.make_dir() |
2965 | current_dir = os.path.join(tftpdir, 'current') |
2966 | os.makedirs(current_dir) |
2967 | for options in product(osystems, archs, subarchs, releases, labels): |
2968 | os.makedirs(os.path.join(current_dir, *options)) |
2969 | make_osystem(self, options[0], purposes) |
2970 | - self.patch(boot_images, 'BOOT_RESOURCES_STORAGE', tftpdir) |
2971 | + |
2972 | + self.useFixture(ClusterConfigurationFixture( |
2973 | + tftp_root=os.path.join(tftpdir, "current"))) |
2974 | self.patch(boot_images, 'CACHED_BOOT_IMAGES', None) |
2975 | |
2976 | expected_images = [ |
2977 | @@ -470,7 +468,7 @@ |
2978 | def test__get_rpc_info_url(self): |
2979 | maas_url = "http://%s/%s/" % ( |
2980 | factory.make_hostname(), factory.make_name("path")) |
2981 | - self.useFixture(EnvironmentVariable("MAAS_URL", maas_url)) |
2982 | + self.useFixture(ClusterConfigurationFixture(maas_url=maas_url)) |
2983 | expected_rpc_info_url = maas_url + "rpc/" |
2984 | observed_rpc_info_url = ClusterClientService._get_rpc_info_url() |
2985 | self.assertThat(observed_rpc_info_url, Equals(expected_rpc_info_url)) |
2986 | @@ -550,7 +548,7 @@ |
2987 | def test_update_calls__update_connections(self): |
2988 | maas_url = "http://%s/%s/" % ( |
2989 | factory.make_hostname(), factory.make_name("path")) |
2990 | - self.useFixture(EnvironmentVariable("MAAS_URL", maas_url)) |
2991 | + self.useFixture(ClusterConfigurationFixture(maas_url=maas_url)) |
2992 | getPage = self.patch(clusterservice, "getPage") |
2993 | getPage.return_value = succeed(self.example_rpc_info_view_response) |
2994 | service = ClusterClientService(Clock()) |
2995 | @@ -817,10 +815,9 @@ |
2996 | |
2997 | def setUp(self): |
2998 | super(TestClusterClient, self).setUp() |
2999 | - self.useFixture(EnvironmentVariable( |
3000 | - "MAAS_URL", factory.make_simple_http_url())) |
3001 | - self.useFixture(EnvironmentVariable( |
3002 | - "CLUSTER_UUID", factory.make_UUID().encode("ascii"))) |
3003 | + self.useFixture(ClusterConfigurationFixture( |
3004 | + maas_url=factory.make_simple_http_url(), |
3005 | + cluster_uuid=factory.make_UUID())) |
3006 | |
3007 | def make_running_client(self): |
3008 | client = clusterservice.ClusterClient( |
3009 | @@ -1220,15 +1217,18 @@ |
3010 | |
3011 | @inlineCallbacks |
3012 | def test_registerWithRegion_end_to_end(self): |
3013 | + maas_url = factory.make_simple_http_url() |
3014 | + cluster_uuid = factory.make_UUID() |
3015 | + self.useFixture(ClusterConfigurationFixture( |
3016 | + maas_url=maas_url, cluster_uuid=cluster_uuid)) |
3017 | fixture = self.useFixture(MockLiveClusterToRegionRPCFixture()) |
3018 | protocol, connecting = fixture.makeEventLoop() |
3019 | self.addCleanup((yield connecting)) |
3020 | yield getRegionClient() |
3021 | self.assertThat( |
3022 | protocol.Register, MockCalledOnceWith( |
3023 | - protocol, uuid=get_cluster_uuid(), |
3024 | - networks=discover_networks(), |
3025 | - url=urlparse(get_maas_url()))) |
3026 | + protocol, uuid=cluster_uuid, networks=discover_networks(), |
3027 | + url=urlparse(maas_url))) |
3028 | |
3029 | |
3030 | class TestClusterProtocol_ListSupportedArchitectures(MAASTestCase): |
3031 | @@ -1859,11 +1859,7 @@ |
3032 | |
3033 | @inlineCallbacks |
3034 | def test_happy_path(self): |
3035 | - get_maas_url = self.patch_autospec(tags, "get_maas_url") |
3036 | - get_maas_url.return_value = sentinel.maas_url |
3037 | - get_cluster_uuid = self.patch_autospec(tags, "get_cluster_uuid") |
3038 | - get_cluster_uuid.return_value = sentinel.cluster_uuid |
3039 | - |
3040 | + self.useFixture(ClusterConfigurationFixture()) |
3041 | # Prevent real work being done, which would involve HTTP calls. |
3042 | self.patch_autospec(tags, "process_node_tags") |
3043 | |
3044 | |
3045 | === modified file 'src/provisioningserver/rpc/tests/test_tags.py' |
3046 | --- src/provisioningserver/rpc/tests/test_tags.py 2015-05-29 16:47:37 +0000 |
3047 | +++ src/provisioningserver/rpc/tests/test_tags.py 2015-07-03 11:18:28 +0000 |
3048 | @@ -27,16 +27,17 @@ |
3049 | sentinel, |
3050 | ) |
3051 | from provisioningserver.rpc import tags |
3052 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
3053 | |
3054 | |
3055 | class TestEvaluateTag(MAASTestCase): |
3056 | |
3057 | def setUp(self): |
3058 | super(TestEvaluateTag, self).setUp() |
3059 | - get_maas_url = self.patch_autospec(tags, "get_maas_url") |
3060 | - get_maas_url.return_value = sentinel.maas_url |
3061 | - get_cluster_uuid = self.patch_autospec(tags, "get_cluster_uuid") |
3062 | - get_cluster_uuid.return_value = sentinel.cluster_uuid |
3063 | + self.mock_cluster_uuid = factory.make_UUID() |
3064 | + self.mock_url = factory.make_simple_http_url() |
3065 | + self.useFixture(ClusterConfigurationFixture( |
3066 | + cluster_uuid=self.mock_cluster_uuid, maas_url=self.mock_url)) |
3067 | |
3068 | def test__calls_process_node_tags(self): |
3069 | credentials = "aaa", "bbb", "ccc" |
3070 | @@ -49,7 +50,7 @@ |
3071 | tag_name=sentinel.tag_name, |
3072 | tag_definition=sentinel.tag_definition, |
3073 | tag_nsmap=sentinel.tag_nsmap, client=ANY, |
3074 | - nodegroup_uuid=sentinel.cluster_uuid)) |
3075 | + nodegroup_uuid=self.mock_cluster_uuid)) |
3076 | |
3077 | def test__constructs_client_with_credentials(self): |
3078 | consumer_key = factory.make_name("ckey") |
3079 | @@ -66,7 +67,7 @@ |
3080 | |
3081 | client = tags.process_node_tags.call_args[1]["client"] |
3082 | self.assertIsInstance(client, MAASClient) |
3083 | - self.assertEqual(sentinel.maas_url, client.url) |
3084 | + self.assertEqual(self.mock_url, client.url) |
3085 | self.assertIsInstance(client.dispatcher, MAASDispatcher) |
3086 | self.assertIsInstance(client.auth, MAASOAuth) |
3087 | self.assertThat(tags.MAASOAuth, MockCalledOnceWith( |
3088 | |
3089 | === modified file 'src/provisioningserver/testing/config.py' |
3090 | --- src/provisioningserver/testing/config.py 2015-05-29 16:47:37 +0000 |
3091 | +++ src/provisioningserver/testing/config.py 2015-07-03 11:18:28 +0000 |
3092 | @@ -15,10 +15,8 @@ |
3093 | __all__ = [ |
3094 | "BootSourcesFixture", |
3095 | "ClusterConfigurationFixture", |
3096 | - "ConfigFixture", |
3097 | "ConfigFixtureBase", |
3098 | "ConfigurationFixtureBase", |
3099 | - "set_tftp_root", |
3100 | ] |
3101 | |
3102 | from os import path |
3103 | @@ -31,7 +29,6 @@ |
3104 | from provisioningserver.config import ( |
3105 | BootSources, |
3106 | ClusterConfiguration, |
3107 | - Config, |
3108 | ) |
3109 | import yaml |
3110 | |
3111 | @@ -69,15 +66,6 @@ |
3112 | self.useFixture(EnvironmentVariableFixture(name, value)) |
3113 | |
3114 | |
3115 | -class ConfigFixture(ConfigFixtureBase): |
3116 | - """Fixture to substitute for :class:`Config` in tests.""" |
3117 | - |
3118 | - schema = Config |
3119 | - |
3120 | - def __init__(self, config=None, name='pserv.yaml'): |
3121 | - super(ConfigFixture, self).__init__(config=config, name=name) |
3122 | - |
3123 | - |
3124 | class BootSourcesFixture(ConfigFixtureBase): |
3125 | """Fixture to substitute for :class:`BootSources` in tests. |
3126 | |
3127 | @@ -93,14 +81,6 @@ |
3128 | super(BootSourcesFixture, self).__init__(config=sources, name=name) |
3129 | |
3130 | |
3131 | -def set_tftp_root(tftproot): |
3132 | - """Create a `ConfigFixture` fixture that sets the TFTP root directory. |
3133 | - |
3134 | - Add the resulting fixture to your test using `self.useFixture`. |
3135 | - """ |
3136 | - return ConfigFixture({'tftp': {'resource_root': tftproot}}) |
3137 | - |
3138 | - |
3139 | class ConfigurationFixtureBase(Fixture): |
3140 | """Base class for new-style configuration testing fixtures. |
3141 | |
3142 | |
3143 | === modified file 'src/provisioningserver/testing/testcase.py' |
3144 | --- src/provisioningserver/testing/testcase.py 2015-05-29 16:47:37 +0000 |
3145 | +++ src/provisioningserver/testing/testcase.py 2015-07-03 11:18:28 +0000 |
3146 | @@ -16,22 +16,13 @@ |
3147 | 'PservTestCase', |
3148 | ] |
3149 | |
3150 | -from fixtures import EnvironmentVariableFixture |
3151 | from maastesting import testcase |
3152 | -from maastesting.factory import factory |
3153 | from twisted.internet import reactor |
3154 | from twisted.python import threadable |
3155 | |
3156 | |
3157 | class PservTestCase(testcase.MAASTestCase): |
3158 | |
3159 | - def make_maas_url(self): |
3160 | - return 'http://127.0.0.1/%s' % factory.make_name('path') |
3161 | - |
3162 | - def set_maas_url(self): |
3163 | - self.useFixture( |
3164 | - EnvironmentVariableFixture("MAAS_URL", self.make_maas_url())) |
3165 | - |
3166 | def register_as_io_thread(self): |
3167 | """Make the current thread the IO thread. |
3168 | |
3169 | |
3170 | === removed file 'src/provisioningserver/tests/test_cluster_config.py' |
3171 | --- src/provisioningserver/tests/test_cluster_config.py 2015-05-29 16:47:37 +0000 |
3172 | +++ src/provisioningserver/tests/test_cluster_config.py 1970-01-01 00:00:00 +0000 |
3173 | @@ -1,48 +0,0 @@ |
3174 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
3175 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
3176 | - |
3177 | -"""Tests for `provisioningserver.cluster_config`.""" |
3178 | - |
3179 | -from __future__ import ( |
3180 | - absolute_import, |
3181 | - print_function, |
3182 | - unicode_literals, |
3183 | - ) |
3184 | - |
3185 | -str = None |
3186 | - |
3187 | -__metaclass__ = type |
3188 | -__all__ = [] |
3189 | - |
3190 | -from fixtures import EnvironmentVariableFixture |
3191 | -from maastesting.factory import factory |
3192 | -from maastesting.testcase import MAASTestCase |
3193 | -from provisioningserver.cluster_config import ( |
3194 | - get_cluster_uuid, |
3195 | - get_cluster_variable, |
3196 | - get_maas_url, |
3197 | -) |
3198 | - |
3199 | - |
3200 | -class TestClusterConfig(MAASTestCase): |
3201 | - |
3202 | - def test_get_cluster_variable_reads_env(self): |
3203 | - var = factory.make_name('variable') |
3204 | - value = factory.make_name('value') |
3205 | - self.useFixture(EnvironmentVariableFixture(var, value)) |
3206 | - self.assertEqual(value, get_cluster_variable(var)) |
3207 | - |
3208 | - def test_get_cluster_variable_fails_if_not_set(self): |
3209 | - self.assertRaises( |
3210 | - AssertionError, |
3211 | - get_cluster_variable, factory.make_name('nonexistent-variable')) |
3212 | - |
3213 | - def test_get_cluster_uuid_reads_CLUSTER_UUID(self): |
3214 | - uuid = factory.make_name('uuid') |
3215 | - self.useFixture(EnvironmentVariableFixture('CLUSTER_UUID', uuid)) |
3216 | - self.assertEqual(uuid, get_cluster_uuid()) |
3217 | - |
3218 | - def test_get_maas_url_reads_MAAS_URL(self): |
3219 | - maas_url = factory.make_name('maas_url') |
3220 | - self.useFixture(EnvironmentVariableFixture('MAAS_URL', maas_url)) |
3221 | - self.assertEqual(maas_url, get_maas_url()) |
3222 | |
3223 | === modified file 'src/provisioningserver/tests/test_cluster_config_command.py' |
3224 | --- src/provisioningserver/tests/test_cluster_config_command.py 2015-04-09 16:21:27 +0000 |
3225 | +++ src/provisioningserver/tests/test_cluster_config_command.py 2015-07-03 11:18:28 +0000 |
3226 | @@ -1,7 +1,7 @@ |
3227 | # Copyright 2015 Canonical Ltd. This software is licensed under the |
3228 | # GNU Affero General Public License version 3 (see the file LICENSE). |
3229 | |
3230 | -"""Tests for `MAAS_URL` configuration update code.""" |
3231 | +"""Tests for configuration update code.""" |
3232 | |
3233 | from __future__ import ( |
3234 | absolute_import, |
3235 | @@ -28,12 +28,10 @@ |
3236 | patch, |
3237 | ) |
3238 | from provisioningserver import cluster_config_command |
3239 | -from provisioningserver.cluster_config import ( |
3240 | - CLUSTER_CONFIG, |
3241 | - get_config_cluster_variable, |
3242 | - set_config_cluster_variable, |
3243 | +from provisioningserver.config import ( |
3244 | + ClusterConfiguration, |
3245 | + UUID_NOT_SET, |
3246 | ) |
3247 | -from provisioningserver.config import UUID_NOT_SET |
3248 | from provisioningserver.testing.config import ClusterConfigurationFixture |
3249 | from testtools import ExpectedException |
3250 | |
3251 | @@ -134,23 +132,28 @@ |
3252 | def test_config_set_maas_url_sets_url(self): |
3253 | expected = factory.make_simple_http_url() |
3254 | cluster_config_command.run(self.make_args(region_url=expected)) |
3255 | - observed = get_config_cluster_variable(CLUSTER_CONFIG.DB_maas_url) |
3256 | + with ClusterConfiguration.open() as config: |
3257 | + observed = config.maas_url |
3258 | self.assertEqual(expected, observed) |
3259 | |
3260 | def test_config_set_maas_url_without_setting_does_nothing(self): |
3261 | - expected = get_config_cluster_variable(CLUSTER_CONFIG.DB_maas_url) |
3262 | + with ClusterConfiguration.open() as config: |
3263 | + expected = config.maas_url |
3264 | cluster_config_command.run(self.make_args(region_url=None)) |
3265 | - observed = get_config_cluster_variable(CLUSTER_CONFIG.DB_maas_url) |
3266 | + with ClusterConfiguration.open() as config: |
3267 | + observed = config.maas_url |
3268 | self.assertEqual(expected, observed) |
3269 | |
3270 | def test_config_set_cluster_uuid_sets_cluster_uuid(self): |
3271 | expected = unicode(uuid.uuid4()) |
3272 | cluster_config_command.run(self.make_args(uuid=expected)) |
3273 | - observed = get_config_cluster_variable(CLUSTER_CONFIG.DB_cluster_uuid) |
3274 | + with ClusterConfiguration.open() as config: |
3275 | + observed = config.cluster_uuid |
3276 | self.assertEqual(expected, observed) |
3277 | |
3278 | def get_parsed_uuid_from_config(self): |
3279 | - observed = get_config_cluster_variable(CLUSTER_CONFIG.DB_cluster_uuid) |
3280 | + with ClusterConfiguration.open() as config: |
3281 | + observed = config.cluster_uuid |
3282 | try: |
3283 | parsed_observed = unicode(uuid.UUID(observed)) |
3284 | except: |
3285 | @@ -160,10 +163,10 @@ |
3286 | |
3287 | def test_config_set_cluster_uuid_without_setting_does_nothing(self): |
3288 | expected_previous_value = unicode(uuid.uuid4()) |
3289 | - set_config_cluster_variable( |
3290 | - CLUSTER_CONFIG.DB_cluster_uuid, expected_previous_value) |
3291 | - observed_previous_value = get_config_cluster_variable( |
3292 | - CLUSTER_CONFIG.DB_cluster_uuid) |
3293 | + with ClusterConfiguration.open() as config: |
3294 | + config.cluster_uuid = expected_previous_value |
3295 | + with ClusterConfiguration.open() as config: |
3296 | + observed_previous_value = config.cluster_uuid |
3297 | self.assertEqual(expected_previous_value, observed_previous_value) |
3298 | |
3299 | cluster_config_command.run(self.make_args(uuid=None)) |
3300 | @@ -173,8 +176,8 @@ |
3301 | self.assertEqual(parsed_observed, expected_previous_value) |
3302 | |
3303 | def test_config_init_creates_initial_cluster_id(self): |
3304 | - observed_default = get_config_cluster_variable( |
3305 | - CLUSTER_CONFIG.DB_cluster_uuid) |
3306 | + with ClusterConfiguration.open() as config: |
3307 | + observed_default = config.cluster_uuid |
3308 | self.assertEqual(UUID_NOT_SET, observed_default) |
3309 | |
3310 | cluster_config_command.run(self.make_args(init=True)) |
3311 | @@ -184,10 +187,10 @@ |
3312 | |
3313 | def test_config_init_when_already_configured_does_nothing(self): |
3314 | expected_previous_value = unicode(uuid.uuid4()) |
3315 | - set_config_cluster_variable( |
3316 | - CLUSTER_CONFIG.DB_cluster_uuid, expected_previous_value) |
3317 | - observed_previous_value = get_config_cluster_variable( |
3318 | - CLUSTER_CONFIG.DB_cluster_uuid) |
3319 | + with ClusterConfiguration.open() as config: |
3320 | + config.cluster_uuid = expected_previous_value |
3321 | + with ClusterConfiguration.open() as config: |
3322 | + observed_previous_value = config.cluster_uuid |
3323 | self.assertEqual(expected_previous_value, observed_previous_value) |
3324 | |
3325 | cluster_config_command.run(self.make_args(init=True)) |
3326 | @@ -199,27 +202,29 @@ |
3327 | def test_config_set_tftp_port_sets_tftp_port(self): |
3328 | expected = factory.pick_port() |
3329 | cluster_config_command.run(self.make_args(tftp_port=expected)) |
3330 | - observed = get_config_cluster_variable(CLUSTER_CONFIG.DB_tftpport) |
3331 | + with ClusterConfiguration.open() as config: |
3332 | + observed = config.tftp_port |
3333 | self.assertEqual(expected, observed) |
3334 | |
3335 | def test_config_set_tftp_port_without_setting_does_nothing(self): |
3336 | - expected = get_config_cluster_variable(CLUSTER_CONFIG.DB_tftpport) |
3337 | + with ClusterConfiguration.open() as config: |
3338 | + expected = config.tftp_port |
3339 | cluster_config_command.run(self.make_args(tftp_port=None)) |
3340 | - observed = get_config_cluster_variable(CLUSTER_CONFIG.DB_tftpport) |
3341 | + with ClusterConfiguration.open() as config: |
3342 | + observed = config.tftp_port |
3343 | self.assertEqual(expected, observed) |
3344 | |
3345 | def test_config_set_tftp_port_sets_tftp_root(self): |
3346 | expected = self.make_dir() |
3347 | cluster_config_command.run(self.make_args(tftp_root=expected)) |
3348 | - observed = get_config_cluster_variable( |
3349 | - CLUSTER_CONFIG.DB_tftp_resource_root) |
3350 | + with ClusterConfiguration.open() as config: |
3351 | + observed = config.tftp_root |
3352 | self.assertEqual(expected, observed) |
3353 | |
3354 | def test_config_set_tftp_root_without_setting_does_nothing(self): |
3355 | - expected = get_config_cluster_variable( |
3356 | - CLUSTER_CONFIG.DB_tftp_resource_root) |
3357 | + with ClusterConfiguration.open() as config: |
3358 | + expected = config.tftp_root |
3359 | cluster_config_command.run(self.make_args(tftp_root=None)) |
3360 | - observed = get_config_cluster_variable( |
3361 | - CLUSTER_CONFIG.DB_tftp_resource_root) |
3362 | - |
3363 | + with ClusterConfiguration.open() as config: |
3364 | + observed = config.tftp_root |
3365 | self.assertEqual(expected, observed) |
3366 | |
3367 | === modified file 'src/provisioningserver/tests/test_config.py' |
3368 | --- src/provisioningserver/tests/test_config.py 2015-06-24 13:27:05 +0000 |
3369 | +++ src/provisioningserver/tests/test_config.py 2015-07-03 11:18:28 +0000 |
3370 | @@ -15,25 +15,19 @@ |
3371 | __all__ = [] |
3372 | |
3373 | import contextlib |
3374 | -from copy import deepcopy |
3375 | -import errno |
3376 | -from functools import partial |
3377 | -from getpass import getuser |
3378 | -from io import BytesIO |
3379 | from operator import methodcaller |
3380 | import os.path |
3381 | import random |
3382 | import re |
3383 | import sqlite3 |
3384 | -from textwrap import dedent |
3385 | from uuid import uuid4 |
3386 | |
3387 | from fixtures import EnvironmentVariableFixture |
3388 | import formencode |
3389 | import formencode.validators |
3390 | from formencode.validators import Invalid |
3391 | -from maastesting import root |
3392 | from maastesting.factory import factory |
3393 | +from maastesting.fixtures import ImportErrorFixture |
3394 | from maastesting.matchers import ( |
3395 | MockCalledOnceWith, |
3396 | MockNotCalled, |
3397 | @@ -41,11 +35,7 @@ |
3398 | from maastesting.testcase import MAASTestCase |
3399 | from mock import sentinel |
3400 | from provisioningserver.config import ( |
3401 | - BootSources, |
3402 | ClusterConfiguration, |
3403 | - Config, |
3404 | - ConfigBase, |
3405 | - ConfigMeta, |
3406 | Configuration, |
3407 | ConfigurationDatabase, |
3408 | ConfigurationFile, |
3409 | @@ -53,20 +43,18 @@ |
3410 | ConfigurationOption, |
3411 | Directory, |
3412 | ExtendedURL, |
3413 | + is_dev_environment, |
3414 | UUID, |
3415 | ) |
3416 | from provisioningserver.path import get_path |
3417 | -from provisioningserver.testing.config import ConfigFixtureBase |
3418 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
3419 | from provisioningserver.utils.fs import FileLockProxy |
3420 | from testtools import ExpectedException |
3421 | from testtools.matchers import ( |
3422 | - DirExists, |
3423 | FileContains, |
3424 | FileExists, |
3425 | Is, |
3426 | - MatchesException, |
3427 | MatchesStructure, |
3428 | - Raises, |
3429 | ) |
3430 | from twisted.python.filepath import FilePath |
3431 | import yaml |
3432 | @@ -224,444 +212,6 @@ |
3433 | "url: %s" % url) |
3434 | |
3435 | |
3436 | -class ExampleConfig(ConfigBase, formencode.Schema): |
3437 | - """An example configuration schema. |
3438 | - |
3439 | - It derives from :class:`ConfigBase` and has a metaclass derived from |
3440 | - :class:`ConfigMeta`, just as a "real" schema must. |
3441 | - """ |
3442 | - |
3443 | - class __metaclass__(ConfigMeta): |
3444 | - envvar = "MAAS_TESTING_SETTINGS" |
3445 | - default = "example.yaml" |
3446 | - |
3447 | - something = formencode.validators.String(if_missing="*missing*") |
3448 | - |
3449 | - |
3450 | -class ExampleConfigFixture(ConfigFixtureBase): |
3451 | - """A fixture to help with testing :class:`ExampleConfig`.""" |
3452 | - |
3453 | - schema = ExampleConfig |
3454 | - |
3455 | - |
3456 | -class TestConfigFixtureBase(MAASTestCase): |
3457 | - """Tests for `ConfigFixtureBase`.""" |
3458 | - |
3459 | - def exercise_fixture(self, fixture): |
3460 | - # ConfigFixtureBase arranges a minimal configuration on disk, |
3461 | - # and exports the configuration filename to the environment so |
3462 | - # that subprocesses can find it. |
3463 | - with fixture: |
3464 | - self.assertThat(fixture.dir, DirExists()) |
3465 | - self.assertThat(fixture.filename, FileExists()) |
3466 | - self.assertEqual( |
3467 | - {fixture.schema.envvar: fixture.filename}, |
3468 | - fixture.environ) |
3469 | - self.assertEqual( |
3470 | - fixture.filename, os.environ[fixture.schema.envvar]) |
3471 | - with open(fixture.filename, "rb") as stream: |
3472 | - self.assertEqual(fixture.config, yaml.safe_load(stream)) |
3473 | - |
3474 | - def test_use_minimal(self): |
3475 | - # With no arguments, ConfigFixtureBase arranges a minimal |
3476 | - # configuration. |
3477 | - fixture = ExampleConfigFixture() |
3478 | - self.exercise_fixture(fixture) |
3479 | - |
3480 | - def test_use_with_config(self): |
3481 | - # Given a configuration, ConfigFixtureBase can arrange a minimal |
3482 | - # global configuration with the additional options merged in. |
3483 | - something = self.getUniqueString("something") |
3484 | - fixture = ExampleConfigFixture({"something": something}) |
3485 | - self.assertEqual(something, fixture.config["something"]) |
3486 | - self.exercise_fixture(fixture) |
3487 | - |
3488 | - |
3489 | -class TestConfigMeta_DEFAULT_FILENAME(MAASTestCase): |
3490 | - """Tests for `provisioningserver.config.ConfigBase.DEFAULT_FILENAME`.""" |
3491 | - |
3492 | - def set_envvar(self, filepath=None): |
3493 | - """Continue this test with a given environment variable.""" |
3494 | - self.useFixture(EnvironmentVariableFixture( |
3495 | - ExampleConfig.envvar, filepath)) |
3496 | - |
3497 | - def set_MAAS_CONFIG_DIR(self, dirpath=None): |
3498 | - """Continue this test with a given `MAAS_CONFIG_DIR`.""" |
3499 | - self.useFixture(EnvironmentVariableFixture("MAAS_CONFIG_DIR", dirpath)) |
3500 | - |
3501 | - def make_config(self): |
3502 | - """Create a config file in a directory of its own.""" |
3503 | - return self.make_file(name=ExampleConfig.default) |
3504 | - |
3505 | - def test_gets_filename_from_MAAS_PROVISIONING_SETTNGS(self): |
3506 | - dummy_filename = factory.make_name("config") |
3507 | - self.set_MAAS_CONFIG_DIR(None) |
3508 | - self.set_envvar(dummy_filename) |
3509 | - self.assertEqual(dummy_filename, ExampleConfig.DEFAULT_FILENAME) |
3510 | - |
3511 | - def test_falls_back_to_MAAS_CONFIG_DIR(self): |
3512 | - config_file = self.make_config() |
3513 | - self.set_MAAS_CONFIG_DIR(os.path.dirname(config_file)) |
3514 | - self.set_envvar(None) |
3515 | - self.assertEqual(config_file, ExampleConfig.DEFAULT_FILENAME) |
3516 | - |
3517 | - def test_MAAS_PROVISIONING_SETTINGS_trumps_MAAS_CONFIG_DIR(self): |
3518 | - provisioning_settings = factory.make_name("config") |
3519 | - self.set_MAAS_CONFIG_DIR(os.path.dirname(self.make_config())) |
3520 | - self.set_envvar(provisioning_settings) |
3521 | - self.assertEqual( |
3522 | - provisioning_settings, |
3523 | - ExampleConfig.DEFAULT_FILENAME) |
3524 | - |
3525 | - def test_defaults_to_global_config(self): |
3526 | - self.set_MAAS_CONFIG_DIR(None) |
3527 | - self.set_envvar(None) |
3528 | - self.assertEqual( |
3529 | - '/etc/maas/%s' % ExampleConfig.default, |
3530 | - ExampleConfig.DEFAULT_FILENAME) |
3531 | - |
3532 | - def test_set(self): |
3533 | - dummy_filename = factory.make_name("config") |
3534 | - ExampleConfig.DEFAULT_FILENAME = dummy_filename |
3535 | - self.assertEqual(dummy_filename, ExampleConfig.DEFAULT_FILENAME) |
3536 | - |
3537 | - def test_delete(self): |
3538 | - self.set_MAAS_CONFIG_DIR(None) |
3539 | - self.set_envvar(None) |
3540 | - ExampleConfig.DEFAULT_FILENAME = factory.make_name("config") |
3541 | - del ExampleConfig.DEFAULT_FILENAME |
3542 | - # The filename reverts; see test_get_with_environment_empty. |
3543 | - self.assertEqual( |
3544 | - "/etc/maas/%s" % ExampleConfig.default, |
3545 | - ExampleConfig.DEFAULT_FILENAME) |
3546 | - # The delete does not fail when called multiple times. |
3547 | - del ExampleConfig.DEFAULT_FILENAME |
3548 | - |
3549 | - |
3550 | -class TestConfigBase(MAASTestCase): |
3551 | - """Tests for `provisioningserver.config.ConfigBase`.""" |
3552 | - |
3553 | - def make_config_data(self): |
3554 | - """Return random config data for `ExampleConfig`.""" |
3555 | - return {'something': factory.make_name('value')} |
3556 | - |
3557 | - def make_config_file(self, name=None, data=None): |
3558 | - """Write a YAML config file, and return its path.""" |
3559 | - if name is None: |
3560 | - name = factory.make_name('config') + '.yaml' |
3561 | - if data is None: |
3562 | - data = self.make_config_data() |
3563 | - return self.make_file(name=name, contents=yaml.safe_dump(data)) |
3564 | - |
3565 | - def test_get_defaults_returns_default_config(self): |
3566 | - # The default configuration is production-ready. |
3567 | - observed = ExampleConfig.get_defaults() |
3568 | - self.assertEqual({"something": "*missing*"}, observed) |
3569 | - |
3570 | - def test_get_defaults_ignores_settings(self): |
3571 | - self.useFixture(ExampleConfigFixture(self.make_config_data())) |
3572 | - observed = ExampleConfig.get_defaults() |
3573 | - self.assertEqual({"something": "*missing*"}, observed) |
3574 | - |
3575 | - def test_parse(self): |
3576 | - # Configuration can be parsed from a snippet of YAML. |
3577 | - observed = ExampleConfig.parse(b'something: "important"\n') |
3578 | - self.assertEqual("important", observed["something"]) |
3579 | - |
3580 | - def test_load(self): |
3581 | - # Configuration can be loaded and parsed from a file. |
3582 | - config = dedent(""" |
3583 | - something: "important" |
3584 | - """) |
3585 | - filename = self.make_file(contents=config) |
3586 | - observed = ExampleConfig.load(filename) |
3587 | - self.assertEqual({"something": "important"}, observed) |
3588 | - |
3589 | - def test_load_defaults_to_default_filename(self): |
3590 | - data = self.make_config_data() |
3591 | - filename = self.make_config_file(name='config.yaml', data=data) |
3592 | - self.patch(ExampleConfig, 'DEFAULT_FILENAME', filename) |
3593 | - self.assertEqual(data, ExampleConfig.load()) |
3594 | - |
3595 | - def test_load_from_cache_loads_config(self): |
3596 | - data = self.make_config_data() |
3597 | - filename = self.make_config_file(data=data) |
3598 | - self.assertEqual(data, ExampleConfig.load_from_cache(filename)) |
3599 | - |
3600 | - def test_load_from_cache_uses_defaults(self): |
3601 | - filename = self.make_file(contents='') |
3602 | - self.assertEqual( |
3603 | - ExampleConfig.get_defaults(), |
3604 | - ExampleConfig.load_from_cache(filename)) |
3605 | - |
3606 | - def test_load_from_cache_caches_each_file_separately(self): |
3607 | - config1 = self.make_file(contents=yaml.safe_dump({'something': "1"})) |
3608 | - config2 = self.make_file(contents=yaml.safe_dump({'something': "2"})) |
3609 | - |
3610 | - self.assertEqual( |
3611 | - {"something": "1"}, |
3612 | - ExampleConfig.load_from_cache(config1)) |
3613 | - self.assertEqual( |
3614 | - {"something": "2"}, |
3615 | - ExampleConfig.load_from_cache(config2)) |
3616 | - |
3617 | - def test_load_from_cache_reloads_from_cache_not_from_file(self): |
3618 | - # A config loaded by Config.load_from_cache() is never reloaded. |
3619 | - filename = self.make_config_file() |
3620 | - config_before = ExampleConfig.load_from_cache(filename) |
3621 | - os.unlink(filename) |
3622 | - config_after = ExampleConfig.load_from_cache(filename) |
3623 | - self.assertEqual(config_before, config_after) |
3624 | - |
3625 | - def test_load_from_cache_caches_immutable_copy(self): |
3626 | - filename = self.make_config_file() |
3627 | - |
3628 | - first_load = ExampleConfig.load_from_cache(filename) |
3629 | - second_load = ExampleConfig.load_from_cache(filename) |
3630 | - |
3631 | - self.assertEqual(first_load, second_load) |
3632 | - self.assertIsNot(first_load, second_load) |
3633 | - first_load['something'] = factory.make_name('newthing') |
3634 | - self.assertNotEqual(first_load['something'], second_load['something']) |
3635 | - |
3636 | - def test_flush_cache_without_filename_empties_cache(self): |
3637 | - filename = self.make_config_file() |
3638 | - ExampleConfig.load_from_cache(filename) |
3639 | - os.unlink(filename) |
3640 | - ExampleConfig.flush_cache() |
3641 | - error = self.assertRaises( |
3642 | - IOError, |
3643 | - ExampleConfig.load_from_cache, filename) |
3644 | - self.assertEqual(errno.ENOENT, error.errno) |
3645 | - |
3646 | - def test_flush_cache_flushes_specific_file(self): |
3647 | - filename = self.make_config_file() |
3648 | - ExampleConfig.load_from_cache(filename) |
3649 | - os.unlink(filename) |
3650 | - ExampleConfig.flush_cache(filename) |
3651 | - error = self.assertRaises( |
3652 | - IOError, |
3653 | - ExampleConfig.load_from_cache, filename) |
3654 | - self.assertEqual(errno.ENOENT, error.errno) |
3655 | - |
3656 | - def test_flush_cache_retains_other_files(self): |
3657 | - flushed_file = self.make_config_file() |
3658 | - cached_file = self.make_config_file() |
3659 | - ExampleConfig.load_from_cache(flushed_file) |
3660 | - cached_config = ExampleConfig.load_from_cache(cached_file) |
3661 | - os.unlink(cached_file) |
3662 | - ExampleConfig.flush_cache(flushed_file) |
3663 | - self.assertEqual( |
3664 | - cached_config, |
3665 | - ExampleConfig.load_from_cache(cached_file)) |
3666 | - |
3667 | - def test_flush_cache_ignores_uncached_files(self): |
3668 | - data = self.make_config_data() |
3669 | - filename = self.make_config_file(data=data) |
3670 | - ExampleConfig.flush_cache(filename) |
3671 | - self.assertEqual(data, ExampleConfig.load_from_cache(filename)) |
3672 | - |
3673 | - def test_field(self): |
3674 | - self.assertIs(ExampleConfig, ExampleConfig.field()) |
3675 | - self.assertIs( |
3676 | - ExampleConfig.fields["something"], |
3677 | - ExampleConfig.field("something")) |
3678 | - |
3679 | - def test_save_and_load_interoperate(self): |
3680 | - something = self.getUniqueString() |
3681 | - saved_file = self.make_file() |
3682 | - |
3683 | - ExampleConfig.save({'something': something}, saved_file) |
3684 | - loaded_config = ExampleConfig.load(saved_file) |
3685 | - self.assertEqual(something, loaded_config['something']) |
3686 | - |
3687 | - def test_save_saves_yaml_file(self): |
3688 | - config = {'something': self.getUniqueString()} |
3689 | - saved_file = self.make_file() |
3690 | - |
3691 | - ExampleConfig.save(config, saved_file) |
3692 | - |
3693 | - with open(saved_file, 'rb') as written_file: |
3694 | - loaded_config = yaml.safe_load(written_file) |
3695 | - self.assertEqual(config, loaded_config) |
3696 | - |
3697 | - def test_save_defaults_to_default_filename(self): |
3698 | - something = self.getUniqueString() |
3699 | - filename = self.make_file(name="config.yaml") |
3700 | - self.patch(ExampleConfig, 'DEFAULT_FILENAME', filename) |
3701 | - |
3702 | - ExampleConfig.save({'something': something}) |
3703 | - |
3704 | - self.assertEqual( |
3705 | - {'something': something}, |
3706 | - ExampleConfig.load(filename)) |
3707 | - |
3708 | - def test_create_backup_creates_backup(self): |
3709 | - something = self.getUniqueString() |
3710 | - filename = self.make_file(name="config.yaml") |
3711 | - config = {'something': something} |
3712 | - yaml_config = yaml.safe_dump(config) |
3713 | - self.patch(ExampleConfig, 'DEFAULT_FILENAME', filename) |
3714 | - ExampleConfig.save(config) |
3715 | - |
3716 | - ExampleConfig.create_backup('test') |
3717 | - |
3718 | - backup_name = "%s.%s.bak" % (filename, 'test') |
3719 | - self.assertThat(backup_name, FileContains(yaml_config)) |
3720 | - |
3721 | - |
3722 | -class TestConfig(MAASTestCase): |
3723 | - """Tests for `provisioningserver.config.Config`.""" |
3724 | - |
3725 | - default_production_config = { |
3726 | - 'broker': { |
3727 | - 'host': 'localhost', |
3728 | - 'port': 5673, |
3729 | - 'username': getuser(), |
3730 | - 'password': 'test', |
3731 | - 'vhost': '/', |
3732 | - }, |
3733 | - 'logfile': 'pserv.log', |
3734 | - 'oops': { |
3735 | - 'directory': '', |
3736 | - 'reporter': '', |
3737 | - }, |
3738 | - 'rpc': {}, |
3739 | - 'tftp': { |
3740 | - 'generator': 'http://localhost/MAAS/api/1.0/pxeconfig/', |
3741 | - 'port': 69, |
3742 | - # The "root" setting is obsolete; resource_root replaces it. |
3743 | - 'root': "/var/lib/maas/tftp", |
3744 | - 'resource_root': "/var/lib/maas/boot-resources/current/", |
3745 | - }, |
3746 | - # Legacy section. Became unused in MAAS 1.5. |
3747 | - 'boot': { |
3748 | - 'architectures': None, |
3749 | - 'ephemeral': { |
3750 | - 'images_directory': None, |
3751 | - 'releases': None, |
3752 | - }, |
3753 | - }, |
3754 | - } |
3755 | - |
3756 | - default_development_config = deepcopy(default_production_config) |
3757 | - default_development_config.update(logfile="/dev/null") |
3758 | - default_development_config["tftp"].update( |
3759 | - port=5244, generator="http://localhost:5240/MAAS/api/1.0/pxeconfig/") |
3760 | - |
3761 | - def test_get_defaults_returns_default_config(self): |
3762 | - # The default configuration is production-ready. |
3763 | - observed = Config.get_defaults() |
3764 | - self.assertEqual(self.default_production_config, observed) |
3765 | - |
3766 | - def test_load_example(self): |
3767 | - # The example configuration is designed for development. |
3768 | - filename = os.path.join(root, "etc", "maas", "pserv.yaml") |
3769 | - self.assertEqual( |
3770 | - self.default_development_config, |
3771 | - Config.load(filename)) |
3772 | - |
3773 | - def test_oops_directory_without_reporter(self): |
3774 | - # It is an error to omit the OOPS reporter if directory is specified. |
3775 | - config = ( |
3776 | - 'oops:\n' |
3777 | - ' directory: /tmp/oops\n' |
3778 | - ) |
3779 | - expected = MatchesException( |
3780 | - formencode.Invalid, "oops: You must give a value for reporter") |
3781 | - self.assertThat( |
3782 | - partial(Config.parse, config), |
3783 | - Raises(expected)) |
3784 | - |
3785 | - def test_accepts_1_4_config_file(self): |
3786 | - # A config file that was valid with MAAS 1.4 still loads, even though |
3787 | - # its "boot" section is no longer used. |
3788 | - broker_password = factory.make_name('pass') |
3789 | - config = Config.parse(dedent("""\ |
3790 | - logfile: "/dev/null" |
3791 | - oops: |
3792 | - directory: "logs/oops" |
3793 | - reporter: "maas-pserv" |
3794 | - broker: |
3795 | - host: "localhost" |
3796 | - port: 5673 |
3797 | - username: brokeruser |
3798 | - password: "%s" |
3799 | - vhost: "/" |
3800 | - tftp: |
3801 | - root: /var/lib/maas/tftp |
3802 | - port: 5244 |
3803 | - generator: http://localhost:5240/api/1.0/pxeconfig/ |
3804 | - boot: |
3805 | - architectures: ['i386', 'armhf'] |
3806 | - ephemeral: |
3807 | - images_directory: /var/lib/maas/ephemeral |
3808 | - releases: ['precise', 'saucy'] |
3809 | - """) % broker_password) |
3810 | - # This does not fail. |
3811 | - self.assertEqual(broker_password, config['broker']['password']) |
3812 | - |
3813 | - |
3814 | -class TestBootSources(MAASTestCase): |
3815 | - """Tests for `provisioningserver.config.BootSources`.""" |
3816 | - |
3817 | - default_source = { |
3818 | - 'url': ( |
3819 | - 'http://maas.ubuntu.com/images/ephemeral-v2/releases/' |
3820 | - ), |
3821 | - 'keyring': ( |
3822 | - '/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg'), |
3823 | - 'keyring_data': None, |
3824 | - 'selections': [ |
3825 | - { |
3826 | - 'os': '*', |
3827 | - 'release': '*', |
3828 | - 'labels': ['*'], |
3829 | - 'arches': ['*'], |
3830 | - 'subarches': ['*'], |
3831 | - }, |
3832 | - ], |
3833 | - } |
3834 | - |
3835 | - def make_source(self): |
3836 | - """Create a dict defining an arbitrary `BootSource`.""" |
3837 | - return { |
3838 | - 'url': 'http://example.com/' + factory.make_name('path'), |
3839 | - 'keyring': factory.make_name('keyring'), |
3840 | - 'keyring_data': factory.make_string(), |
3841 | - 'selections': [{ |
3842 | - 'os': factory.make_name('os'), |
3843 | - 'release': factory.make_name('release'), |
3844 | - 'labels': [factory.make_name('label')], |
3845 | - 'arches': [factory.make_name('arch')], |
3846 | - 'subarches': [factory.make_name('sub') for _ in range(3)], |
3847 | - }], |
3848 | - } |
3849 | - |
3850 | - def test_parse_parses_source(self): |
3851 | - sources = [self.make_source()] |
3852 | - self.assertEqual( |
3853 | - sources, |
3854 | - BootSources.parse(BytesIO(yaml.safe_dump(sources)))) |
3855 | - |
3856 | - def test_parse_parses_multiple_sources(self): |
3857 | - sources = [self.make_source() for _ in range(2)] |
3858 | - self.assertEqual( |
3859 | - sources, |
3860 | - BootSources.parse(BytesIO(yaml.safe_dump(sources)))) |
3861 | - |
3862 | - def test_parse_uses_defaults(self): |
3863 | - self.assertEqual( |
3864 | - [self.default_source], |
3865 | - BootSources.parse(BytesIO(b'[{}]'))) |
3866 | - |
3867 | - def test_load_parses_file(self): |
3868 | - sources = [self.make_source()] |
3869 | - self.assertEqual( |
3870 | - sources, |
3871 | - BootSources.load(self.make_file(contents=yaml.safe_dump(sources)))) |
3872 | - |
3873 | - |
3874 | ############################################################################### |
3875 | # New configuration API follows. |
3876 | ############################################################################### |
3877 | @@ -1153,9 +703,12 @@ |
3878 | self.assertEqual({"tftp_port": example_port}, config.store) |
3879 | |
3880 | def test_default_tftp_root(self): |
3881 | + maas_root = os.getenv("MAAS_ROOT") |
3882 | + self.assertIsNotNone(maas_root) |
3883 | config = ClusterConfiguration({}) |
3884 | self.assertEqual( |
3885 | - "/var/lib/maas/boot-resources/current/", config.tftp_root) |
3886 | + os.path.join(maas_root, "var/lib/maas/boot-resources/current"), |
3887 | + config.tftp_root) |
3888 | |
3889 | def test_set_and_get_tftp_root(self): |
3890 | config = ClusterConfiguration({}) |
3891 | @@ -1164,3 +717,62 @@ |
3892 | self.assertEqual(example_dir, config.tftp_root) |
3893 | # It's also stored in the configuration database. |
3894 | self.assertEqual({"tftp_root": example_dir}, config.store) |
3895 | + |
3896 | + def test_default_cluster_uuid(self): |
3897 | + config = ClusterConfiguration({}) |
3898 | + self.assertEqual("** UUID NOT SET **", config.cluster_uuid) |
3899 | + |
3900 | + def test_set_and_get_cluster_uuid(self): |
3901 | + example_uuid = uuid4() |
3902 | + config = ClusterConfiguration({}) |
3903 | + config.cluster_uuid = example_uuid |
3904 | + self.assertEqual(unicode(example_uuid), config.cluster_uuid) |
3905 | + # It's also stored in the configuration database. |
3906 | + self.assertEqual({"cluster_uuid": unicode(example_uuid)}, config.store) |
3907 | + |
3908 | + |
3909 | +class TestClusterConfigurationTFTPGeneratorURL(MAASTestCase): |
3910 | + """Tests for `ClusterConfiguration.tftp_generator_url`.""" |
3911 | + |
3912 | + def test__is_relative_to_maas_url(self): |
3913 | + random_url = factory.make_simple_http_url() |
3914 | + self.useFixture(ClusterConfigurationFixture(maas_url=random_url)) |
3915 | + with ClusterConfiguration.open() as configuration: |
3916 | + self.assertEqual( |
3917 | + random_url + "/api/1.0/pxeconfig/", |
3918 | + configuration.tftp_generator_url) |
3919 | + |
3920 | + def test__strips_trailing_slashes_from_maas_url(self): |
3921 | + random_url = factory.make_simple_http_url(path="foobar/") |
3922 | + self.useFixture(ClusterConfigurationFixture(maas_url=random_url)) |
3923 | + with ClusterConfiguration.open() as configuration: |
3924 | + self.assertEqual( |
3925 | + random_url.rstrip("/") + "/api/1.0/pxeconfig/", |
3926 | + configuration.tftp_generator_url) |
3927 | + |
3928 | + |
3929 | +class TestClusterConfigurationGRUBRoot(MAASTestCase): |
3930 | + """Tests for `ClusterConfiguration.grub_root`.""" |
3931 | + |
3932 | + def test__is_relative_to_tftp_root_without_trailing_slash(self): |
3933 | + random_dir = self.make_dir().rstrip("/") |
3934 | + self.useFixture(ClusterConfigurationFixture(tftp_root=random_dir)) |
3935 | + with ClusterConfiguration.open() as configuration: |
3936 | + self.assertEqual(random_dir + "/grub", configuration.grub_root) |
3937 | + |
3938 | + def test__is_relative_to_tftp_root_with_trailing_slash(self): |
3939 | + random_dir = self.make_dir().rstrip("/") + "/" |
3940 | + self.useFixture(ClusterConfigurationFixture(tftp_root=random_dir)) |
3941 | + with ClusterConfiguration.open() as configuration: |
3942 | + self.assertEqual(random_dir + "grub", configuration.grub_root) |
3943 | + |
3944 | + |
3945 | +class TestConfig(MAASTestCase): |
3946 | + """Tests for `maasserver.config`.""" |
3947 | + |
3948 | + def test_is_dev_environment_returns_false(self): |
3949 | + self.useFixture(ImportErrorFixture('maastesting', 'root')) |
3950 | + self.assertFalse(is_dev_environment()) |
3951 | + |
3952 | + def test_is_dev_environment_returns_true(self): |
3953 | + self.assertTrue(is_dev_environment()) |
3954 | |
3955 | === removed file 'src/provisioningserver/tests/test_configure_maas_url.py' |
3956 | --- src/provisioningserver/tests/test_configure_maas_url.py 2015-05-29 16:47:37 +0000 |
3957 | +++ src/provisioningserver/tests/test_configure_maas_url.py 1970-01-01 00:00:00 +0000 |
3958 | @@ -1,351 +0,0 @@ |
3959 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
3960 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
3961 | - |
3962 | -"""Tests for `MAAS_URL` configuration update code.""" |
3963 | - |
3964 | -from __future__ import ( |
3965 | - absolute_import, |
3966 | - print_function, |
3967 | - unicode_literals, |
3968 | - ) |
3969 | - |
3970 | -str = None |
3971 | - |
3972 | -__metaclass__ = type |
3973 | -__all__ = [] |
3974 | - |
3975 | -from argparse import ArgumentParser |
3976 | -from random import randint |
3977 | -from textwrap import dedent |
3978 | - |
3979 | -from maastesting.factory import factory |
3980 | -from maastesting.matchers import ( |
3981 | - MockAnyCall, |
3982 | - MockCalledOnceWith, |
3983 | -) |
3984 | -from maastesting.testcase import MAASTestCase |
3985 | -from mock import ( |
3986 | - ANY, |
3987 | - Mock, |
3988 | -) |
3989 | -from provisioningserver import configure_maas_url |
3990 | -from provisioningserver.configure_maas_url import substitute_pserv_yaml_line |
3991 | -from testtools.matchers import ( |
3992 | - FileContains, |
3993 | - StartsWith, |
3994 | -) |
3995 | - |
3996 | - |
3997 | -class TestRewriteConfigFile(MAASTestCase): |
3998 | - |
3999 | - def test__rewrites_file(self): |
4000 | - path = self.make_file(contents='foo\n') |
4001 | - configure_maas_url.rewrite_config_file(path, lambda line: 'bar') |
4002 | - self.assertThat(path, FileContains('bar\n')) |
4003 | - |
4004 | - def test__sets_access_permissions(self): |
4005 | - writer = self.patch(configure_maas_url, 'atomic_write') |
4006 | - mode = 0215 |
4007 | - path = self.make_file() |
4008 | - configure_maas_url.rewrite_config_file( |
4009 | - path, lambda line: line, mode=mode) |
4010 | - self.assertThat(writer, MockCalledOnceWith(ANY, path, mode=mode)) |
4011 | - |
4012 | - def test__preserves_trailing_newline(self): |
4013 | - path = self.make_file(contents='x\n') |
4014 | - configure_maas_url.rewrite_config_file(path, lambda line: line) |
4015 | - self.assertThat(path, FileContains('x\n')) |
4016 | - |
4017 | - def test__ensures_trailing_newline(self): |
4018 | - path = self.make_file(contents='x') |
4019 | - configure_maas_url.rewrite_config_file(path, lambda line: line) |
4020 | - self.assertThat(path, FileContains('x\n')) |
4021 | - |
4022 | - |
4023 | -class TestUpdateMAASClusterConf(MAASTestCase): |
4024 | - |
4025 | - def patch_file(self, content): |
4026 | - """Inject a fake `/etc/maas/maas_cluster.conf`.""" |
4027 | - path = self.make_file(name='maas_cluster.conf', contents=content) |
4028 | - self.patch(configure_maas_url, 'MAAS_CLUSTER_CONF', path) |
4029 | - return path |
4030 | - |
4031 | - def test__updates_realistic_file(self): |
4032 | - config_file = self.patch_file(dedent("""\ |
4033 | - # Leading comments. |
4034 | - MAAS_URL="http://10.9.8.7/MAAS" |
4035 | - CLUSTER_UUID="5d02950e-6318-8195-ac3e-e6ccb12673c5" |
4036 | - """)) |
4037 | - configure_maas_url.update_maas_cluster_conf('http://1.2.3.4/MAAS') |
4038 | - self.assertThat( |
4039 | - config_file, |
4040 | - FileContains(dedent("""\ |
4041 | - # Leading comments. |
4042 | - MAAS_URL="http://1.2.3.4/MAAS" |
4043 | - CLUSTER_UUID="5d02950e-6318-8195-ac3e-e6ccb12673c5" |
4044 | - """))) |
4045 | - |
4046 | - def test__updates_quoted_value(self): |
4047 | - old_url = factory.make_url() |
4048 | - new_url = factory.make_url() |
4049 | - config_file = self.patch_file('MAAS_URL="%s"\n' % old_url) |
4050 | - configure_maas_url.update_maas_cluster_conf(new_url) |
4051 | - self.assertThat( |
4052 | - config_file, |
4053 | - FileContains('MAAS_URL="%s"\n' % new_url)) |
4054 | - |
4055 | - def test__updates_unquoted_value(self): |
4056 | - old_url = factory.make_url() |
4057 | - new_url = factory.make_url() |
4058 | - config_file = self.patch_file('MAAS_URL=%s\n' % old_url) |
4059 | - configure_maas_url.update_maas_cluster_conf(new_url) |
4060 | - self.assertThat( |
4061 | - config_file, |
4062 | - FileContains('MAAS_URL="%s"\n' % new_url)) |
4063 | - |
4064 | - def test__leaves_other_lines_unchanged(self): |
4065 | - old_content = '#MAAS_URL="%s"\n' % factory.make_url() |
4066 | - config_file = self.patch_file(old_content) |
4067 | - configure_maas_url.update_maas_cluster_conf(factory.make_url()) |
4068 | - self.assertThat(config_file, FileContains(old_content)) |
4069 | - |
4070 | - |
4071 | -class TestExtractHost(MAASTestCase): |
4072 | - |
4073 | - def test__extracts_hostname(self): |
4074 | - host = factory.make_name('host').lower() |
4075 | - port = factory.pick_port() |
4076 | - self.assertEqual( |
4077 | - host, |
4078 | - configure_maas_url.extract_host('http://%s/path' % host)) |
4079 | - self.assertEqual( |
4080 | - host, |
4081 | - configure_maas_url.extract_host('http://%s:%d' % (host, port))) |
4082 | - |
4083 | - def test__extracts_IPv4_address(self): |
4084 | - host = factory.make_ipv4_address() |
4085 | - port = factory.pick_port() |
4086 | - self.assertEqual( |
4087 | - host, |
4088 | - configure_maas_url.extract_host('http://%s' % host)) |
4089 | - self.assertEqual( |
4090 | - host, |
4091 | - configure_maas_url.extract_host('http://%s:%d' % (host, port))) |
4092 | - |
4093 | - def test__extracts_IPv6_address(self): |
4094 | - host = factory.make_ipv6_address() |
4095 | - port = factory.pick_port() |
4096 | - self.assertEqual( |
4097 | - host, |
4098 | - configure_maas_url.extract_host('http://[%s]' % host)) |
4099 | - self.assertEqual( |
4100 | - host, |
4101 | - configure_maas_url.extract_host('http://[%s]:%d' % (host, port))) |
4102 | - |
4103 | - def test__extracts_IPv6_address_with_zone_index(self): |
4104 | - host = ( |
4105 | - factory.make_ipv6_address() + |
4106 | - '%25' + |
4107 | - factory.make_name('zone').lower()) |
4108 | - port = factory.pick_port() |
4109 | - self.assertEqual( |
4110 | - host, |
4111 | - configure_maas_url.extract_host('http://[%s]' % host)) |
4112 | - self.assertEqual( |
4113 | - host, |
4114 | - configure_maas_url.extract_host('http://[%s]:%d' % (host, port))) |
4115 | - |
4116 | - |
4117 | -class TestSubstitutePservYamlLine(MAASTestCase): |
4118 | - |
4119 | - def make_generator_line(self, url): |
4120 | - return " generator: %s" % url |
4121 | - |
4122 | - def test__replaces_hostname_generator_URL(self): |
4123 | - old_host = factory.make_name('old-host') |
4124 | - new_host = factory.make_name('new-host') |
4125 | - input_line = self.make_generator_line('http://%s' % old_host) |
4126 | - self.assertEqual( |
4127 | - self.make_generator_line('http://%s' % new_host), |
4128 | - substitute_pserv_yaml_line(new_host, input_line)) |
4129 | - |
4130 | - def test__replaces_IPv4_generator_URL(self): |
4131 | - old_host = factory.make_ipv4_address() |
4132 | - new_host = factory.make_name('new-host') |
4133 | - input_line = self.make_generator_line('http://%s' % old_host) |
4134 | - self.assertEqual( |
4135 | - self.make_generator_line('http://%s' % new_host), |
4136 | - substitute_pserv_yaml_line(new_host, input_line)) |
4137 | - |
4138 | - def test__replaces_IPv6_generator_URL(self): |
4139 | - old_host = factory.make_ipv6_address() |
4140 | - new_host = factory.make_name('new-host') |
4141 | - input_line = self.make_generator_line('http://[%s]' % old_host) |
4142 | - self.assertEqual( |
4143 | - self.make_generator_line('http://%s' % new_host), |
4144 | - substitute_pserv_yaml_line(new_host, input_line)) |
4145 | - |
4146 | - def test__replaces_IPv6_generator_URL_with_zone_index(self): |
4147 | - old_host = ( |
4148 | - factory.make_ipv6_address() + |
4149 | - '%25' + |
4150 | - factory.make_name('zone') |
4151 | - ) |
4152 | - new_host = factory.make_name('new-host') |
4153 | - input_line = self.make_generator_line('http://[%s]' % old_host) |
4154 | - self.assertEqual( |
4155 | - self.make_generator_line('http://%s' % new_host), |
4156 | - substitute_pserv_yaml_line(new_host, input_line)) |
4157 | - |
4158 | - def test__inserts_IPv6_with_brackets(self): |
4159 | - old_host = factory.make_name('old-host') |
4160 | - new_host = '[%s]' % factory.make_ipv6_address() |
4161 | - input_line = self.make_generator_line('http://%s' % old_host) |
4162 | - self.assertEqual( |
4163 | - self.make_generator_line('http://%s' % new_host), |
4164 | - substitute_pserv_yaml_line(new_host, input_line)) |
4165 | - |
4166 | - def test__inserts_IPv6_without_brackets(self): |
4167 | - old_host = factory.make_name('old-host') |
4168 | - new_host = factory.make_ipv6_address() |
4169 | - input_line = self.make_generator_line('http://%s' % old_host) |
4170 | - self.assertEqual( |
4171 | - self.make_generator_line('http://[%s]' % new_host), |
4172 | - substitute_pserv_yaml_line(new_host, input_line)) |
4173 | - |
4174 | - def test__preserves_port_after_simple_host(self): |
4175 | - port = factory.pick_port() |
4176 | - old_host = factory.make_name('old-host') |
4177 | - new_host = factory.make_name('new-host') |
4178 | - input_line = self.make_generator_line( |
4179 | - 'http://%s:%d' % (old_host, port)) |
4180 | - self.assertEqual( |
4181 | - self.make_generator_line('http://%s:%d' % (new_host, port)), |
4182 | - substitute_pserv_yaml_line(new_host, input_line)) |
4183 | - |
4184 | - def test__preserves_port_with_IPv6(self): |
4185 | - port = factory.pick_port() |
4186 | - old_host = factory.make_ipv6_address() |
4187 | - new_host = factory.make_name('new-host') |
4188 | - input_line = self.make_generator_line( |
4189 | - 'http://[%s]:%d' % (old_host, port)) |
4190 | - self.assertEqual( |
4191 | - self.make_generator_line('http://%s:%d' % (new_host, port)), |
4192 | - substitute_pserv_yaml_line(new_host, input_line)) |
4193 | - |
4194 | - def test__preserves_port_with_IPv6_and_zone_index(self): |
4195 | - port = factory.pick_port() |
4196 | - old_host = ( |
4197 | - factory.make_ipv6_address() + |
4198 | - '%25' + |
4199 | - factory.make_name('zone') |
4200 | - ) |
4201 | - new_host = factory.make_name('new-host') |
4202 | - input_line = self.make_generator_line( |
4203 | - 'http://[%s]:%d' % (old_host, port)) |
4204 | - self.assertEqual( |
4205 | - self.make_generator_line('http://%s:%d' % (new_host, port)), |
4206 | - substitute_pserv_yaml_line(new_host, input_line)) |
4207 | - |
4208 | - def test__preserves_other_line(self): |
4209 | - line = '#' + self.make_generator_line(factory.make_url()) |
4210 | - self.assertEqual( |
4211 | - line, |
4212 | - substitute_pserv_yaml_line(factory.make_name('host'), line)) |
4213 | - |
4214 | - def test__preserves_indentation(self): |
4215 | - spaces = ' ' * randint(0, 10) |
4216 | - input_line = spaces + 'generator: %s' % factory.make_url() |
4217 | - output_line = substitute_pserv_yaml_line( |
4218 | - factory.make_name('host'), input_line) |
4219 | - self.assertThat(output_line, StartsWith(spaces + 'generator:')) |
4220 | - |
4221 | - def test__preserves_trailing_comments(self): |
4222 | - comment = " # Trailing comment." |
4223 | - old_host = factory.make_name('old-host') |
4224 | - new_host = factory.make_name('new-host') |
4225 | - input_line = self.make_generator_line('http://%s' % old_host) + comment |
4226 | - self.assertEqual( |
4227 | - self.make_generator_line('http://%s' % new_host) + comment, |
4228 | - substitute_pserv_yaml_line(new_host, input_line)) |
4229 | - |
4230 | - |
4231 | -class TestUpdatePservYaml(MAASTestCase): |
4232 | - |
4233 | - def patch_file(self, content): |
4234 | - """Inject a fake `/etc/maas/pserv.yaml`.""" |
4235 | - path = self.make_file(name='pserv.yaml', contents=content) |
4236 | - self.patch(configure_maas_url, 'PSERV_YAML', path) |
4237 | - return path |
4238 | - |
4239 | - def test__updates_realistic_file(self): |
4240 | - old_host = factory.make_name('old-host') |
4241 | - new_host = factory.make_name('new-host') |
4242 | - config_file = self.patch_file(dedent("""\ |
4243 | - ## TFTP configuration. |
4244 | - tftp: |
4245 | - ## The URL to be contacted to generate PXE configurations. |
4246 | - generator: http://%s/MAAS/api/1.0/pxeconfig/ |
4247 | - """) % old_host) |
4248 | - configure_maas_url.update_pserv_yaml(new_host) |
4249 | - self.assertThat( |
4250 | - config_file, |
4251 | - FileContains(dedent("""\ |
4252 | - ## TFTP configuration. |
4253 | - tftp: |
4254 | - ## The URL to be contacted to generate PXE configurations. |
4255 | - generator: http://%s/MAAS/api/1.0/pxeconfig/ |
4256 | - """) % new_host)) |
4257 | - |
4258 | - |
4259 | -class TestAddArguments(MAASTestCase): |
4260 | - |
4261 | - def test__accepts_maas_url(self): |
4262 | - url = factory.make_url() |
4263 | - parser = ArgumentParser() |
4264 | - configure_maas_url.add_arguments(parser) |
4265 | - args = parser.parse_args([url]) |
4266 | - self.assertEqual(url, args.maas_url) |
4267 | - |
4268 | - |
4269 | -class TestRun(MAASTestCase): |
4270 | - |
4271 | - def make_args(self, maas_url): |
4272 | - args = Mock() |
4273 | - args.maas_url = maas_url |
4274 | - return args |
4275 | - |
4276 | - def patch_read_file(self): |
4277 | - return self.patch(configure_maas_url, 'read_text_file') |
4278 | - |
4279 | - def patch_write_file(self): |
4280 | - return self.patch(configure_maas_url, 'atomic_write') |
4281 | - |
4282 | - def test__updates_maas_cluster_conf(self): |
4283 | - reader = self.patch_read_file() |
4284 | - writer = self.patch_write_file() |
4285 | - url = factory.make_url() |
4286 | - configure_maas_url.run(self.make_args(url)) |
4287 | - self.assertThat(reader, MockAnyCall('/etc/maas/maas_cluster.conf')) |
4288 | - self.assertThat( |
4289 | - writer, |
4290 | - MockAnyCall(ANY, '/etc/maas/maas_cluster.conf', mode=0640)) |
4291 | - |
4292 | - def test__updates_pserv_yaml(self): |
4293 | - reader = self.patch_read_file() |
4294 | - writer = self.patch_write_file() |
4295 | - url = factory.make_url() |
4296 | - configure_maas_url.run(self.make_args(url)) |
4297 | - self.assertThat(reader, MockAnyCall('/etc/maas/pserv.yaml')) |
4298 | - self.assertThat( |
4299 | - writer, |
4300 | - MockAnyCall(ANY, '/etc/maas/pserv.yaml', mode=0644)) |
4301 | - |
4302 | - def test__passes_host_to_update_pserv_yaml(self): |
4303 | - self.patch_read_file() |
4304 | - self.patch_write_file() |
4305 | - update_pserv_yaml = self.patch(configure_maas_url, 'update_pserv_yaml') |
4306 | - host = factory.make_name('host').lower() |
4307 | - url = factory.make_url(netloc=host) |
4308 | - configure_maas_url.run(self.make_args(url)) |
4309 | - self.assertThat(update_pserv_yaml, MockCalledOnceWith(host)) |
4310 | |
4311 | === modified file 'src/provisioningserver/tests/test_diskless.py' |
4312 | --- src/provisioningserver/tests/test_diskless.py 2015-05-29 16:47:37 +0000 |
4313 | +++ src/provisioningserver/tests/test_diskless.py 2015-07-03 11:18:28 +0000 |
4314 | @@ -24,10 +24,8 @@ |
4315 | ) |
4316 | from maastesting.testcase import MAASTestCase |
4317 | from mock import sentinel |
4318 | -from provisioningserver import ( |
4319 | - config, |
4320 | - diskless, |
4321 | -) |
4322 | +from provisioningserver import diskless |
4323 | +from provisioningserver.config import ClusterConfiguration |
4324 | from provisioningserver.diskless import ( |
4325 | compose_diskless_link_path, |
4326 | compose_diskless_tgt_config, |
4327 | @@ -54,6 +52,7 @@ |
4328 | BOOT_IMAGE_PURPOSE, |
4329 | OperatingSystemRegistry, |
4330 | ) |
4331 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
4332 | from provisioningserver.testing.os import FakeOS |
4333 | from provisioningserver.utils.testing import RegistryFixture |
4334 | from testtools.matchers import ( |
4335 | @@ -71,13 +70,17 @@ |
4336 | |
4337 | def setUp(self): |
4338 | super(DisklessTestMixin, self).setUp() |
4339 | + self.useFixture(ClusterConfigurationFixture()) |
4340 | # Ensure the global registry is empty for each test run. |
4341 | self.useFixture(RegistryFixture()) |
4342 | |
4343 | def configure_resource_storage(self): |
4344 | resource_dir = self.make_dir() |
4345 | os.mkdir(os.path.join(resource_dir, 'diskless')) |
4346 | - self.patch(config, 'BOOT_RESOURCES_STORAGE', resource_dir) |
4347 | + current_dir = os.path.join(resource_dir, 'current') + '/' |
4348 | + os.mkdir(current_dir) |
4349 | + with ClusterConfiguration.open() as config: |
4350 | + config.tftp_root = current_dir |
4351 | return resource_dir |
4352 | |
4353 | def configure_diskless_storage(self): |
4354 | @@ -113,11 +116,14 @@ |
4355 | self.patch(diskless, 'reload_diskless_tgt') |
4356 | |
4357 | |
4358 | -class TestHelpers(MAASTestCase, DisklessTestMixin): |
4359 | +class TestHelpers(DisklessTestMixin, MAASTestCase): |
4360 | |
4361 | def test_get_diskless_store(self): |
4362 | - storage_dir = factory.make_name('storage') |
4363 | - self.patch(config, 'BOOT_RESOURCES_STORAGE', storage_dir) |
4364 | + storage_dir = self.make_dir() |
4365 | + current_dir = os.path.join(storage_dir, 'current') + '/' |
4366 | + os.mkdir(current_dir) |
4367 | + with ClusterConfiguration.open() as config: |
4368 | + config.tftp_root = current_dir |
4369 | self.assertEqual( |
4370 | os.path.join(storage_dir, 'diskless', 'store'), |
4371 | get_diskless_store()) |
4372 | @@ -199,7 +205,7 @@ |
4373 | self.assertRaises(DisklessError, get_diskless_driver, invalid_name) |
4374 | |
4375 | |
4376 | -class TestTgtHelpers(MAASTestCase, DisklessTestMixin): |
4377 | +class TestTgtHelpers(DisklessTestMixin, MAASTestCase): |
4378 | |
4379 | def test_get_diskless_target(self): |
4380 | system_id = factory.make_name('system_id') |
4381 | @@ -269,10 +275,10 @@ |
4382 | update_diskless_tgt() |
4383 | self.assertThat( |
4384 | mock_write, |
4385 | - MockCalledOnceWith(tgt_config, tgt_path, mode=0644)) |
4386 | - |
4387 | - |
4388 | -class TestComposeSourcePath(MAASTestCase, DisklessTestMixin): |
4389 | + MockCalledOnceWith(tgt_config, tgt_path, mode=0o644)) |
4390 | + |
4391 | + |
4392 | +class TestComposeSourcePath(DisklessTestMixin, MAASTestCase): |
4393 | |
4394 | def test__raises_error_on_missing_os_from_registry(self): |
4395 | self.assertRaises( |
4396 | @@ -297,14 +303,15 @@ |
4397 | osystem = OperatingSystemRegistry[os_name] |
4398 | mock_xi_params = self.patch(osystem, 'get_xinstall_parameters') |
4399 | mock_xi_params.return_value = (root_path, 'tgz') |
4400 | + with ClusterConfiguration.open() as config: |
4401 | + tftp_root = config.tftp_root |
4402 | self.assertEqual( |
4403 | os.path.join( |
4404 | - config.BOOT_RESOURCES_STORAGE, 'current', os_name, |
4405 | - arch, subarch, release, label, root_path), |
4406 | + tftp_root, os_name, arch, subarch, release, label, root_path), |
4407 | compose_source_path(os_name, arch, subarch, release, label)) |
4408 | |
4409 | |
4410 | -class TestCreateDisklessDisk(MAASTestCase, DisklessTestMixin): |
4411 | +class TestCreateDisklessDisk(DisklessTestMixin, MAASTestCase): |
4412 | |
4413 | def test__raises_error_on_doesnt_exist_source_path(self): |
4414 | self.configure_compose_source_path(factory.make_name('invalid_path')) |
4415 | @@ -408,7 +415,7 @@ |
4416 | self.assertThat(mock_update_tgt, MockCalledOnceWith()) |
4417 | |
4418 | |
4419 | -class TestDeleteDisklessDisk(MAASTestCase, DisklessTestMixin): |
4420 | +class TestDeleteDisklessDisk(DisklessTestMixin, MAASTestCase): |
4421 | |
4422 | def test__exits_early_on_missing_link(self): |
4423 | self.configure_diskless_storage() |
4424 | |
4425 | === modified file 'src/provisioningserver/tests/test_path.py' |
4426 | --- src/provisioningserver/tests/test_path.py 2015-05-07 18:14:38 +0000 |
4427 | +++ src/provisioningserver/tests/test_path.py 2015-07-03 11:18:28 +0000 |
4428 | @@ -73,6 +73,14 @@ |
4429 | os.path.join(root, path), |
4430 | self.get_path_function(path)) |
4431 | |
4432 | + def test__assumes_MAAS_ROOT_is_unset_if_empty(self): |
4433 | + self.set_root("") |
4434 | + self.patch(provisioningserver.path, 'ensure_dir') |
4435 | + path = factory.make_name('path') |
4436 | + self.assertEqual( |
4437 | + os.path.join("/", path), |
4438 | + self.get_path_function(path)) |
4439 | + |
4440 | def test__returns_absolute_path(self): |
4441 | self.set_root('.') |
4442 | self.patch(provisioningserver.path, 'ensure_dir') |
4443 | |
4444 | === modified file 'src/provisioningserver/tests/test_plugin.py' |
4445 | --- src/provisioningserver/tests/test_plugin.py 2015-06-25 14:48:14 +0000 |
4446 | +++ src/provisioningserver/tests/test_plugin.py 2015-07-03 11:18:28 +0000 |
4447 | @@ -14,10 +14,6 @@ |
4448 | __metaclass__ = type |
4449 | __all__ = [] |
4450 | |
4451 | -import os |
4452 | - |
4453 | -from fixtures import EnvironmentVariableFixture |
4454 | -from maastesting.factory import factory |
4455 | from maastesting.matchers import MockCalledOnceWith |
4456 | from maastesting.testcase import ( |
4457 | MAASTestCase, |
4458 | @@ -25,6 +21,7 @@ |
4459 | ) |
4460 | import provisioningserver |
4461 | from provisioningserver import plugin as plugin_module |
4462 | +from provisioningserver.config import ClusterConfiguration |
4463 | from provisioningserver.plugin import ( |
4464 | Options, |
4465 | ProvisioningServiceMaker, |
4466 | @@ -46,6 +43,7 @@ |
4467 | TFTPBackend, |
4468 | TFTPService, |
4469 | ) |
4470 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
4471 | from testtools.matchers import ( |
4472 | AfterPreprocessing, |
4473 | Equals, |
4474 | @@ -54,7 +52,8 @@ |
4475 | MatchesStructure, |
4476 | ) |
4477 | from twisted.application.service import MultiService |
4478 | -import yaml |
4479 | +from twisted.python.filepath import FilePath |
4480 | +from twisted.web.server import Site |
4481 | |
4482 | |
4483 | class TestOptions(MAASTestCase): |
4484 | @@ -62,7 +61,7 @@ |
4485 | |
4486 | def test_defaults(self): |
4487 | options = Options() |
4488 | - expected = {"config-file": "pserv.yaml", "introspect": None} |
4489 | + expected = {"introspect": None} |
4490 | self.assertEqual(expected, options.defaults) |
4491 | |
4492 | def test_parse_minimal_options(self): |
4493 | @@ -79,17 +78,9 @@ |
4494 | |
4495 | def setUp(self): |
4496 | super(TestProvisioningServiceMaker, self).setUp() |
4497 | + self.useFixture(ClusterConfigurationFixture()) |
4498 | self.patch(provisioningserver, "services", MultiService()) |
4499 | self.tempdir = self.make_dir() |
4500 | - self.useFixture( |
4501 | - EnvironmentVariableFixture( |
4502 | - "CLUSTER_UUID", factory.make_UUID())) |
4503 | - |
4504 | - def write_config(self, config): |
4505 | - config_filename = os.path.join(self.tempdir, "config.yaml") |
4506 | - with open(config_filename, "wb") as stream: |
4507 | - yaml.safe_dump(config, stream) |
4508 | - return config_filename |
4509 | |
4510 | def test_init(self): |
4511 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4512 | @@ -101,7 +92,6 @@ |
4513 | Only the site service is created when no options are given. |
4514 | """ |
4515 | options = Options() |
4516 | - options["config-file"] = self.write_config({}) |
4517 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4518 | service = service_maker.makeService(options) |
4519 | self.assertIsInstance(service, MultiService) |
4520 | @@ -120,7 +110,6 @@ |
4521 | mock_simplestreams_patch = ( |
4522 | self.patch(plugin_module, 'force_simplestreams_to_use_urllib2')) |
4523 | options = Options() |
4524 | - options["config-file"] = self.write_config({}) |
4525 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4526 | service_maker.makeService(options) |
4527 | self.assertThat(mock_simplestreams_patch, MockCalledOnceWith()) |
4528 | @@ -129,14 +118,12 @@ |
4529 | mock_tftp_patch = ( |
4530 | self.patch(plugin_module, 'add_term_error_code_to_tftp')) |
4531 | options = Options() |
4532 | - options["config-file"] = self.write_config({}) |
4533 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4534 | service_maker.makeService(options) |
4535 | self.assertThat(mock_tftp_patch, MockCalledOnceWith()) |
4536 | |
4537 | def test_image_download_service(self): |
4538 | options = Options() |
4539 | - options["config-file"] = self.write_config({}) |
4540 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4541 | service = service_maker.makeService(options) |
4542 | image_service = service.getServiceNamed("image_download") |
4543 | @@ -144,7 +131,6 @@ |
4544 | |
4545 | def test_node_monitor_service(self): |
4546 | options = Options() |
4547 | - options["config-file"] = self.write_config({}) |
4548 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4549 | service = service_maker.makeService(options) |
4550 | node_monitor = service.getServiceNamed("node_monitor") |
4551 | @@ -152,7 +138,6 @@ |
4552 | |
4553 | def test_dhcp_probe_service(self): |
4554 | options = Options() |
4555 | - options["config-file"] = self.write_config({}) |
4556 | service_maker = ProvisioningServiceMaker("Spike", "Milligan") |
4557 | service = service_maker.makeService(options) |
4558 | dhcp_probe = service.getServiceNamed("dhcp_probe") |
4559 | @@ -160,7 +145,6 @@ |
4560 | |
4561 | def test_service_monitor_service(self): |
4562 | options = Options() |
4563 | - options["config-file"] = self.write_config({}) |
4564 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4565 | service = service_maker.makeService(options) |
4566 | service_monitor = service.getServiceNamed("service_monitor") |
4567 | @@ -168,53 +152,44 @@ |
4568 | |
4569 | def test_tftp_service(self): |
4570 | # A TFTP service is configured and added to the top-level service. |
4571 | - config = { |
4572 | - "tftp": { |
4573 | - "generator": "http://candlemass/solitude", |
4574 | - "resource_root": self.tempdir, |
4575 | - "port": factory.pick_port(), |
4576 | - }, |
4577 | - } |
4578 | options = Options() |
4579 | - options["config-file"] = self.write_config(config) |
4580 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4581 | service = service_maker.makeService(options) |
4582 | tftp_service = service.getServiceNamed("tftp") |
4583 | self.assertIsInstance(tftp_service, TFTPService) |
4584 | |
4585 | + with ClusterConfiguration.open() as config: |
4586 | + tftp_generator_url = config.tftp_generator_url |
4587 | + tftp_root = config.tftp_root |
4588 | + tftp_port = config.tftp_port |
4589 | + |
4590 | expected_backend = MatchesAll( |
4591 | IsInstance(TFTPBackend), |
4592 | AfterPreprocessing( |
4593 | lambda backend: backend.base.path, |
4594 | - Equals(config["tftp"]["resource_root"])), |
4595 | + Equals(tftp_root)), |
4596 | AfterPreprocessing( |
4597 | lambda backend: backend.generator_url.geturl(), |
4598 | - Equals(config["tftp"]["generator"]))) |
4599 | + Equals(tftp_generator_url))) |
4600 | |
4601 | self.assertThat( |
4602 | tftp_service, MatchesStructure( |
4603 | backend=expected_backend, |
4604 | - port=Equals(config["tftp"]["port"]), |
4605 | + port=Equals(tftp_port), |
4606 | )) |
4607 | |
4608 | def test_image_service(self): |
4609 | - from provisioningserver import config |
4610 | - from twisted.web.server import Site |
4611 | - from twisted.python.filepath import FilePath |
4612 | - |
4613 | options = Options() |
4614 | - options["config-file"] = self.write_config({}) |
4615 | service_maker = ProvisioningServiceMaker("Harry", "Hill") |
4616 | service = service_maker.makeService(options) |
4617 | image_service = service.getServiceNamed("image_service") |
4618 | self.assertIsInstance(image_service, BootImageEndpointService) |
4619 | self.assertIsInstance(image_service.site, Site) |
4620 | resource = image_service.site.resource |
4621 | - root = resource.getChildWithDefault( |
4622 | - "images", request=None) |
4623 | + root = resource.getChildWithDefault("images", request=None) |
4624 | self.assertThat(root, IsInstance(FilePath)) |
4625 | |
4626 | - resource_root = os.path.join( |
4627 | - config.BOOT_RESOURCES_STORAGE, "current") |
4628 | + with ClusterConfiguration.open() as config: |
4629 | + resource_root = FilePath(config.tftp_root) |
4630 | |
4631 | - self.assertEqual(resource_root, root.path) |
4632 | + self.assertEqual(resource_root, root) |
4633 | |
4634 | === modified file 'src/provisioningserver/tests/test_tags.py' |
4635 | --- src/provisioningserver/tests/test_tags.py 2015-05-07 18:14:38 +0000 |
4636 | +++ src/provisioningserver/tests/test_tags.py 2015-07-03 11:18:28 +0000 |
4637 | @@ -37,6 +37,7 @@ |
4638 | sentinel, |
4639 | ) |
4640 | from provisioningserver import tags |
4641 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
4642 | from provisioningserver.testing.testcase import PservTestCase |
4643 | from testtools.matchers import ( |
4644 | DocTestMatches, |
4645 | @@ -484,7 +485,7 @@ |
4646 | self.useFixture(FakeLogger()) |
4647 | |
4648 | def fake_client(self): |
4649 | - return MAASClient(None, None, self.make_maas_url()) |
4650 | + return MAASClient(None, None, factory.make_simple_http_url()) |
4651 | |
4652 | def fake_cached_knowledge(self): |
4653 | nodegroup_uuid = factory.make_name('nodegroupuuid') |
4654 | @@ -592,7 +593,8 @@ |
4655 | tags.classify(xpath, node_details)) |
4656 | |
4657 | def test_process_node_tags_integration(self): |
4658 | - self.set_maas_url() |
4659 | + self.useFixture(ClusterConfigurationFixture( |
4660 | + maas_url=factory.make_simple_http_url())) |
4661 | get_nodes = FakeMethod( |
4662 | result=factory.make_response( |
4663 | httplib.OK, |
4664 | |
4665 | === modified file 'src/provisioningserver/tests/test_upgrade_cluster.py' |
4666 | --- src/provisioningserver/tests/test_upgrade_cluster.py 2015-05-29 16:47:37 +0000 |
4667 | +++ src/provisioningserver/tests/test_upgrade_cluster.py 2015-07-03 11:18:28 +0000 |
4668 | @@ -27,11 +27,9 @@ |
4669 | from maastesting.testcase import MAASTestCase |
4670 | from maastesting.utils import sample_binary_data |
4671 | from mock import Mock |
4672 | -from provisioningserver import ( |
4673 | - config, |
4674 | - upgrade_cluster, |
4675 | -) |
4676 | +from provisioningserver import upgrade_cluster |
4677 | from provisioningserver.boot.tftppath import list_subdirs |
4678 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
4679 | from provisioningserver.utils.fs import read_text_file |
4680 | from testtools.matchers import ( |
4681 | DirExists, |
4682 | @@ -88,7 +86,7 @@ |
4683 | |
4684 | def configure_storage(self, storage_dir): |
4685 | """Create a storage config.""" |
4686 | - self.patch(config, 'BOOT_RESOURCES_STORAGE', storage_dir) |
4687 | + self.useFixture(ClusterConfigurationFixture(tftp_root=storage_dir)) |
4688 | |
4689 | def test__calls_chown_if_boot_resources_dir_exists(self): |
4690 | self.patch(upgrade_cluster, 'check_call') |
4691 | @@ -102,7 +100,9 @@ |
4692 | def test__skips_chown_if_boot_resources_dir_does_not_exist(self): |
4693 | self.patch(upgrade_cluster, 'check_call') |
4694 | storage_dir = os.path.join(self.make_dir(), factory.make_name('none')) |
4695 | + os.mkdir(storage_dir) |
4696 | self.configure_storage(storage_dir) |
4697 | + os.rmdir(storage_dir) |
4698 | upgrade_cluster.make_maas_own_boot_resources() |
4699 | self.assertThat(upgrade_cluster.check_call, MockNotCalled()) |
4700 | |
4701 | @@ -205,10 +205,11 @@ |
4702 | |
4703 | def configure_storage(self, storage_dir, make_current_dir=True): |
4704 | """Create a storage config.""" |
4705 | - if make_current_dir: |
4706 | - current_dir = os.path.join(storage_dir, "current") |
4707 | - os.mkdir(current_dir) |
4708 | - self.patch(config, 'BOOT_RESOURCES_STORAGE', storage_dir) |
4709 | + current_dir = os.path.join(storage_dir, "current") |
4710 | + os.makedirs(current_dir) |
4711 | + self.useFixture(ClusterConfigurationFixture(tftp_root=current_dir)) |
4712 | + if not make_current_dir: |
4713 | + os.rmdir(current_dir) |
4714 | |
4715 | def test__list_subdirs_under_current_directory(self): |
4716 | self.patch(upgrade_cluster, 'list_subdirs').return_value = ['ubuntu'] |
4717 | |
4718 | === modified file 'src/provisioningserver/upgrade_cluster.py' |
4719 | --- src/provisioningserver/upgrade_cluster.py 2015-05-29 16:47:37 +0000 |
4720 | +++ src/provisioningserver/upgrade_cluster.py 2015-07-03 11:18:28 +0000 |
4721 | @@ -38,12 +38,12 @@ |
4722 | from subprocess import check_call |
4723 | from textwrap import dedent |
4724 | |
4725 | -from provisioningserver import config |
4726 | from provisioningserver.auth import get_maas_user_gpghome |
4727 | from provisioningserver.boot.tftppath import ( |
4728 | drill_down, |
4729 | list_subdirs, |
4730 | ) |
4731 | +from provisioningserver.config import ClusterConfiguration |
4732 | from provisioningserver.import_images.boot_resources import ( |
4733 | update_targets_conf, |
4734 | write_targets_conf, |
4735 | @@ -57,8 +57,10 @@ |
4736 | def make_maas_own_boot_resources(): |
4737 | """Upgrade hook: make the `maas` user the owner of the boot resources.""" |
4738 | # This reduces the privileges required for importing and managing images. |
4739 | - if os.path.isdir(config.BOOT_RESOURCES_STORAGE): |
4740 | - check_call(['chown', '-R', 'maas', config.BOOT_RESOURCES_STORAGE]) |
4741 | + with ClusterConfiguration.open() as config: |
4742 | + boot_resources_storage = config.tftp_root |
4743 | + if os.path.isdir(boot_resources_storage): |
4744 | + check_call(['chown', '-R', 'maas', boot_resources_storage]) |
4745 | |
4746 | |
4747 | def create_gnupg_home(): |
4748 | @@ -145,10 +147,10 @@ |
4749 | def filter_out_directories_with_extra_levels(paths): |
4750 | """Remove paths that contain directories with more levels. We don't want |
4751 | to move other operating systems under the ubuntu directory.""" |
4752 | + with ClusterConfiguration.open() as config: |
4753 | + tftp_root = config.tftp_root |
4754 | for arch, subarch, release, label in paths: |
4755 | - path = os.path.join( |
4756 | - config.BOOT_RESOURCES_STORAGE, 'current', |
4757 | - arch, subarch, release, label) |
4758 | + path = os.path.join(tftp_root, arch, subarch, release, label) |
4759 | if len(list_subdirs(path)) == 0: |
4760 | yield (arch, subarch, release, label) |
4761 | |
4762 | @@ -165,7 +167,8 @@ |
4763 | folders have structure arch/subarch/release/label and move them into |
4764 | ubuntu folder. Making the final path ubuntu/arch/subarch/release/label. |
4765 | """ |
4766 | - current_dir = os.path.join(config.BOOT_RESOURCES_STORAGE, "current") |
4767 | + with ClusterConfiguration.open() as config: |
4768 | + current_dir = config.tftp_root |
4769 | if not os.path.isdir(current_dir): |
4770 | return |
4771 | # If ubuntu folder already exists, then no reason to continue |
4772 | |
4773 | === modified file 'src/provisioningserver/utils/__init__.py' |
4774 | --- src/provisioningserver/utils/__init__.py 2015-05-29 16:47:37 +0000 |
4775 | +++ src/provisioningserver/utils/__init__.py 2015-07-03 11:18:28 +0000 |
4776 | @@ -18,7 +18,6 @@ |
4777 | "commission_node", |
4778 | "filter_dict", |
4779 | "flatten", |
4780 | - "get_cluster_config", |
4781 | "import_settings", |
4782 | "locate_config", |
4783 | "parse_key_value_file", |
4784 | @@ -116,7 +115,10 @@ |
4785 | """ |
4786 | # Avoid circular dependencies. |
4787 | from provisioningserver.rpc.region import CreateNode |
4788 | - from provisioningserver.cluster_config import get_cluster_uuid |
4789 | + from provisioningserver.config import ClusterConfiguration |
4790 | + |
4791 | + with ClusterConfiguration.open() as config: |
4792 | + cluster_uuid = config.cluster_uuid |
4793 | |
4794 | if hostname is not None: |
4795 | hostname = coerce_to_valid_hostname(hostname) |
4796 | @@ -138,7 +140,7 @@ |
4797 | try: |
4798 | response = yield client( |
4799 | CreateNode, |
4800 | - cluster_uuid=get_cluster_uuid(), |
4801 | + cluster_uuid=cluster_uuid, |
4802 | architecture=arch, |
4803 | power_type=power_type, |
4804 | power_parameters=json.dumps(power_parameters), |
4805 | @@ -216,24 +218,16 @@ |
4806 | def locate_config(*path): |
4807 | """Return the location of a given config file or directory. |
4808 | |
4809 | - Defaults to `/etc/maas` (followed by any further path elements you |
4810 | - specify), but can be overridden using the `MAAS_CONFIG_DIR` environment |
4811 | - variable. (When running from a branch, this variable will point to the |
4812 | - `etc/maas` inside the branch.) |
4813 | - |
4814 | - The result is absolute and normalized. |
4815 | + :param path: Path elements to resolve relative to `${MAAS_ROOT}/etc/maas`. |
4816 | """ |
4817 | - # Check for MAAS_CONFIG_DIR. Count empty string as "not set." |
4818 | - env_setting = os.getenv('MAAS_CONFIG_DIR', '') |
4819 | - if env_setting == '': |
4820 | - # Running from installed package. Config is in /etc/maas. |
4821 | - config_dir = '/etc/maas' |
4822 | + # The `os.curdir` avoids a crash when `path` is empty. |
4823 | + path = os.path.join(os.curdir, *path) |
4824 | + if os.path.isabs(path): |
4825 | + return path |
4826 | else: |
4827 | - # Running from branch or other customized setup. Config is at |
4828 | - # $MAAS_CONFIG_DIR/etc/maas. |
4829 | - config_dir = env_setting |
4830 | - |
4831 | - return os.path.abspath(os.path.join(config_dir, *path)) |
4832 | + # Avoid circular imports. |
4833 | + from provisioningserver.path import get_tentative_path |
4834 | + return get_tentative_path("etc", "maas", path) |
4835 | |
4836 | |
4837 | setting_expression = r""" |
4838 | @@ -245,15 +239,6 @@ |
4839 | """ |
4840 | |
4841 | |
4842 | -def get_cluster_config(config_path): |
4843 | - contents = open(config_path).read() |
4844 | - |
4845 | - results = re.findall( |
4846 | - setting_expression, contents, re.MULTILINE | re.VERBOSE) |
4847 | - |
4848 | - return dict(results) |
4849 | - |
4850 | - |
4851 | def find_settings(whence): |
4852 | """Return settings from `whence`, which is assumed to be a module.""" |
4853 | # XXX 2012-10-11 JeroenVermeulen, bug=1065456: Put this in a shared |
4854 | |
4855 | === modified file 'src/provisioningserver/utils/fs.py' |
4856 | --- src/provisioningserver/utils/fs.py 2015-05-29 16:47:37 +0000 |
4857 | +++ src/provisioningserver/utils/fs.py 2015-07-03 11:18:28 +0000 |
4858 | @@ -30,7 +30,6 @@ |
4859 | from itertools import count |
4860 | import os |
4861 | from os import ( |
4862 | - environ, |
4863 | rename, |
4864 | stat, |
4865 | chown, |
4866 | @@ -58,7 +57,13 @@ |
4867 | development mode it will return the path for the current development |
4868 | environment. |
4869 | """ |
4870 | - return environ.get("MAAS_PROVISION_CMD", "maas-provision") |
4871 | + # Avoid circular imports. |
4872 | + from provisioningserver.config import is_dev_environment |
4873 | + if is_dev_environment(): |
4874 | + from maastesting import root |
4875 | + return os.path.join(root, "bin", "maas-provision") |
4876 | + else: |
4877 | + return "maas-provision" |
4878 | |
4879 | |
4880 | def _write_temp_file(content, filename): |
4881 | |
4882 | === modified file 'src/provisioningserver/utils/script.py' |
4883 | --- src/provisioningserver/utils/script.py 2015-05-29 16:47:37 +0000 |
4884 | +++ src/provisioningserver/utils/script.py 2015-07-03 11:18:28 +0000 |
4885 | @@ -88,24 +88,7 @@ |
4886 | |
4887 | |
4888 | class MainScript(ActionScript): |
4889 | - """An `ActionScript` that always accepts a `--config-file` option. |
4890 | - |
4891 | - The `--config-file` option defaults to the value of |
4892 | - `MAAS_PROVISIONING_SETTINGS` in the process's environment, or absent |
4893 | - that, `$MAAS_CONFIG_DIR/pserv.yaml` (normally /etc/maas/pserv.yaml for |
4894 | - packaged installations, or when running from branch, the equivalent |
4895 | - inside that branch). |
4896 | - """ |
4897 | - |
4898 | - def __init__(self, description): |
4899 | - # Avoid circular imports. |
4900 | - from provisioningserver.config import Config |
4901 | - |
4902 | - super(MainScript, self).__init__(description) |
4903 | - self.parser.add_argument( |
4904 | - "-c", "--config-file", metavar="FILENAME", |
4905 | - help="Configuration file to load [%(default)s].", |
4906 | - default=Config.DEFAULT_FILENAME) |
4907 | + """An `ActionScript` denoting the main script in an application.""" |
4908 | |
4909 | |
4910 | class AtomicWriteScript: |
4911 | @@ -141,7 +124,7 @@ |
4912 | if args.mode is not None: |
4913 | mode = int(args.mode, 8) |
4914 | else: |
4915 | - mode = 0600 |
4916 | + mode = 0o600 |
4917 | atomic_write( |
4918 | content, args.filename, overwrite=not args.no_overwrite, |
4919 | mode=mode) |
4920 | |
4921 | === modified file 'src/provisioningserver/utils/tests/test_fs.py' |
4922 | --- src/provisioningserver/utils/tests/test_fs.py 2015-05-29 16:47:37 +0000 |
4923 | +++ src/provisioningserver/utils/tests/test_fs.py 2015-07-03 11:18:28 +0000 |
4924 | @@ -28,6 +28,7 @@ |
4925 | import time |
4926 | |
4927 | from lockfile import FileLock |
4928 | +from maastesting import root |
4929 | from maastesting.factory import factory |
4930 | from maastesting.fakemethod import FakeMethod |
4931 | from maastesting.matchers import MockCalledOnceWith |
4932 | @@ -37,11 +38,13 @@ |
4933 | Mock, |
4934 | sentinel, |
4935 | ) |
4936 | +import provisioningserver.config |
4937 | from provisioningserver.utils.fs import ( |
4938 | atomic_symlink, |
4939 | atomic_write, |
4940 | ensure_dir, |
4941 | FileLockProxy, |
4942 | + get_maas_provision_command, |
4943 | get_mtime, |
4944 | incremental_write, |
4945 | pick_new_mtime, |
4946 | @@ -284,6 +287,21 @@ |
4947 | self.assertEqual(now, pick_new_mtime(now)) |
4948 | |
4949 | |
4950 | +class TestGetMAASProvisionCommand(MAASTestCase): |
4951 | + |
4952 | + def test__returns_just_command_for_production(self): |
4953 | + self.patch(provisioningserver.config, "is_dev_environment") |
4954 | + provisioningserver.config.is_dev_environment.return_value = False |
4955 | + self.assertEqual("maas-provision", get_maas_provision_command()) |
4956 | + |
4957 | + def test__returns_full_path_for_development(self): |
4958 | + self.patch(provisioningserver.config, "is_dev_environment") |
4959 | + provisioningserver.config.is_dev_environment.return_value = True |
4960 | + self.assertEqual( |
4961 | + root.rstrip("/") + "/bin/maas-provision", |
4962 | + get_maas_provision_command()) |
4963 | + |
4964 | + |
4965 | class TestSudoWriteFile(MAASTestCase): |
4966 | """Testing for `sudo_write_file`.""" |
4967 | |
4968 | @@ -301,11 +319,9 @@ |
4969 | |
4970 | sudo_write_file(path, contents) |
4971 | |
4972 | - self.assertThat(fs_module.Popen, MockCalledOnceWith([ |
4973 | - 'sudo', '-n', 'maas-provision', 'atomic-write', |
4974 | - '--filename', path, '--mode', '0644', |
4975 | - ], |
4976 | - stdin=PIPE)) |
4977 | + self.assertThat(fs_module.Popen, MockCalledOnceWith( |
4978 | + ['sudo', '-n', get_maas_provision_command(), 'atomic-write', |
4979 | + '--filename', path, '--mode', '0644'], stdin=PIPE)) |
4980 | |
4981 | def test_encodes_contents(self): |
4982 | process = self.patch_popen() |
4983 | |
4984 | === modified file 'src/provisioningserver/utils/tests/test_script.py' |
4985 | --- src/provisioningserver/utils/tests/test_script.py 2015-05-29 16:47:37 +0000 |
4986 | +++ src/provisioningserver/utils/tests/test_script.py 2015-07-03 11:18:28 +0000 |
4987 | @@ -38,7 +38,6 @@ |
4988 | from provisioningserver.utils.script import ( |
4989 | ActionScript, |
4990 | AtomicWriteScript, |
4991 | - MainScript, |
4992 | ) |
4993 | from testtools.matchers import ( |
4994 | FileContains, |
4995 | @@ -158,30 +157,6 @@ |
4996 | self.assertEqual(1, error.code) |
4997 | |
4998 | |
4999 | -class TestMainScript(TestActionScript): |
5000 | - |
Tested this, seems to be working just fine so +1