Merge lp:~allenap/maas/duplicate-vlan-on-start--bug-1583333 into lp:maas/trunk

Proposed by Gavin Panella on 2016-05-27
Status: Merged
Approved by: Gavin Panella on 2016-05-31
Approved revision: 5060
Merged at revision: 5058
Proposed branch: lp:~allenap/maas/duplicate-vlan-on-start--bug-1583333
Merge into: lp:maas/trunk
Diff against target: 675 lines (+243/-147)
8 files modified
src/maasserver/locks.py (+11/-1)
src/maasserver/models/node.py (+7/-0)
src/maasserver/models/tests/test_node.py (+20/-0)
src/maasserver/rpc/rackcontrollers.py (+78/-70)
src/maasserver/rpc/regionservice.py (+4/-7)
src/maasserver/rpc/testing/fixtures.py (+9/-11)
src/maasserver/rpc/tests/test_rackcontrollers.py (+110/-58)
src/maasserver/rpc/tests/test_regionservice.py (+4/-0)
To merge this branch: bzr merge lp:~allenap/maas/duplicate-vlan-on-start--bug-1583333
Reviewer Review Type Date Requested Status
Blake Rouse 2016-05-27 Approve on 2016-05-31
Review via email: mp+295956@code.launchpad.net

Commit message

Register rack controllers in the region serially.

To post a comment you must log in.
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve
MAAS Lander (maas-lander) wrote :
Download full text (31.6 KiB)

The attempt to merge lp:~allenap/maas/duplicate-vlan-on-start--bug-1583333 into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [94.5 kB]
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Get:4 http://security.ubuntu.com/ubuntu xenial-security InRelease [94.5 kB]
Get:5 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages [165 kB]
Get:6 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [76.9 kB]
Get:7 http://security.ubuntu.com/ubuntu xenial-security/main Sources [20.2 kB]
Get:8 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [76.5 kB]
Get:9 http://security.ubuntu.com/ubuntu xenial-security/main Translation-en [29.6 kB]
Fetched 557 kB in 0s (1,215 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
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.20160115ubuntu3).
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 ve...

5059. By Gavin Panella on 2016-05-31

Merged trunk into duplicate-vlan-on-start--bug-1583333.

5060. By Gavin Panella on 2016-05-31

Update test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/locks.py'
2--- src/maasserver/locks.py 2015-12-01 18:12:59 +0000
3+++ src/maasserver/locks.py 2016-05-31 19:19:02 +0000
4@@ -1,12 +1,17 @@
5-# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
6+# Copyright 2014-2016 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Region-wide locks."""
10
11 __all__ = [
12+ "dns",
13 "eventloop",
14+ "import_images",
15+ "node_acquire",
16+ "rack_registration",
17 "security",
18 "startup",
19+ "staticip_acquire",
20 ]
21
22 from maasserver.utils.dblocks import (
23@@ -35,3 +40,8 @@
24
25 # Lock to prevent concurrent allocation of StaticIPAddress
26 staticip_acquire = DatabaseXactLock(8)
27+
28+# Lock to prevent concurrent registration of rack controllers. This can be a
29+# problem because registration involves populating fabrics, VLANs, and other
30+# information that may overlap between rack controller.
31+rack_registration = DatabaseLock(9)
32
33=== modified file 'src/maasserver/models/node.py'
34--- src/maasserver/models/node.py 2016-05-26 17:07:30 +0000
35+++ src/maasserver/models/node.py 2016-05-31 19:19:02 +0000
36@@ -877,6 +877,13 @@
37 ]
38
39 @property
40+ def is_region_controller(self):
41+ return self.node_type in [
42+ NODE_TYPE.REGION_AND_RACK_CONTROLLER,
43+ NODE_TYPE.REGION_CONTROLLER,
44+ ]
45+
46+ @property
47 def is_controller(self):
48 return self.node_type in [
49 NODE_TYPE.REGION_CONTROLLER,
50
51=== modified file 'src/maasserver/models/tests/test_node.py'
52--- src/maasserver/models/tests/test_node.py 2016-05-26 17:07:30 +0000
53+++ src/maasserver/models/tests/test_node.py 2016-05-31 19:19:02 +0000
54@@ -517,6 +517,26 @@
55 rack = factory.make_RackController()
56 self.assertTrue(rack.is_rack_controller)
57
58+ def test_is_region_controller_machine(self):
59+ machine = factory.make_Node()
60+ self.assertFalse(machine.is_region_controller)
61+
62+ def test_is_region_controller_device(self):
63+ device = factory.make_Device()
64+ self.assertFalse(device.is_region_controller)
65+
66+ def test_is_region_controller_region_controller(self):
67+ region = factory.make_RegionController()
68+ self.assertTrue(region.is_region_controller)
69+
70+ def test_is_region_controller_region_rack_controller(self):
71+ region_rack = factory.make_RegionRackController()
72+ self.assertTrue(region_rack.is_region_controller)
73+
74+ def test_is_region_controller_rack_controller(self):
75+ rack = factory.make_RackController()
76+ self.assertFalse(rack.is_region_controller)
77+
78 def test_is_controller_machine(self):
79 machine = factory.make_Node()
80 self.assertFalse(machine.is_controller)
81
82=== modified file 'src/maasserver/rpc/rackcontrollers.py'
83--- src/maasserver/rpc/rackcontrollers.py 2016-05-24 07:30:16 +0000
84+++ src/maasserver/rpc/rackcontrollers.py 2016-05-31 19:19:02 +0000
85@@ -4,16 +4,19 @@
86 """RPC helpers relating to rack controllers."""
87
88 __all__ = [
89- "register_rackcontroller",
90+ "handle_upgrade",
91+ "register",
92+ "update_interfaces",
93 "update_last_image_sync",
94 ]
95
96-from django.db import (
97- IntegrityError,
98- transaction,
99-)
100+from typing import Optional
101+
102 from django.db.models import Q
103-from maasserver import worker_user
104+from maasserver import (
105+ locks,
106+ worker_user,
107+)
108 from maasserver.enum import NODE_TYPE
109 from maasserver.models import (
110 Node,
111@@ -23,8 +26,13 @@
112 )
113 from maasserver.models.node import typecast_node
114 from maasserver.models.timestampedmodel import now
115-from maasserver.utils.orm import transactional
116+from maasserver.utils import synchronised
117+from maasserver.utils.orm import (
118+ transactional,
119+ with_connection,
120+)
121 from provisioningserver.logger import get_maas_logger
122+from provisioningserver.utils import typed
123 from provisioningserver.utils.twisted import synchronous
124
125
126@@ -59,22 +67,37 @@
127
128
129 @synchronous
130+@with_connection
131+@synchronised(locks.rack_registration)
132 @transactional
133-def register_rackcontroller(
134- system_id=None, hostname='', interfaces={}, url=None,
135- nodegroup_uuid=None):
136+def register(system_id=None, hostname='', interfaces=None, url=None):
137 """Register a new rack controller if not already registered.
138
139 Attempt to see if the rack controller was already registered as a node.
140 This can be looked up either by system_id, hostname, or mac address. If
141- found convert the existing node into a rack controller. If not found create
142- a new rack controller. After the rack controller has been registered and
143- successfully connected we will refresh all commissioning data."""
144- rackcontroller = find_and_register_existing(
145- system_id, hostname, interfaces)
146- if rackcontroller is None:
147- rackcontroller = register_new_rackcontroller(system_id, hostname)
148-
149+ found convert the existing node into a rack controller. If not found
150+ create a new rack controller. After the rack controller has been
151+ registered and successfully connected we will refresh all commissioning
152+ data.
153+
154+ :return: A ``(rack-controller, refresh-hint)`` tuple.
155+ """
156+ if interfaces is None:
157+ interfaces = {}
158+
159+ node = find(system_id, hostname, interfaces)
160+ if node is None:
161+ node = RackController.objects.create(hostname=hostname)
162+ maaslog.info("Created new rack controller %s.", node.hostname)
163+ elif node.is_rack_controller:
164+ maaslog.info("Registering existing rack controller %s.", node.hostname)
165+ else:
166+ maaslog.info(
167+ "Found existing node %s as candidate for rack controller.",
168+ node.hostname)
169+
170+ # This may be a no-op, but it does provide us with a refresh hint.
171+ rackcontroller, needs_refresh = upgrade(node)
172 # Update `rackcontroller.url` from the given URL, but only when the
173 # hostname is not 'localhost' (i.e. the default value used when the master
174 # cluster connects).
175@@ -87,31 +110,46 @@
176 rackcontroller.owner = worker_user.get_worker_user()
177 update_fields.append("owner")
178 rackcontroller.save(update_fields=update_fields)
179- return rackcontroller
180-
181-
182-def find_and_register_existing(system_id, hostname, interfaces):
183- mac_addresses = set(
184- interface["mac_address"]
185- for _, interface in interfaces.items()
186+ # Update networking information every time we see a rack.
187+ rackcontroller.update_interfaces(interfaces)
188+ # Hint to callers whether or not this rack needs to be refreshed.
189+ return rackcontroller, needs_refresh
190+
191+
192+@typed
193+def find(system_id: Optional[str], hostname: str, interfaces: dict):
194+ """Find an existing node by `system_id`, `hostname`, and `interfaces`.
195+
196+ :type system_id: str or None
197+ :type hostname: str
198+ :type interfaces: dict
199+ :return: An instance of :class:`Node` or `None`
200+ """
201+ mac_addresses = {
202+ interface["mac_address"] for interface in interfaces.values()
203 if "mac_address" in interface
204+ }
205+ query = (
206+ Q(system_id=system_id) | Q(hostname=hostname) |
207+ Q(interface__mac_address__in=mac_addresses)
208 )
209- node = Node.objects.filter(
210- Q(system_id=system_id) |
211- Q(hostname=hostname) |
212- Q(interface__mac_address__in=mac_addresses)).first()
213- if node is None:
214- return None
215+ return Node.objects.filter(query).first()
216+
217+
218+def upgrade(node):
219+ """Upgrade `node` to a rack controller if it isn't already.
220+
221+ Return a hint as to whether a refresh is necessary.
222+
223+ :return: A ``(rack-controller, refresh-hint)`` tuple.
224+ """
225 # Refresh whenever an existing node is converted for use as a rack
226 # controller. This is needed for two reasons. First, when the region starts
227 # it creates a node for itself but only gathers networking information.
228 # Second, information about the node may have changed since its last use.
229 needs_refresh = True
230- if node.node_type in (
231- NODE_TYPE.RACK_CONTROLLER,
232- NODE_TYPE.REGION_AND_RACK_CONTROLLER):
233- maaslog.info(
234- "Registering existing rack controller %s." % node.hostname)
235+
236+ if node.is_rack_controller:
237 # We don't want to refresh existing rack controllers as each time a
238 # rack controller connects to a region it creates four connections.
239 # This means for every region we connect to we would refresh
240@@ -119,47 +157,17 @@
241 # and memory is non-zero our information at this point should be
242 # current and the user can always manually refresh.
243 needs_refresh = (node.cpu_count == 0 or node.memory == 0)
244- elif node.node_type == NODE_TYPE.REGION_CONTROLLER:
245+ elif node.is_region_controller:
246 maaslog.info(
247- "Converting %s into a region and rack controller." % node.hostname)
248+ "Converting %s into a region and rack controller.", node.hostname)
249 node.node_type = NODE_TYPE.REGION_AND_RACK_CONTROLLER
250 node.save()
251 else:
252- maaslog.info("Converting %s into a rack controller." % node.hostname)
253+ maaslog.info("Converting %s into a rack controller.", node.hostname)
254 node.node_type = NODE_TYPE.RACK_CONTROLLER
255 node.save()
256
257- rackcontroller = typecast_node(node, RackController)
258- # Tell register RPC call a refresh isn't needed
259- rackcontroller.needs_refresh = needs_refresh
260- return rackcontroller
261-
262-
263-def register_new_rackcontroller(system_id, hostname):
264- try:
265- with transaction.atomic():
266- rackcontroller = RackController.objects.create(hostname=hostname)
267- # Tell register RPC call a refresh is needed
268- rackcontroller.needs_refresh = True
269- except IntegrityError as e:
270- # regiond runs on each server with four threads. When a new rack
271- # controller connects it connects to all threads on all servers. We
272- # use the fact that hostnames must be unique to prevent us from
273- # creating multiple node objects for a single node.
274- maaslog.info(
275- "Rack controller(%s) currently being registered, retrying..." %
276- hostname)
277- rackcontroller = find_and_register_existing(system_id, hostname, {})
278- if rackcontroller is not None:
279- return rackcontroller
280- else:
281- # If we still can't find it something has gone wrong so throw the
282- # exception
283- raise e from None
284- maaslog.info(
285- "%s has been created as a new rack controller" %
286- rackcontroller.hostname)
287- return rackcontroller
288+ return typecast_node(node, RackController), needs_refresh
289
290
291 @transactional
292
293=== modified file 'src/maasserver/rpc/regionservice.py'
294--- src/maasserver/rpc/regionservice.py 2016-05-13 21:51:16 +0000
295+++ src/maasserver/rpc/regionservice.py 2016-05-31 19:19:02 +0000
296@@ -533,14 +533,11 @@
297 self, system_id, hostname, interfaces, url, nodegroup_uuid=None):
298
299 try:
300- rack_controller = yield deferToDatabase(
301- rackcontrollers.register_rackcontroller, system_id=system_id,
302+ # Register, which includes updating interfaces.
303+ rack_controller, needs_refresh = yield deferToDatabase(
304+ rackcontrollers.register, system_id=system_id,
305 hostname=hostname, interfaces=interfaces, url=url)
306
307- # Update the interfaces.
308- yield deferToDatabase(
309- transactional(rack_controller.update_interfaces), interfaces)
310-
311 # Check for upgrade.
312 if nodegroup_uuid is not None:
313 yield deferToDatabase(
314@@ -571,7 +568,7 @@
315 # the information about the rack controller if needed.
316 log.msg(
317 "Rack controller '%s' has been registered." % self.ident)
318- if self.hostIsRemote and rack_controller.needs_refresh:
319+ if self.hostIsRemote and needs_refresh:
320 # Needs to be refresh. Perform this operation in a thread but
321 # we ignore when it is done.
322 log.msg(
323
324=== modified file 'src/maasserver/rpc/testing/fixtures.py'
325--- src/maasserver/rpc/testing/fixtures.py 2016-03-28 13:54:47 +0000
326+++ src/maasserver/rpc/testing/fixtures.py 2016-05-31 19:19:02 +0000
327@@ -319,16 +319,16 @@
328 protocol = yield endpoints.connectProtocol(endpoint, protocol)
329
330 # Mock the registration into the database, as the rack controller is
331- # already created. We reset this once registration is complete so not
332- # to interfer with other tests.
333- reg_original_func = rackcontrollers.register_rackcontroller
334- update_original_func = RackController.update_interfaces
335- rackcontrollers.register_rackcontroller = (
336- lambda *args, **kwargs: rack_controller)
337- RackController.update_interfaces = (
338- lambda *args, **kwargs: None)
339+ # already created. We reset this once registration is complete so as
340+ # to not interfere with other tests.
341+ registered = rack_controller, False # Hint that no refresh needed.
342+ patcher = MonkeyPatcher()
343+ patcher.add_patch(
344+ rackcontrollers, "register",
345+ (lambda *args, **kwargs: registered))
346
347 # Register the rack controller with the region.
348+ patcher.patch()
349 try:
350 yield protocol.callRemote(
351 region.RegisterRackController,
352@@ -336,9 +336,7 @@
353 hostname=rack_controller.hostname, interfaces={},
354 url=urlparse(""))
355 finally:
356- # Restore the original functions.
357- rackcontrollers.register_rackcontroller = reg_original_func
358- RackController.update_interfaces = update_original_func
359+ patcher.restore()
360
361 defer.returnValue(protocol)
362
363
364=== modified file 'src/maasserver/rpc/tests/test_rackcontrollers.py'
365--- src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-24 07:30:16 +0000
366+++ src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-31 19:19:02 +0000
367@@ -8,9 +8,11 @@
368 import random
369 from unittest.mock import sentinel
370
371-from django.db import IntegrityError
372 from fixtures import FakeLogger
373-from maasserver import worker_user
374+from maasserver import (
375+ locks,
376+ worker_user,
377+)
378 from maasserver.enum import (
379 INTERFACE_TYPE,
380 IPADDRESS_TYPE,
381@@ -21,11 +23,12 @@
382 NodeGroupToRackController,
383 RackController,
384 )
385+from maasserver.models.interface import PhysicalInterface
386 from maasserver.models.timestampedmodel import now
387+from maasserver.rpc import rackcontrollers
388 from maasserver.rpc.rackcontrollers import (
389 handle_upgrade,
390- register_new_rackcontroller,
391- register_rackcontroller,
392+ register,
393 update_foreign_dhcp,
394 update_interfaces,
395 update_last_image_sync,
396@@ -34,6 +37,12 @@
397 from maasserver.testing.testcase import MAASServerTestCase
398 from maasserver.utils.orm import reload_object
399 from maastesting.matchers import MockCalledOnceWith
400+from testtools.matchers import (
401+ IsInstance,
402+ MatchesAll,
403+ MatchesSetwise,
404+ MatchesStructure,
405+)
406
407
408 class TestHandleUpgrade(MAASServerTestCase):
409@@ -99,23 +108,23 @@
410
411 def test_sets_owner_to_worker_when_none(self):
412 node = factory.make_Node()
413- rack_registered = register_rackcontroller(system_id=node.system_id)
414+ rack_registered, needs_refresh = register(system_id=node.system_id)
415 self.assertEqual(worker_user.get_worker_user(), rack_registered.owner)
416
417 def test_leaves_owner_when_owned(self):
418 user = factory.make_User()
419 node = factory.make_Machine(owner=user)
420- rack_registered = register_rackcontroller(system_id=node.system_id)
421+ rack_registered, needs_refresh = register(system_id=node.system_id)
422 self.assertEqual(user, rack_registered.owner)
423
424 def test_finds_existing_node_by_system_id(self):
425 node = factory.make_Node()
426- rack_registered = register_rackcontroller(system_id=node.system_id)
427+ rack_registered, needs_refresh = register(system_id=node.system_id)
428 self.assertEqual(node.system_id, rack_registered.system_id)
429
430 def test_finds_existing_node_by_hostname(self):
431 node = factory.make_Node()
432- rack_registered = register_rackcontroller(hostname=node.hostname)
433+ rack_registered, needs_refresh = register(hostname=node.hostname)
434 self.assertEqual(node.system_id, rack_registered.system_id)
435
436 def test_finds_existing_node_by_mac(self):
437@@ -131,7 +140,7 @@
438 "enabled": True,
439 }
440 }
441- rack_registered = register_rackcontroller(interfaces=interfaces)
442+ rack_registered, needs_refresh = register(interfaces=interfaces)
443 self.assertEqual(node.system_id, rack_registered.system_id)
444
445 def test_finds_existing_controller_needs_refresh_with_bad_info(self):
446@@ -140,8 +149,8 @@
447 NODE_TYPE.REGION_AND_RACK_CONTROLLER,
448 ])
449 node = factory.make_Node(node_type=node_type)
450- rack_registered = register_rackcontroller(system_id=node.system_id)
451- self.assertTrue(rack_registered.needs_refresh)
452+ rack_registered, needs_refresh = register(system_id=node.system_id)
453+ self.assertTrue(needs_refresh)
454
455 def test_finds_existing_controller_doesnt_need_refresh_good_info(self):
456 node_type = random.choice([
457@@ -151,8 +160,8 @@
458 node = factory.make_Node(
459 node_type=node_type, cpu_count=random.randint(1, 32),
460 memory=random.randint(1024, 8096))
461- rack_registered = register_rackcontroller(system_id=node.system_id)
462- self.assertFalse(rack_registered.needs_refresh)
463+ rack_registered, needs_refresh = register(system_id=node.system_id)
464+ self.assertFalse(needs_refresh)
465
466 def test_converts_existing_node_sets_needs_refresh_to_true(self):
467 node_type = random.choice([
468@@ -161,50 +170,54 @@
469 NODE_TYPE.REGION_CONTROLLER,
470 ])
471 node = factory.make_Node(node_type=node_type)
472- rack_registered = register_rackcontroller(system_id=node.system_id)
473- self.assertTrue(rack_registered.needs_refresh)
474+ rack_registered, needs_refresh = register(system_id=node.system_id)
475+ self.assertTrue(needs_refresh)
476
477 def test_find_existing_keeps_type(self):
478 node_type = random.choice(
479 (NODE_TYPE.RACK_CONTROLLER, NODE_TYPE.REGION_AND_RACK_CONTROLLER))
480 node = factory.make_Node(node_type=node_type)
481- register_rackcontroller(system_id=node.system_id)
482+ register(system_id=node.system_id)
483 self.assertEqual(node_type, node.node_type)
484
485 def test_logs_finding_existing_node(self):
486 logger = self.useFixture(FakeLogger("maas"))
487 node = factory.make_Node(node_type=NODE_TYPE.RACK_CONTROLLER)
488- register_rackcontroller(system_id=node.system_id)
489+ register(system_id=node.system_id)
490 self.assertEqual(
491 "Registering existing rack controller %s." % node.hostname,
492 logger.output.strip())
493
494 def test_converts_region_controller(self):
495 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
496- rack_registered = register_rackcontroller(system_id=node.system_id)
497+ rack_registered, needs_refresh = register(system_id=node.system_id)
498 self.assertEqual(
499 rack_registered.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER)
500
501 def test_logs_converting_region_controller(self):
502 logger = self.useFixture(FakeLogger("maas"))
503 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
504- register_rackcontroller(system_id=node.system_id)
505+ register(system_id=node.system_id)
506 self.assertEqual(
507- "Converting %s into a region and rack controller." % node.hostname,
508- logger.output.strip())
509+ "Found existing node %s as candidate for rack controller.\n"
510+ "Converting %s into a region and rack controller.\n"
511+ % (node.hostname, node.hostname),
512+ logger.output)
513
514 def test_converts_existing_node(self):
515 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
516- rack_registered = register_rackcontroller(system_id=node.system_id)
517+ rack_registered, needs_refresh = register(system_id=node.system_id)
518 self.assertEqual(rack_registered.node_type, NODE_TYPE.RACK_CONTROLLER)
519
520 def test_logs_converting_existing_node(self):
521 logger = self.useFixture(FakeLogger("maas"))
522 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
523- register_rackcontroller(system_id=node.system_id)
524+ register(system_id=node.system_id)
525 self.assertEqual(
526- "Converting %s into a rack controller." % node.hostname,
527- logger.output.strip())
528+ "Found existing node %s as candidate for rack controller.\n"
529+ "Converting %s into a rack controller.\n"
530+ % (node.hostname, node.hostname),
531+ logger.output)
532
533 def test_creates_new_rackcontroller(self):
534 factory.make_Node()
535@@ -218,7 +231,7 @@
536 "enabled": True,
537 }
538 }
539- register_rackcontroller(interfaces=interfaces)
540+ register(interfaces=interfaces)
541 self.assertEqual(node_count + 1, len(Node.objects.all()))
542
543 def test_creates_new_rackcontroller_sets_needs_refresh_to_true(self):
544@@ -231,45 +244,84 @@
545 "enabled": True,
546 }
547 }
548- rack_registered = register_rackcontroller(
549- interfaces=interfaces)
550- self.assertTrue(rack_registered.needs_refresh)
551+ rack_registered, needs_refresh = register(interfaces=interfaces)
552+ self.assertTrue(needs_refresh)
553
554 def test_logs_creating_new_rackcontroller(self):
555 logger = self.useFixture(FakeLogger("maas"))
556 hostname = factory.make_name("hostname")
557- register_rackcontroller(hostname=hostname)
558+ register(hostname=hostname)
559 self.assertEqual(
560- "%s has been created as a new rack controller" % hostname,
561+ "Created new rack controller %s." % hostname,
562 logger.output.strip())
563
564- def test_retries_existing_on_new_integrity_error(self):
565- hostname = factory.make_name("hostname")
566- node = factory.make_Node(hostname=hostname)
567- patched_create = self.patch(RackController.objects, 'create')
568- patched_create.side_effect = IntegrityError()
569- rack_registered = register_new_rackcontroller(None, hostname)
570- self.assertEqual(rack_registered.system_id, node.system_id)
571-
572- def test_raises_exception_on_new_and_existing_failure(self):
573- patched_create = self.patch(RackController.objects, 'create')
574- patched_create.side_effect = IntegrityError()
575- self.assertRaises(
576- IntegrityError, register_new_rackcontroller,
577- None, factory.make_name("hostname"))
578-
579- def test_logs_retrying_existing_on_new_integrity_error(self):
580- logger = self.useFixture(FakeLogger("maas"))
581- hostname = factory.make_name("hostname")
582- patched_create = self.patch(RackController.objects, 'create')
583- patched_create.side_effect = IntegrityError()
584- try:
585- register_new_rackcontroller(None, hostname)
586- except IntegrityError:
587- pass
588- self.assertEqual(
589- "Rack controller(%s) currently being registered, retrying..." %
590- hostname, logger.output.strip())
591+ def test_sets_interfaces(self):
592+ # Interfaces are set on new rack controllers.
593+ interfaces = {
594+ factory.make_name("eth0"): {
595+ "type": "physical",
596+ "mac_address": factory.make_mac_address(),
597+ "parents": [],
598+ "links": [],
599+ "enabled": True,
600+ }
601+ }
602+ rack_registered, needs_refresh = register(interfaces=interfaces)
603+ self.assertThat(
604+ rack_registered.interface_set.all(),
605+ MatchesSetwise(*(
606+ MatchesAll(
607+ IsInstance(PhysicalInterface),
608+ MatchesStructure.byEquality(
609+ name=name, mac_address=interface["mac_address"],
610+ enabled=interface["enabled"],
611+ ),
612+ first_only=True,
613+ )
614+ for name, interface in interfaces.items()
615+ )))
616+
617+ def test_updates_interfaces(self):
618+ # Interfaces are set on existing rack controllers.
619+ rack_controller = factory.make_RackController()
620+ interfaces = {
621+ factory.make_name("eth0"): {
622+ "type": "physical",
623+ "mac_address": factory.make_mac_address(),
624+ "parents": [],
625+ "links": [],
626+ "enabled": True,
627+ }
628+ }
629+ rack_registered, needs_refresh = register(
630+ rack_controller.system_id, interfaces=interfaces)
631+ self.assertThat(
632+ rack_registered.interface_set.all(),
633+ MatchesSetwise(*(
634+ MatchesAll(
635+ IsInstance(PhysicalInterface),
636+ MatchesStructure.byEquality(
637+ name=name, mac_address=interface["mac_address"],
638+ enabled=interface["enabled"],
639+ ),
640+ first_only=True,
641+ )
642+ for name, interface in interfaces.items()
643+ )))
644+
645+ def test_registers_with_rack_registration_lock_held(self):
646+ lock_status = []
647+
648+ def record_lock_status(*args):
649+ lock_status.append(locks.rack_registration.is_locked())
650+ return None # Simulate that no rack found.
651+
652+ find = self.patch(rackcontrollers, "find")
653+ find.side_effect = record_lock_status
654+
655+ register()
656+
657+ self.assertEqual([True], lock_status)
658
659
660 class TestUpdateForeignDHCP(MAASServerTestCase):
661
662=== modified file 'src/maasserver/rpc/tests/test_regionservice.py'
663--- src/maasserver/rpc/tests/test_regionservice.py 2016-05-18 09:00:43 +0000
664+++ src/maasserver/rpc/tests/test_regionservice.py 2016-05-31 19:19:02 +0000
665@@ -522,6 +522,10 @@
666 port=random.randint(1, 400))
667 protocol.transport.getHost.return_value = host
668 mock_deferToDatabase = self.patch(regionservice, "deferToDatabase")
669+ mock_deferToDatabase.side_effect = [
670+ succeed((rack_controller, False)),
671+ succeed(None),
672+ ]
673 yield call_responder(
674 protocol, RegisterRackController, {
675 "system_id": rack_controller.system_id,