Merge lp:~ltrager/maas/lp1592246 into lp:~maas-committers/maas/trunk
- lp1592246
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Lee Trager | ||||||||
Approved revision: | no longer in the source branch. | ||||||||
Merged at revision: | 5135 | ||||||||
Proposed branch: | lp:~ltrager/maas/lp1592246 | ||||||||
Merge into: | lp:~maas-committers/maas/trunk | ||||||||
Diff against target: |
651 lines (+227/-56) 11 files modified
src/maasserver/models/node.py (+2/-2) src/maasserver/models/tests/test_node.py (+30/-7) src/maasserver/rpc/regionservice.py (+2/-1) src/maasserver/start_up.py (+1/-1) src/maasserver/tests/test_start_up.py (+22/-0) src/provisioningserver/register_command.py (+19/-0) src/provisioningserver/rpc/clusterservice.py (+1/-1) src/provisioningserver/rpc/tests/test_clusterservice.py (+25/-1) src/provisioningserver/tests/test_register_command.py (+29/-2) src/provisioningserver/utils/env.py (+18/-4) src/provisioningserver/utils/tests/test_env.py (+78/-37) |
||||||||
To merge this branch: | bzr merge lp:~ltrager/maas/lp1592246 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Needs Fixing | ||
Blake Rouse (community) | Approve | ||
Andres Rodriguez (community) | Needs Information | ||
Review via email: mp+297729@code.launchpad.net |
Commit message
Remove /var/lib/maas_id on rack register and cache contents with get_maas_id
Description of the change
This stops maas-rackd, removes /var/lib/maas_id, and restarts it after the secret key and config has been written. This ensures that a rack doesn't register using a stale system_id. Deleting a rack also stops the service and disables it. By starting and enabling the service users now have an easy way to reregister a rack that was deleted.
get_maas_id now caches /var/lib/maas_id so deleting it during registration has no effect on a region running on the same machine.
Gavin Panella (allenap) wrote : | # |
I agree with Andres about the random system_id thing. The likelihood of
a collision is very small, almost certainly less risk than removing the
maas_id file unconditionally here.
Lee Trager (ltrager) wrote : | # |
> Hi Lee,
>
> I thought we said we were not going to do this, and decided that the random
> system id's would be the way to go?
I had this branch mostly done before the standup yesterday so I figured I'd post it. Besides having a fix for lp:1592246 it caches maas_id so its only read once per process instead of multiple times and resolves lp:1576357.
>
> That said, I have a few questions:
>
> 1. So if we are caching the maas_id, what happens if for whatever reason,
> maas-rack deletes /var/lib/
Both rackd and regiond look for a node object by system_id, hostname, or MAC address. If /var/lib/
> 2. This also means that there's a period of time where maas_id will not be
> present on the disk, and we agreed not to do that.
By caching /var/lib/
> 3. Have you thought about any corner cases? What happens if you don't delete a
> rack from the MAAS UI, and then try to re-register the rack? For what we
> discussed, this would mean that a new rack gets added resulting in two racks
> being present, when it should have been just one?
If you run maas-rack register on an already registered rack MAAS will find the existing node table entry from the hostname or MAC address and return the same system_id. The same goes for the region. The only way to get duplicate racks/regions is by changing the hostname and MAC addresses of an already registered host. Since this restarts maas-rackd the worst that will happen in this case is you'll have an extra rack entry which would need to be manually deleted.
Blake Rouse (blake-rouse) wrote : | # |
Looks good. You need to fix the two tests I commented on. Other than that this looks good.
Make sure you have tested this on an installed version of MAAS as well.
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~ltrager/maas/lp1592246 into lp:maas failed. Below is the output from the failed tests.
Get:1 http://
Hit:2 http://
Get:3 http://
Hit:4 http://
Get:5 http://
Fetched 286 kB in 0s (584 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubun
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
...
Gavin Panella (allenap) wrote : | # |
Minor comment.
Gavin Panella (allenap) wrote : | # |
I don't understand why the read_cache parameter is necessary, plus a few other things still need fixing.
Lee Trager (ltrager) wrote : | # |
I landed this on Friday to solve a couple of bugs for RC1 that we wanted in. The read_cache parameter is used to force get_maas_id to read from the disk even if something is cached. The main use case is for start_up. Right now the region object is created on the master process while the other processes wait for the region object to be created. If the user has a stale value(e.g from a previous install) in /var/lib/
I've posted a separate review for the corrections you've suggested at lp:~ltrager/maas/cleanup_lp1592246
Gavin Panella (allenap) : | # |
Preview Diff
1 | === modified file 'src/maasserver/models/node.py' |
2 | --- src/maasserver/models/node.py 2016-06-16 17:20:19 +0000 |
3 | +++ src/maasserver/models/node.py 2016-06-18 01:12:20 +0000 |
4 | @@ -551,8 +551,8 @@ |
5 | NODE_TYPE.REGION_AND_RACK_CONTROLLER, |
6 | ]} |
7 | |
8 | - def get_running_controller(self): |
9 | - return self.get(system_id=get_maas_id()) |
10 | + def get_running_controller(self, read_cache=True): |
11 | + return self.get(system_id=get_maas_id(read_cache)) |
12 | |
13 | |
14 | class RackControllerManager(ControllerManager): |
15 | |
16 | === modified file 'src/maasserver/models/tests/test_node.py' |
17 | --- src/maasserver/models/tests/test_node.py 2016-06-14 04:22:21 +0000 |
18 | +++ src/maasserver/models/tests/test_node.py 2016-06-18 01:12:20 +0000 |
19 | @@ -6,6 +6,7 @@ |
20 | __all__ = [] |
21 | |
22 | from datetime import datetime |
23 | +import os |
24 | import random |
25 | from unittest.mock import ( |
26 | ANY, |
27 | @@ -53,6 +54,7 @@ |
28 | BootResource, |
29 | BridgeInterface, |
30 | Config, |
31 | + Controller, |
32 | Device, |
33 | Domain, |
34 | Fabric, |
35 | @@ -136,6 +138,7 @@ |
36 | EVENT_DETAILS, |
37 | EVENT_TYPES, |
38 | ) |
39 | +from provisioningserver.path import get_path |
40 | from provisioningserver.power import QUERY_POWER_TYPES |
41 | from provisioningserver.power.schema import JSON_POWER_TYPE_PARAMETERS |
42 | from provisioningserver.rpc.cluster import ( |
43 | @@ -156,7 +159,12 @@ |
44 | map_enum, |
45 | map_enum_reverse, |
46 | ) |
47 | +from provisioningserver.utils.env import ( |
48 | + get_maas_id, |
49 | + set_maas_id, |
50 | +) |
51 | from provisioningserver.utils.fs import NamedLock |
52 | +from provisioningserver.utils.testing import MAASIDFixture |
53 | from testtools import ExpectedException |
54 | from testtools.matchers import ( |
55 | AfterPreprocessing, |
56 | @@ -301,7 +309,13 @@ |
57 | list(Machine.objects.get_available_machines_for_acquisition(user))) |
58 | |
59 | |
60 | -class TestControllerManager(MAASServerTestCase): |
61 | +class TestControllerManaer(MAASServerTestCase): |
62 | + |
63 | + def tearDown(self): |
64 | + super().tearDown() |
65 | + # Make sure the maas_id is cleared when done |
66 | + set_maas_id(None) |
67 | + |
68 | def test_controller_lists_node_type_rack_and_region(self): |
69 | racks_and_regions = set() |
70 | for _ in range(3): |
71 | @@ -311,13 +325,23 @@ |
72 | NODE_TYPE.RACK_CONTROLLER, NODE_TYPE.REGION_CONTROLLER, |
73 | NODE_TYPE.REGION_AND_RACK_CONTROLLER): |
74 | racks_and_regions.add(factory.make_Node(node_type=node_type)) |
75 | - self.assertItemsEqual(racks_and_regions, Node.controllers.all()) |
76 | + self.assertItemsEqual(racks_and_regions, Controller.objects.all()) |
77 | |
78 | def test_get_running_controller(self): |
79 | rack = factory.make_RackController() |
80 | - get_maas_id = self.patch(node_module, 'get_maas_id') |
81 | - get_maas_id.return_value = rack.system_id |
82 | - self.assertEquals(rack, Node.controllers.get_running_controller()) |
83 | + self.useFixture(MAASIDFixture(rack.system_id)) |
84 | + self.assertEquals(rack, Controller.objects.get_running_controller()) |
85 | + |
86 | + def test_get_running_controller_can_ignore_cache(self): |
87 | + set_maas_id(factory.make_string()) |
88 | + rack = factory.make_RackController() |
89 | + maas_id_path = get_path('/var/lib/maas/maas_id') |
90 | + os.unlink(maas_id_path) |
91 | + with open(maas_id_path, 'w') as fd: |
92 | + fd.write(rack.system_id) |
93 | + self.assertEquals( |
94 | + rack, Controller.objects.get_running_controller(read_cache=False)) |
95 | + self.assertEquals(rack.system_id, get_maas_id()) |
96 | |
97 | |
98 | class TestRackControllerManager(MAASServerTestCase): |
99 | @@ -418,8 +442,7 @@ |
100 | mock_getaddr_info = self.patch(node_module.socket, "getaddrinfo") |
101 | ip = random.choice(['127.0.0.1', '::1']) |
102 | mock_getaddr_info.return_value = (('', '', '', '', (ip,)),) |
103 | - get_maas_id = self.patch(node_module, 'get_maas_id') |
104 | - get_maas_id.return_value = rack.system_id |
105 | + self.useFixture(MAASIDFixture(rack.system_id)) |
106 | self.assertEquals( |
107 | [rack, ], |
108 | RackController.objects.filter_by_url_accessible(ip, False)) |
109 | |
110 | === modified file 'src/maasserver/rpc/regionservice.py' |
111 | --- src/maasserver/rpc/regionservice.py 2016-06-14 03:04:05 +0000 |
112 | +++ src/maasserver/rpc/regionservice.py 2016-06-18 01:12:20 +0000 |
113 | @@ -1171,7 +1171,8 @@ |
114 | |
115 | :return: An instance of `RegionAdvertising`. |
116 | """ |
117 | - region_obj = RegionController.objects.get_running_controller() |
118 | + region_obj = RegionController.objects.get_running_controller( |
119 | + read_cache=False) |
120 | # Create the process for this region. This process object will be |
121 | # updated by calls to `update`, which is the responsibility of the |
122 | # rpc-advertise service. Calls to `update` also remove old process |
123 | |
124 | === modified file 'src/maasserver/start_up.py' |
125 | --- src/maasserver/start_up.py 2016-06-16 01:30:23 +0000 |
126 | +++ src/maasserver/start_up.py 2016-06-18 01:12:20 +0000 |
127 | @@ -140,7 +140,7 @@ |
128 | |
129 | |
130 | def create_region_obj(): |
131 | - region_id = get_maas_id() |
132 | + region_id = get_maas_id(read_cache=False) |
133 | hostname = gethostname() |
134 | update_fields = [] |
135 | region_filter = Q() if region_id is None else Q(system_id=region_id) |
136 | |
137 | === modified file 'src/maasserver/tests/test_start_up.py' |
138 | --- src/maasserver/tests/test_start_up.py 2016-06-16 01:30:23 +0000 |
139 | +++ src/maasserver/tests/test_start_up.py 2016-06-18 01:12:20 +0000 |
140 | @@ -5,6 +5,7 @@ |
141 | |
142 | __all__ = [] |
143 | |
144 | +import os |
145 | from unittest.mock import call |
146 | |
147 | from maasserver import ( |
148 | @@ -29,6 +30,8 @@ |
149 | MockCallsMatch, |
150 | MockNotCalled, |
151 | ) |
152 | +from provisioningserver.path import get_path |
153 | +from provisioningserver.utils.env import set_maas_id |
154 | from provisioningserver.utils.testing import MAASIDFixture |
155 | |
156 | |
157 | @@ -61,6 +64,7 @@ |
158 | super(TestStartUp, self).tearDown() |
159 | # start_up starts the Twisted event loop, so we need to stop it. |
160 | eventloop.reset().wait(5) |
161 | + set_maas_id(None) |
162 | |
163 | def test_inner_start_up_runs_in_exclusion(self): |
164 | # Disable boot source cache signals. |
165 | @@ -112,6 +116,11 @@ |
166 | self.addCleanup(bootsources.signals.enable) |
167 | bootsources.signals.disable() |
168 | |
169 | + def tearDown(self): |
170 | + super().tearDown() |
171 | + # Clear maas_id cache |
172 | + set_maas_id(None) |
173 | + |
174 | def test__calls_dns_kms_setting_changed_if_master(self): |
175 | self.patch(start_up, "is_master_process").return_value = True |
176 | self.patch(start_up, "post_commit_do") |
177 | @@ -162,12 +171,25 @@ |
178 | |
179 | """Tests for the actual work done in `create_region_obj`.""" |
180 | |
181 | + def tearDown(self): |
182 | + super().tearDown() |
183 | + # Clear maas_id cache |
184 | + set_maas_id(None) |
185 | + |
186 | def test__creates_obj(self): |
187 | region = start_up.create_region_obj() |
188 | self.assertIsNotNone(region) |
189 | self.assertIsNotNone( |
190 | RegionController.objects.get(system_id=region.system_id)) |
191 | |
192 | + def test__doesnt_read_maas_id_from_cache(self): |
193 | + set_maas_id(factory.make_string()) |
194 | + os.unlink(get_path('/var/lib/maas/maas_id')) |
195 | + region = start_up.create_region_obj() |
196 | + self.assertIsNotNone(region) |
197 | + self.assertIsNotNone( |
198 | + RegionController.objects.get(system_id=region.system_id)) |
199 | + |
200 | def test__finds_region_by_maas_id(self): |
201 | region = factory.make_RegionController() |
202 | self.useFixture(MAASIDFixture(region.system_id)) |
203 | |
204 | === modified file 'src/provisioningserver/register_command.py' |
205 | --- src/provisioningserver/register_command.py 2016-06-03 22:32:14 +0000 |
206 | +++ src/provisioningserver/register_command.py 2016-06-18 01:12:20 +0000 |
207 | @@ -30,6 +30,11 @@ |
208 | set_shared_secret_on_filesystem, |
209 | to_bin, |
210 | ) |
211 | +from provisioningserver.utils.env import set_maas_id |
212 | +from provisioningserver.utils.shell import ( |
213 | + call_and_check, |
214 | + ExternalProcessError, |
215 | +) |
216 | |
217 | |
218 | all_arguments = ( |
219 | @@ -103,6 +108,14 @@ |
220 | "MAAS region controller URL must be given when supplying the " |
221 | "shared secret via stdin with a non-interactive shell.") |
222 | raise SystemExit(1) |
223 | + try: |
224 | + call_and_check(['systemctl', 'stop', 'maas-rackd']) |
225 | + except ExternalProcessError as e: |
226 | + print("Unable to stop maas-rackd service.") |
227 | + print("Failed with error %s." % e.output_as_unicode) |
228 | + return |
229 | + # maas_id could be stale so remove it |
230 | + set_maas_id(None) |
231 | if args.url is not None: |
232 | with ClusterConfiguration.open_for_update() as config: |
233 | config.maas_url = args.url |
234 | @@ -122,3 +135,9 @@ |
235 | set_shared_secret_on_filesystem(to_bin(args.secret)) |
236 | else: |
237 | InstallSharedSecretScript.run(args) |
238 | + try: |
239 | + call_and_check(['systemctl', 'enable', 'maas-rackd']) |
240 | + call_and_check(['systemctl', 'start', 'maas-rackd']) |
241 | + except ExternalProcessError as e: |
242 | + print("Unable to enable and start the maas-rackd service") |
243 | + print("Failed with error %s." % e.output_as_unicode) |
244 | |
245 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
246 | --- src/provisioningserver/rpc/clusterservice.py 2016-06-14 04:39:10 +0000 |
247 | +++ src/provisioningserver/rpc/clusterservice.py 2016-06-18 01:12:20 +0000 |
248 | @@ -531,7 +531,7 @@ |
249 | cluster_uuid = config.cluster_uuid |
250 | |
251 | # Grab the set system_id if already set for this controller. |
252 | - system_id = get_maas_id() |
253 | + system_id = get_maas_id(read_cache=False) |
254 | if system_id is None: |
255 | # Cannot send None over RPC when the system_id is not set. |
256 | system_id = '' |
257 | |
258 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' |
259 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2016-06-14 04:39:10 +0000 |
260 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2016-06-18 01:12:20 +0000 |
261 | @@ -61,6 +61,7 @@ |
262 | power_drivers_by_name, |
263 | PowerError, |
264 | ) |
265 | +from provisioningserver.path import get_path |
266 | from provisioningserver.power import QUERY_POWER_TYPES |
267 | from provisioningserver.power.schema import JSON_POWER_TYPE_PARAMETERS |
268 | from provisioningserver.rpc import ( |
269 | @@ -95,6 +96,7 @@ |
270 | from provisioningserver.security import set_shared_secret_on_filesystem |
271 | from provisioningserver.service_monitor import service_monitor |
272 | from provisioningserver.testing.config import ClusterConfigurationFixture |
273 | +from provisioningserver.utils.env import set_maas_id |
274 | from provisioningserver.utils.fs import NamedLock |
275 | from provisioningserver.utils.network import get_all_interfaces_definition |
276 | from provisioningserver.utils.shell import ExternalProcessError |
277 | @@ -844,7 +846,7 @@ |
278 | self.set_maas_id = self.patch(clusterservice, "set_maas_id") |
279 | self.set_maas_id.side_effect = set_maas_id |
280 | |
281 | - def get_maas_id(): |
282 | + def get_maas_id(read_cache=True): |
283 | return self.maas_id |
284 | |
285 | self.get_maas_id = self.patch(clusterservice, "get_maas_id") |
286 | @@ -1247,6 +1249,28 @@ |
287 | self.assertThat(self.set_maas_id, MockCalledOnceWith(system_id)) |
288 | |
289 | @inlineCallbacks |
290 | + def test_registerRackWithRegion_doesnt_read_maas_id_from_cache(self): |
291 | + set_maas_id(factory.make_string()) |
292 | + os.unlink(get_path('/var/lib/maas/maas_id')) |
293 | + |
294 | + maas_url = factory.make_simple_http_url() |
295 | + hostname = platform.node().split('.')[0] |
296 | + interfaces = get_all_interfaces_definition() |
297 | + self.useFixture(ClusterConfigurationFixture( |
298 | + maas_url=maas_url)) |
299 | + fixture = self.useFixture(MockLiveClusterToRegionRPCFixture()) |
300 | + protocol, connecting = fixture.makeEventLoop() |
301 | + self.addCleanup((yield connecting)) |
302 | + yield getRegionClient() |
303 | + self.assertThat( |
304 | + protocol.RegisterRackController, MockCalledOnceWith( |
305 | + protocol, system_id='', hostname=hostname, |
306 | + interfaces=interfaces, url=urlparse(maas_url), |
307 | + nodegroup_uuid=None)) |
308 | + # Clear cache for the next test |
309 | + set_maas_id(None) |
310 | + |
311 | + @inlineCallbacks |
312 | def test_registerRackWithRegion_returns_False_when_rejected(self): |
313 | client = self.make_running_client() |
314 | |
315 | |
316 | === modified file 'src/provisioningserver/tests/test_register_command.py' |
317 | --- src/provisioningserver/tests/test_register_command.py 2016-06-03 18:48:32 +0000 |
318 | +++ src/provisioningserver/tests/test_register_command.py 2016-06-18 01:12:20 +0000 |
319 | @@ -9,10 +9,16 @@ |
320 | import io |
321 | from itertools import combinations |
322 | import pprint |
323 | -from unittest.mock import Mock |
324 | +from unittest.mock import ( |
325 | + call, |
326 | + Mock, |
327 | +) |
328 | |
329 | from maastesting.factory import factory |
330 | -from maastesting.matchers import MockCalledOnceWith |
331 | +from maastesting.matchers import ( |
332 | + MockCalledOnceWith, |
333 | + MockCallsMatch, |
334 | +) |
335 | from maastesting.testcase import MAASTestCase |
336 | from provisioningserver import register_command |
337 | from provisioningserver.config import ClusterConfiguration |
338 | @@ -22,6 +28,8 @@ |
339 | to_hex, |
340 | ) |
341 | from provisioningserver.testing.config import ClusterConfigurationFixture |
342 | +from provisioningserver.utils.env import get_maas_id |
343 | +from provisioningserver.utils.testing import MAASIDFixture |
344 | from testtools.matchers import Equals |
345 | |
346 | |
347 | @@ -92,6 +100,8 @@ |
348 | def setUp(self): |
349 | super(TestRegisterMAASRack, self).setUp() |
350 | self.useFixture(ClusterConfigurationFixture()) |
351 | + self.mock_call_and_check = self.patch_autospec( |
352 | + register_command, 'call_and_check') |
353 | |
354 | def make_args(self, **kwargs): |
355 | args = Mock() |
356 | @@ -170,3 +180,20 @@ |
357 | input.side_effect = KeyboardInterrupt |
358 | self.assertRaises( |
359 | KeyboardInterrupt, register_command.run, args) |
360 | + |
361 | + def test__restarts_maas_rackd_service(self): |
362 | + url = factory.make_simple_http_url() |
363 | + secret = factory.make_bytes() |
364 | + register_command.run(self.make_args(url=url, secret=to_hex(secret))) |
365 | + self.assertThat(self.mock_call_and_check, MockCallsMatch( |
366 | + call(['systemctl', 'stop', 'maas-rackd']), |
367 | + call(['systemctl', 'enable', 'maas-rackd']), |
368 | + call(['systemctl', 'start', 'maas-rackd']) |
369 | + )) |
370 | + |
371 | + def test__deletes_maas_id_file(self): |
372 | + self.useFixture(MAASIDFixture(factory.make_string())) |
373 | + url = factory.make_simple_http_url() |
374 | + secret = factory.make_bytes() |
375 | + register_command.run(self.make_args(url=url, secret=to_hex(secret))) |
376 | + self.assertIsNone(get_maas_id()) |
377 | |
378 | === modified file 'src/provisioningserver/utils/env.py' |
379 | --- src/provisioningserver/utils/env.py 2016-06-07 11:34:46 +0000 |
380 | +++ src/provisioningserver/utils/env.py 2016-06-18 01:12:20 +0000 |
381 | @@ -35,10 +35,19 @@ |
382 | os.environ.update(prior_environ) |
383 | |
384 | |
385 | -def get_maas_id(): |
386 | +# Cache the MAAS ID so we don't have to keep reading it from the filesystem. |
387 | +# This avoids errors when running maas-rack register while regiond is running. |
388 | +_maas_id = None |
389 | + |
390 | + |
391 | +def get_maas_id(read_cache=True): |
392 | """Return the system_id for this rack/region controller that is created |
393 | when either the rack or region first starts. |
394 | """ |
395 | + global _maas_id |
396 | + if read_cache and _maas_id is not None: |
397 | + return _maas_id |
398 | + |
399 | maas_id_path = get_path('/var/lib/maas/maas_id') |
400 | try: |
401 | with open(maas_id_path, "r", encoding="ascii") as fp: |
402 | @@ -46,19 +55,24 @@ |
403 | except FileNotFoundError: |
404 | return None |
405 | else: |
406 | - return _normalise_maas_id(contents) |
407 | + _maas_id = _normalise_maas_id(contents) |
408 | + return _maas_id |
409 | |
410 | |
411 | def set_maas_id(system_id): |
412 | """Set the system_id for this rack/region permanently for MAAS.""" |
413 | + global _maas_id |
414 | maas_id_path = get_path('/var/lib/maas/maas_id') |
415 | - if _normalise_maas_id(system_id) is None: |
416 | + maas_id = _normalise_maas_id(system_id) |
417 | + if maas_id is None: |
418 | try: |
419 | atomic_delete(maas_id_path) |
420 | except FileNotFoundError: |
421 | pass # Job done already. |
422 | + _maas_id = None |
423 | else: |
424 | - atomic_write(system_id.encode("ascii"), maas_id_path) |
425 | + atomic_write(maas_id.encode("ascii"), maas_id_path) |
426 | + _maas_id = maas_id |
427 | |
428 | |
429 | def _normalise_maas_id(system_id): |
430 | |
431 | === modified file 'src/provisioningserver/utils/tests/test_env.py' |
432 | --- src/provisioningserver/utils/tests/test_env.py 2016-06-07 11:34:46 +0000 |
433 | +++ src/provisioningserver/utils/tests/test_env.py 2016-06-18 01:12:20 +0000 |
434 | @@ -11,11 +11,8 @@ |
435 | from maastesting.factory import factory |
436 | from maastesting.testcase import MAASTestCase |
437 | from provisioningserver.path import get_path |
438 | -from provisioningserver.utils.env import ( |
439 | - environment_variables, |
440 | - get_maas_id, |
441 | - set_maas_id, |
442 | -) |
443 | +from provisioningserver.utils import env |
444 | +from testtools import ExpectedException |
445 | from testtools.matchers import ( |
446 | Equals, |
447 | FileContains, |
448 | @@ -23,50 +20,49 @@ |
449 | Is, |
450 | Not, |
451 | ) |
452 | -from testtools.testcase import ExpectedException |
453 | |
454 | |
455 | class TestEnvironmentVariables(MAASTestCase): |
456 | - """Tests for `environment_variables`.""" |
457 | + """Tests for `env.environment_variables`.""" |
458 | |
459 | def make_variable(self): |
460 | return factory.make_name('testvar'), factory.make_name('value') |
461 | |
462 | def test__sets_variables(self): |
463 | var, value = self.make_variable() |
464 | - with environment_variables({var: value}): |
465 | - env = os.environ.copy() |
466 | - self.assertEqual(value, env[var]) |
467 | + with env.environment_variables({var: value}): |
468 | + environment = os.environ.copy() |
469 | + self.assertEqual(value, environment[var]) |
470 | |
471 | def test__overrides_prior_values(self): |
472 | var, prior_value = self.make_variable() |
473 | temp_value = factory.make_name('temp-value') |
474 | - with environment_variables({var: prior_value}): |
475 | - with environment_variables({var: temp_value}): |
476 | - env = os.environ.copy() |
477 | - self.assertEqual(temp_value, env[var]) |
478 | + with env.environment_variables({var: prior_value}): |
479 | + with env.environment_variables({var: temp_value}): |
480 | + environment = os.environ.copy() |
481 | + self.assertEqual(temp_value, environment[var]) |
482 | |
483 | def test__leaves_other_variables_intact(self): |
484 | untouched_var, untouched_value = self.make_variable() |
485 | var, value = self.make_variable() |
486 | - with environment_variables({untouched_var: untouched_value}): |
487 | - with environment_variables({var: value}): |
488 | - env = os.environ.copy() |
489 | - self.assertEqual(untouched_value, env[untouched_var]) |
490 | + with env.environment_variables({untouched_var: untouched_value}): |
491 | + with env.environment_variables({var: value}): |
492 | + environment = os.environ.copy() |
493 | + self.assertEqual(untouched_value, environment[untouched_var]) |
494 | |
495 | def test__restores_variables_to_previous_values(self): |
496 | var, prior_value = self.make_variable() |
497 | temp_value = factory.make_name('temp-value') |
498 | - with environment_variables({var: prior_value}): |
499 | - with environment_variables({var: temp_value}): |
500 | + with env.environment_variables({var: prior_value}): |
501 | + with env.environment_variables({var: temp_value}): |
502 | pass |
503 | - env = os.environ.copy() |
504 | - self.assertEqual(prior_value, env[var]) |
505 | + environment = os.environ.copy() |
506 | + self.assertEqual(prior_value, environment[var]) |
507 | |
508 | def test__restores_previously_unset_variables_to_being_unset(self): |
509 | var, value = self.make_variable() |
510 | self.assertNotIn(var, os.environ) |
511 | - with environment_variables({var: value}): |
512 | + with env.environment_variables({var: value}): |
513 | pass |
514 | self.assertNotIn(var, os.environ) |
515 | |
516 | @@ -78,7 +74,7 @@ |
517 | pass |
518 | |
519 | with ExpectedException(DeliberateException): |
520 | - with environment_variables({var: value}): |
521 | + with env.environment_variables({var: value}): |
522 | raise DeliberateException() |
523 | |
524 | self.assertNotIn(var, os.environ) |
525 | @@ -93,32 +89,36 @@ |
526 | |
527 | |
528 | class TestMAASID(MAASTestCase): |
529 | - """Tests for `get_maas_id` and `set_maas_id`.""" |
530 | + """Tests for `env.get_maas_id` and `env.set_maas_id`.""" |
531 | |
532 | def setUp(self): |
533 | super(TestMAASID, self).setUp() |
534 | self.maas_id_path = get_path('/var/lib/maas/maas_id') |
535 | self.addCleanup(unlink_if_exists, self.maas_id_path) |
536 | |
537 | + def tearDown(self): |
538 | + super(TestMAASID, self).tearDown() |
539 | + env.set_maas_id(None) |
540 | + |
541 | def test_get_returns_None_if_maas_id_file_does_not_exist(self): |
542 | self.assertThat(self.maas_id_path, Not(FileExists())) |
543 | - self.assertThat(get_maas_id(), Is(None)) |
544 | + self.assertThat(env.get_maas_id(), Is(None)) |
545 | |
546 | def test_get_returns_None_if_maas_id_file_is_empty(self): |
547 | with open(self.maas_id_path, "w"): |
548 | pass # Write nothing. |
549 | - self.assertThat(get_maas_id(), Is(None)) |
550 | + self.assertThat(env.get_maas_id(), Is(None)) |
551 | |
552 | def test_get_returns_None_if_maas_id_file_is_whitespace(self): |
553 | with open(self.maas_id_path, "w") as fd: |
554 | fd.write(string.whitespace) |
555 | - self.assertThat(get_maas_id(), Is(None)) |
556 | + self.assertThat(env.get_maas_id(), Is(None)) |
557 | |
558 | def test_get_returns_contents_if_maas_id_file_contains_something(self): |
559 | contents = factory.make_name("contents") |
560 | with open(self.maas_id_path, "w") as fd: |
561 | fd.write(contents) |
562 | - self.assertThat(get_maas_id(), Equals(contents)) |
563 | + self.assertThat(env.get_maas_id(), Equals(contents)) |
564 | |
565 | def test_get_strips_contents_if_maas_id_file_contains_something(self): |
566 | contents = factory.make_name("contents") |
567 | @@ -126,35 +126,76 @@ |
568 | fd.write(string.whitespace) |
569 | fd.write(contents) |
570 | fd.write(string.whitespace) |
571 | - self.assertThat(get_maas_id(), Equals(contents)) |
572 | + self.assertThat(env.get_maas_id(), Equals(contents)) |
573 | |
574 | def test_get_rejects_non_ASCII_content(self): |
575 | contents = factory.make_unicode_non_ascii_string() |
576 | with open(self.maas_id_path, "w") as fd: |
577 | fd.write(contents) |
578 | - self.assertRaises(UnicodeDecodeError, get_maas_id) |
579 | + self.assertRaises(UnicodeDecodeError, env.get_maas_id) |
580 | + |
581 | + def test_get_caches_result(self): |
582 | + contents = factory.make_name("contents") |
583 | + with open(self.maas_id_path, "w") as fd: |
584 | + fd.write(contents) |
585 | + self.assertEquals(contents, env.get_maas_id()) |
586 | + os.unlink(self.maas_id_path) |
587 | + self.assertEquals(contents, env.get_maas_id()) |
588 | + |
589 | + def test_get_can_ignore_cache(self): |
590 | + contents = factory.make_name("contents") |
591 | + with open(self.maas_id_path, "w") as fd: |
592 | + fd.write(contents) |
593 | + self.assertEquals(contents, env.get_maas_id()) |
594 | + contents = factory.make_name("contents") |
595 | + os.unlink(self.maas_id_path) |
596 | + with open(self.maas_id_path, "w") as fd: |
597 | + fd.write(contents) |
598 | + self.assertEquals(contents, env.get_maas_id(read_cache=False)) |
599 | |
600 | def test_set_writes_argument_to_maas_id_file(self): |
601 | contents = factory.make_name("contents") |
602 | - set_maas_id(contents) |
603 | + env.set_maas_id(contents) |
604 | self.assertThat(self.maas_id_path, FileContains(contents)) |
605 | |
606 | def test_set_deletes_maas_id_file_if_argument_is_None(self): |
607 | with open(self.maas_id_path, "w") as fd: |
608 | fd.write("This file will be deleted.") |
609 | - set_maas_id(None) |
610 | + env.set_maas_id(None) |
611 | self.assertThat(self.maas_id_path, Not(FileExists())) |
612 | + self.assertIsNone(env.get_maas_id()) |
613 | |
614 | def test_set_deletes_maas_id_file_if_argument_is_whitespace(self): |
615 | with open(self.maas_id_path, "w") as fd: |
616 | fd.write("This file will be deleted.") |
617 | - set_maas_id(string.whitespace) |
618 | + env.set_maas_id(string.whitespace) |
619 | self.assertThat(self.maas_id_path, Not(FileExists())) |
620 | + self.assertIsNone(env.get_maas_id()) |
621 | |
622 | - def test_set_None_does_nothing_if_maas_id_file_idoes_not_exist(self): |
623 | + def test_set_None_does_nothing_if_maas_id_file_does_not_exist(self): |
624 | self.assertThat(self.maas_id_path, Not(FileExists())) |
625 | - set_maas_id(None) |
626 | + env.set_maas_id(None) |
627 | |
628 | def test_set_rejects_non_ASCII_content(self): |
629 | contents = factory.make_unicode_non_ascii_string() |
630 | - self.assertRaises(UnicodeEncodeError, set_maas_id, contents) |
631 | + self.assertRaises(UnicodeEncodeError, env.set_maas_id, contents) |
632 | + |
633 | + def test_set_caches(self): |
634 | + contents = factory.make_name("contents") |
635 | + env.set_maas_id(contents) |
636 | + os.unlink(self.maas_id_path) |
637 | + self.assertEquals(contents, env.get_maas_id()) |
638 | + |
639 | + def test_set_doesnt_cache_when_write_fails(self): |
640 | + mock_atomic_write = self.patch_autospec(env, 'atomic_write') |
641 | + exception = factory.make_exception() |
642 | + mock_atomic_write.side_effect = exception |
643 | + contents = factory.make_name("contents") |
644 | + with ExpectedException(type(exception)): |
645 | + env.set_maas_id(contents) |
646 | + self.assertIsNone(env.get_maas_id()) |
647 | + |
648 | + def test_set_caches_to_normalized_value(self): |
649 | + contents = " %s " % factory.make_name("contents") |
650 | + env.set_maas_id(contents) |
651 | + self.assertEquals(env._normalise_maas_id(contents), env.get_maas_id()) |
Hi Lee,
I thought we said we were not going to do this, and decided that the random system id's would be the way to go?
That said, I have a few questions:
1. So if we are caching the maas_id, what happens if for whatever reason, maas-rack deletes /var/lib/ maas/maas_ id and the rack controller is restarted?
2. This also means that there's a period of time where maas_id will not be present on the disk, and we agreed not to do that.
3. Have you thought about any corner cases? What happens if you don't delete a rack from the MAAS UI, and then try to re-register the rack? For what we discussed, this would mean that a new rack gets added resulting in two racks being present, when it should have been just one?