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

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5058
Proposed branch: lp:~allenap/maas/duplicate-vlan-on-start--bug-1583333
Merge into: lp:~maas-committers/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 (community) Approve
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.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve
Revision history for this message
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...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/locks.py'
--- src/maasserver/locks.py 2015-12-01 18:12:59 +0000
+++ src/maasserver/locks.py 2016-05-31 19:19:02 +0000
@@ -1,12 +1,17 @@
1# Copyright 2014-2015 Canonical Ltd. This software is licensed under the1# Copyright 2014-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Region-wide locks."""4"""Region-wide locks."""
55
6__all__ = [6__all__ = [
7 "dns",
7 "eventloop",8 "eventloop",
9 "import_images",
10 "node_acquire",
11 "rack_registration",
8 "security",12 "security",
9 "startup",13 "startup",
14 "staticip_acquire",
10]15]
1116
12from maasserver.utils.dblocks import (17from maasserver.utils.dblocks import (
@@ -35,3 +40,8 @@
3540
36# Lock to prevent concurrent allocation of StaticIPAddress41# Lock to prevent concurrent allocation of StaticIPAddress
37staticip_acquire = DatabaseXactLock(8)42staticip_acquire = DatabaseXactLock(8)
43
44# Lock to prevent concurrent registration of rack controllers. This can be a
45# problem because registration involves populating fabrics, VLANs, and other
46# information that may overlap between rack controller.
47rack_registration = DatabaseLock(9)
3848
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2016-05-26 17:07:30 +0000
+++ src/maasserver/models/node.py 2016-05-31 19:19:02 +0000
@@ -877,6 +877,13 @@
877 ]877 ]
878878
879 @property879 @property
880 def is_region_controller(self):
881 return self.node_type in [
882 NODE_TYPE.REGION_AND_RACK_CONTROLLER,
883 NODE_TYPE.REGION_CONTROLLER,
884 ]
885
886 @property
880 def is_controller(self):887 def is_controller(self):
881 return self.node_type in [888 return self.node_type in [
882 NODE_TYPE.REGION_CONTROLLER,889 NODE_TYPE.REGION_CONTROLLER,
883890
=== modified file 'src/maasserver/models/tests/test_node.py'
--- src/maasserver/models/tests/test_node.py 2016-05-26 17:07:30 +0000
+++ src/maasserver/models/tests/test_node.py 2016-05-31 19:19:02 +0000
@@ -517,6 +517,26 @@
517 rack = factory.make_RackController()517 rack = factory.make_RackController()
518 self.assertTrue(rack.is_rack_controller)518 self.assertTrue(rack.is_rack_controller)
519519
520 def test_is_region_controller_machine(self):
521 machine = factory.make_Node()
522 self.assertFalse(machine.is_region_controller)
523
524 def test_is_region_controller_device(self):
525 device = factory.make_Device()
526 self.assertFalse(device.is_region_controller)
527
528 def test_is_region_controller_region_controller(self):
529 region = factory.make_RegionController()
530 self.assertTrue(region.is_region_controller)
531
532 def test_is_region_controller_region_rack_controller(self):
533 region_rack = factory.make_RegionRackController()
534 self.assertTrue(region_rack.is_region_controller)
535
536 def test_is_region_controller_rack_controller(self):
537 rack = factory.make_RackController()
538 self.assertFalse(rack.is_region_controller)
539
520 def test_is_controller_machine(self):540 def test_is_controller_machine(self):
521 machine = factory.make_Node()541 machine = factory.make_Node()
522 self.assertFalse(machine.is_controller)542 self.assertFalse(machine.is_controller)
523543
=== modified file 'src/maasserver/rpc/rackcontrollers.py'
--- src/maasserver/rpc/rackcontrollers.py 2016-05-24 07:30:16 +0000
+++ src/maasserver/rpc/rackcontrollers.py 2016-05-31 19:19:02 +0000
@@ -4,16 +4,19 @@
4"""RPC helpers relating to rack controllers."""4"""RPC helpers relating to rack controllers."""
55
6__all__ = [6__all__ = [
7 "register_rackcontroller",7 "handle_upgrade",
8 "register",
9 "update_interfaces",
8 "update_last_image_sync",10 "update_last_image_sync",
9]11]
1012
11from django.db import (13from typing import Optional
12 IntegrityError,14
13 transaction,
14)
15from django.db.models import Q15from django.db.models import Q
16from maasserver import worker_user16from maasserver import (
17 locks,
18 worker_user,
19)
17from maasserver.enum import NODE_TYPE20from maasserver.enum import NODE_TYPE
18from maasserver.models import (21from maasserver.models import (
19 Node,22 Node,
@@ -23,8 +26,13 @@
23)26)
24from maasserver.models.node import typecast_node27from maasserver.models.node import typecast_node
25from maasserver.models.timestampedmodel import now28from maasserver.models.timestampedmodel import now
26from maasserver.utils.orm import transactional29from maasserver.utils import synchronised
30from maasserver.utils.orm import (
31 transactional,
32 with_connection,
33)
27from provisioningserver.logger import get_maas_logger34from provisioningserver.logger import get_maas_logger
35from provisioningserver.utils import typed
28from provisioningserver.utils.twisted import synchronous36from provisioningserver.utils.twisted import synchronous
2937
3038
@@ -59,22 +67,37 @@
5967
6068
61@synchronous69@synchronous
70@with_connection
71@synchronised(locks.rack_registration)
62@transactional72@transactional
63def register_rackcontroller(73def register(system_id=None, hostname='', interfaces=None, url=None):
64 system_id=None, hostname='', interfaces={}, url=None,
65 nodegroup_uuid=None):
66 """Register a new rack controller if not already registered.74 """Register a new rack controller if not already registered.
6775
68 Attempt to see if the rack controller was already registered as a node.76 Attempt to see if the rack controller was already registered as a node.
69 This can be looked up either by system_id, hostname, or mac address. If77 This can be looked up either by system_id, hostname, or mac address. If
70 found convert the existing node into a rack controller. If not found create78 found convert the existing node into a rack controller. If not found
71 a new rack controller. After the rack controller has been registered and79 create a new rack controller. After the rack controller has been
72 successfully connected we will refresh all commissioning data."""80 registered and successfully connected we will refresh all commissioning
73 rackcontroller = find_and_register_existing(81 data.
74 system_id, hostname, interfaces)82
75 if rackcontroller is None:83 :return: A ``(rack-controller, refresh-hint)`` tuple.
76 rackcontroller = register_new_rackcontroller(system_id, hostname)84 """
7785 if interfaces is None:
86 interfaces = {}
87
88 node = find(system_id, hostname, interfaces)
89 if node is None:
90 node = RackController.objects.create(hostname=hostname)
91 maaslog.info("Created new rack controller %s.", node.hostname)
92 elif node.is_rack_controller:
93 maaslog.info("Registering existing rack controller %s.", node.hostname)
94 else:
95 maaslog.info(
96 "Found existing node %s as candidate for rack controller.",
97 node.hostname)
98
99 # This may be a no-op, but it does provide us with a refresh hint.
100 rackcontroller, needs_refresh = upgrade(node)
78 # Update `rackcontroller.url` from the given URL, but only when the101 # Update `rackcontroller.url` from the given URL, but only when the
79 # hostname is not 'localhost' (i.e. the default value used when the master102 # hostname is not 'localhost' (i.e. the default value used when the master
80 # cluster connects).103 # cluster connects).
@@ -87,31 +110,46 @@
87 rackcontroller.owner = worker_user.get_worker_user()110 rackcontroller.owner = worker_user.get_worker_user()
88 update_fields.append("owner")111 update_fields.append("owner")
89 rackcontroller.save(update_fields=update_fields)112 rackcontroller.save(update_fields=update_fields)
90 return rackcontroller113 # Update networking information every time we see a rack.
91114 rackcontroller.update_interfaces(interfaces)
92115 # Hint to callers whether or not this rack needs to be refreshed.
93def find_and_register_existing(system_id, hostname, interfaces):116 return rackcontroller, needs_refresh
94 mac_addresses = set(117
95 interface["mac_address"]118
96 for _, interface in interfaces.items()119@typed
120def find(system_id: Optional[str], hostname: str, interfaces: dict):
121 """Find an existing node by `system_id`, `hostname`, and `interfaces`.
122
123 :type system_id: str or None
124 :type hostname: str
125 :type interfaces: dict
126 :return: An instance of :class:`Node` or `None`
127 """
128 mac_addresses = {
129 interface["mac_address"] for interface in interfaces.values()
97 if "mac_address" in interface130 if "mac_address" in interface
131 }
132 query = (
133 Q(system_id=system_id) | Q(hostname=hostname) |
134 Q(interface__mac_address__in=mac_addresses)
98 )135 )
99 node = Node.objects.filter(136 return Node.objects.filter(query).first()
100 Q(system_id=system_id) |137
101 Q(hostname=hostname) |138
102 Q(interface__mac_address__in=mac_addresses)).first()139def upgrade(node):
103 if node is None:140 """Upgrade `node` to a rack controller if it isn't already.
104 return None141
142 Return a hint as to whether a refresh is necessary.
143
144 :return: A ``(rack-controller, refresh-hint)`` tuple.
145 """
105 # Refresh whenever an existing node is converted for use as a rack146 # Refresh whenever an existing node is converted for use as a rack
106 # controller. This is needed for two reasons. First, when the region starts147 # controller. This is needed for two reasons. First, when the region starts
107 # it creates a node for itself but only gathers networking information.148 # it creates a node for itself but only gathers networking information.
108 # Second, information about the node may have changed since its last use.149 # Second, information about the node may have changed since its last use.
109 needs_refresh = True150 needs_refresh = True
110 if node.node_type in (151
111 NODE_TYPE.RACK_CONTROLLER,152 if node.is_rack_controller:
112 NODE_TYPE.REGION_AND_RACK_CONTROLLER):
113 maaslog.info(
114 "Registering existing rack controller %s." % node.hostname)
115 # We don't want to refresh existing rack controllers as each time a153 # We don't want to refresh existing rack controllers as each time a
116 # rack controller connects to a region it creates four connections.154 # rack controller connects to a region it creates four connections.
117 # This means for every region we connect to we would refresh155 # This means for every region we connect to we would refresh
@@ -119,47 +157,17 @@
119 # and memory is non-zero our information at this point should be157 # and memory is non-zero our information at this point should be
120 # current and the user can always manually refresh.158 # current and the user can always manually refresh.
121 needs_refresh = (node.cpu_count == 0 or node.memory == 0)159 needs_refresh = (node.cpu_count == 0 or node.memory == 0)
122 elif node.node_type == NODE_TYPE.REGION_CONTROLLER:160 elif node.is_region_controller:
123 maaslog.info(161 maaslog.info(
124 "Converting %s into a region and rack controller." % node.hostname)162 "Converting %s into a region and rack controller.", node.hostname)
125 node.node_type = NODE_TYPE.REGION_AND_RACK_CONTROLLER163 node.node_type = NODE_TYPE.REGION_AND_RACK_CONTROLLER
126 node.save()164 node.save()
127 else:165 else:
128 maaslog.info("Converting %s into a rack controller." % node.hostname)166 maaslog.info("Converting %s into a rack controller.", node.hostname)
129 node.node_type = NODE_TYPE.RACK_CONTROLLER167 node.node_type = NODE_TYPE.RACK_CONTROLLER
130 node.save()168 node.save()
131169
132 rackcontroller = typecast_node(node, RackController)170 return typecast_node(node, RackController), needs_refresh
133 # Tell register RPC call a refresh isn't needed
134 rackcontroller.needs_refresh = needs_refresh
135 return rackcontroller
136
137
138def register_new_rackcontroller(system_id, hostname):
139 try:
140 with transaction.atomic():
141 rackcontroller = RackController.objects.create(hostname=hostname)
142 # Tell register RPC call a refresh is needed
143 rackcontroller.needs_refresh = True
144 except IntegrityError as e:
145 # regiond runs on each server with four threads. When a new rack
146 # controller connects it connects to all threads on all servers. We
147 # use the fact that hostnames must be unique to prevent us from
148 # creating multiple node objects for a single node.
149 maaslog.info(
150 "Rack controller(%s) currently being registered, retrying..." %
151 hostname)
152 rackcontroller = find_and_register_existing(system_id, hostname, {})
153 if rackcontroller is not None:
154 return rackcontroller
155 else:
156 # If we still can't find it something has gone wrong so throw the
157 # exception
158 raise e from None
159 maaslog.info(
160 "%s has been created as a new rack controller" %
161 rackcontroller.hostname)
162 return rackcontroller
163171
164172
165@transactional173@transactional
166174
=== modified file 'src/maasserver/rpc/regionservice.py'
--- src/maasserver/rpc/regionservice.py 2016-05-13 21:51:16 +0000
+++ src/maasserver/rpc/regionservice.py 2016-05-31 19:19:02 +0000
@@ -533,14 +533,11 @@
533 self, system_id, hostname, interfaces, url, nodegroup_uuid=None):533 self, system_id, hostname, interfaces, url, nodegroup_uuid=None):
534534
535 try:535 try:
536 rack_controller = yield deferToDatabase(536 # Register, which includes updating interfaces.
537 rackcontrollers.register_rackcontroller, system_id=system_id,537 rack_controller, needs_refresh = yield deferToDatabase(
538 rackcontrollers.register, system_id=system_id,
538 hostname=hostname, interfaces=interfaces, url=url)539 hostname=hostname, interfaces=interfaces, url=url)
539540
540 # Update the interfaces.
541 yield deferToDatabase(
542 transactional(rack_controller.update_interfaces), interfaces)
543
544 # Check for upgrade.541 # Check for upgrade.
545 if nodegroup_uuid is not None:542 if nodegroup_uuid is not None:
546 yield deferToDatabase(543 yield deferToDatabase(
@@ -571,7 +568,7 @@
571 # the information about the rack controller if needed.568 # the information about the rack controller if needed.
572 log.msg(569 log.msg(
573 "Rack controller '%s' has been registered." % self.ident)570 "Rack controller '%s' has been registered." % self.ident)
574 if self.hostIsRemote and rack_controller.needs_refresh:571 if self.hostIsRemote and needs_refresh:
575 # Needs to be refresh. Perform this operation in a thread but572 # Needs to be refresh. Perform this operation in a thread but
576 # we ignore when it is done.573 # we ignore when it is done.
577 log.msg(574 log.msg(
578575
=== modified file 'src/maasserver/rpc/testing/fixtures.py'
--- src/maasserver/rpc/testing/fixtures.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/rpc/testing/fixtures.py 2016-05-31 19:19:02 +0000
@@ -319,16 +319,16 @@
319 protocol = yield endpoints.connectProtocol(endpoint, protocol)319 protocol = yield endpoints.connectProtocol(endpoint, protocol)
320320
321 # Mock the registration into the database, as the rack controller is321 # Mock the registration into the database, as the rack controller is
322 # already created. We reset this once registration is complete so not322 # already created. We reset this once registration is complete so as
323 # to interfer with other tests.323 # to not interfere with other tests.
324 reg_original_func = rackcontrollers.register_rackcontroller324 registered = rack_controller, False # Hint that no refresh needed.
325 update_original_func = RackController.update_interfaces325 patcher = MonkeyPatcher()
326 rackcontrollers.register_rackcontroller = (326 patcher.add_patch(
327 lambda *args, **kwargs: rack_controller)327 rackcontrollers, "register",
328 RackController.update_interfaces = (328 (lambda *args, **kwargs: registered))
329 lambda *args, **kwargs: None)
330329
331 # Register the rack controller with the region.330 # Register the rack controller with the region.
331 patcher.patch()
332 try:332 try:
333 yield protocol.callRemote(333 yield protocol.callRemote(
334 region.RegisterRackController,334 region.RegisterRackController,
@@ -336,9 +336,7 @@
336 hostname=rack_controller.hostname, interfaces={},336 hostname=rack_controller.hostname, interfaces={},
337 url=urlparse(""))337 url=urlparse(""))
338 finally:338 finally:
339 # Restore the original functions.339 patcher.restore()
340 rackcontrollers.register_rackcontroller = reg_original_func
341 RackController.update_interfaces = update_original_func
342340
343 defer.returnValue(protocol)341 defer.returnValue(protocol)
344342
345343
=== modified file 'src/maasserver/rpc/tests/test_rackcontrollers.py'
--- src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-24 07:30:16 +0000
+++ src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-31 19:19:02 +0000
@@ -8,9 +8,11 @@
8import random8import random
9from unittest.mock import sentinel9from unittest.mock import sentinel
1010
11from django.db import IntegrityError
12from fixtures import FakeLogger11from fixtures import FakeLogger
13from maasserver import worker_user12from maasserver import (
13 locks,
14 worker_user,
15)
14from maasserver.enum import (16from maasserver.enum import (
15 INTERFACE_TYPE,17 INTERFACE_TYPE,
16 IPADDRESS_TYPE,18 IPADDRESS_TYPE,
@@ -21,11 +23,12 @@
21 NodeGroupToRackController,23 NodeGroupToRackController,
22 RackController,24 RackController,
23)25)
26from maasserver.models.interface import PhysicalInterface
24from maasserver.models.timestampedmodel import now27from maasserver.models.timestampedmodel import now
28from maasserver.rpc import rackcontrollers
25from maasserver.rpc.rackcontrollers import (29from maasserver.rpc.rackcontrollers import (
26 handle_upgrade,30 handle_upgrade,
27 register_new_rackcontroller,31 register,
28 register_rackcontroller,
29 update_foreign_dhcp,32 update_foreign_dhcp,
30 update_interfaces,33 update_interfaces,
31 update_last_image_sync,34 update_last_image_sync,
@@ -34,6 +37,12 @@
34from maasserver.testing.testcase import MAASServerTestCase37from maasserver.testing.testcase import MAASServerTestCase
35from maasserver.utils.orm import reload_object38from maasserver.utils.orm import reload_object
36from maastesting.matchers import MockCalledOnceWith39from maastesting.matchers import MockCalledOnceWith
40from testtools.matchers import (
41 IsInstance,
42 MatchesAll,
43 MatchesSetwise,
44 MatchesStructure,
45)
3746
3847
39class TestHandleUpgrade(MAASServerTestCase):48class TestHandleUpgrade(MAASServerTestCase):
@@ -99,23 +108,23 @@
99108
100 def test_sets_owner_to_worker_when_none(self):109 def test_sets_owner_to_worker_when_none(self):
101 node = factory.make_Node()110 node = factory.make_Node()
102 rack_registered = register_rackcontroller(system_id=node.system_id)111 rack_registered, needs_refresh = register(system_id=node.system_id)
103 self.assertEqual(worker_user.get_worker_user(), rack_registered.owner)112 self.assertEqual(worker_user.get_worker_user(), rack_registered.owner)
104113
105 def test_leaves_owner_when_owned(self):114 def test_leaves_owner_when_owned(self):
106 user = factory.make_User()115 user = factory.make_User()
107 node = factory.make_Machine(owner=user)116 node = factory.make_Machine(owner=user)
108 rack_registered = register_rackcontroller(system_id=node.system_id)117 rack_registered, needs_refresh = register(system_id=node.system_id)
109 self.assertEqual(user, rack_registered.owner)118 self.assertEqual(user, rack_registered.owner)
110119
111 def test_finds_existing_node_by_system_id(self):120 def test_finds_existing_node_by_system_id(self):
112 node = factory.make_Node()121 node = factory.make_Node()
113 rack_registered = register_rackcontroller(system_id=node.system_id)122 rack_registered, needs_refresh = register(system_id=node.system_id)
114 self.assertEqual(node.system_id, rack_registered.system_id)123 self.assertEqual(node.system_id, rack_registered.system_id)
115124
116 def test_finds_existing_node_by_hostname(self):125 def test_finds_existing_node_by_hostname(self):
117 node = factory.make_Node()126 node = factory.make_Node()
118 rack_registered = register_rackcontroller(hostname=node.hostname)127 rack_registered, needs_refresh = register(hostname=node.hostname)
119 self.assertEqual(node.system_id, rack_registered.system_id)128 self.assertEqual(node.system_id, rack_registered.system_id)
120129
121 def test_finds_existing_node_by_mac(self):130 def test_finds_existing_node_by_mac(self):
@@ -131,7 +140,7 @@
131 "enabled": True,140 "enabled": True,
132 }141 }
133 }142 }
134 rack_registered = register_rackcontroller(interfaces=interfaces)143 rack_registered, needs_refresh = register(interfaces=interfaces)
135 self.assertEqual(node.system_id, rack_registered.system_id)144 self.assertEqual(node.system_id, rack_registered.system_id)
136145
137 def test_finds_existing_controller_needs_refresh_with_bad_info(self):146 def test_finds_existing_controller_needs_refresh_with_bad_info(self):
@@ -140,8 +149,8 @@
140 NODE_TYPE.REGION_AND_RACK_CONTROLLER,149 NODE_TYPE.REGION_AND_RACK_CONTROLLER,
141 ])150 ])
142 node = factory.make_Node(node_type=node_type)151 node = factory.make_Node(node_type=node_type)
143 rack_registered = register_rackcontroller(system_id=node.system_id)152 rack_registered, needs_refresh = register(system_id=node.system_id)
144 self.assertTrue(rack_registered.needs_refresh)153 self.assertTrue(needs_refresh)
145154
146 def test_finds_existing_controller_doesnt_need_refresh_good_info(self):155 def test_finds_existing_controller_doesnt_need_refresh_good_info(self):
147 node_type = random.choice([156 node_type = random.choice([
@@ -151,8 +160,8 @@
151 node = factory.make_Node(160 node = factory.make_Node(
152 node_type=node_type, cpu_count=random.randint(1, 32),161 node_type=node_type, cpu_count=random.randint(1, 32),
153 memory=random.randint(1024, 8096))162 memory=random.randint(1024, 8096))
154 rack_registered = register_rackcontroller(system_id=node.system_id)163 rack_registered, needs_refresh = register(system_id=node.system_id)
155 self.assertFalse(rack_registered.needs_refresh)164 self.assertFalse(needs_refresh)
156165
157 def test_converts_existing_node_sets_needs_refresh_to_true(self):166 def test_converts_existing_node_sets_needs_refresh_to_true(self):
158 node_type = random.choice([167 node_type = random.choice([
@@ -161,50 +170,54 @@
161 NODE_TYPE.REGION_CONTROLLER,170 NODE_TYPE.REGION_CONTROLLER,
162 ])171 ])
163 node = factory.make_Node(node_type=node_type)172 node = factory.make_Node(node_type=node_type)
164 rack_registered = register_rackcontroller(system_id=node.system_id)173 rack_registered, needs_refresh = register(system_id=node.system_id)
165 self.assertTrue(rack_registered.needs_refresh)174 self.assertTrue(needs_refresh)
166175
167 def test_find_existing_keeps_type(self):176 def test_find_existing_keeps_type(self):
168 node_type = random.choice(177 node_type = random.choice(
169 (NODE_TYPE.RACK_CONTROLLER, NODE_TYPE.REGION_AND_RACK_CONTROLLER))178 (NODE_TYPE.RACK_CONTROLLER, NODE_TYPE.REGION_AND_RACK_CONTROLLER))
170 node = factory.make_Node(node_type=node_type)179 node = factory.make_Node(node_type=node_type)
171 register_rackcontroller(system_id=node.system_id)180 register(system_id=node.system_id)
172 self.assertEqual(node_type, node.node_type)181 self.assertEqual(node_type, node.node_type)
173182
174 def test_logs_finding_existing_node(self):183 def test_logs_finding_existing_node(self):
175 logger = self.useFixture(FakeLogger("maas"))184 logger = self.useFixture(FakeLogger("maas"))
176 node = factory.make_Node(node_type=NODE_TYPE.RACK_CONTROLLER)185 node = factory.make_Node(node_type=NODE_TYPE.RACK_CONTROLLER)
177 register_rackcontroller(system_id=node.system_id)186 register(system_id=node.system_id)
178 self.assertEqual(187 self.assertEqual(
179 "Registering existing rack controller %s." % node.hostname,188 "Registering existing rack controller %s." % node.hostname,
180 logger.output.strip())189 logger.output.strip())
181190
182 def test_converts_region_controller(self):191 def test_converts_region_controller(self):
183 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)192 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
184 rack_registered = register_rackcontroller(system_id=node.system_id)193 rack_registered, needs_refresh = register(system_id=node.system_id)
185 self.assertEqual(194 self.assertEqual(
186 rack_registered.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER)195 rack_registered.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER)
187196
188 def test_logs_converting_region_controller(self):197 def test_logs_converting_region_controller(self):
189 logger = self.useFixture(FakeLogger("maas"))198 logger = self.useFixture(FakeLogger("maas"))
190 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)199 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
191 register_rackcontroller(system_id=node.system_id)200 register(system_id=node.system_id)
192 self.assertEqual(201 self.assertEqual(
193 "Converting %s into a region and rack controller." % node.hostname,202 "Found existing node %s as candidate for rack controller.\n"
194 logger.output.strip())203 "Converting %s into a region and rack controller.\n"
204 % (node.hostname, node.hostname),
205 logger.output)
195206
196 def test_converts_existing_node(self):207 def test_converts_existing_node(self):
197 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)208 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
198 rack_registered = register_rackcontroller(system_id=node.system_id)209 rack_registered, needs_refresh = register(system_id=node.system_id)
199 self.assertEqual(rack_registered.node_type, NODE_TYPE.RACK_CONTROLLER)210 self.assertEqual(rack_registered.node_type, NODE_TYPE.RACK_CONTROLLER)
200211
201 def test_logs_converting_existing_node(self):212 def test_logs_converting_existing_node(self):
202 logger = self.useFixture(FakeLogger("maas"))213 logger = self.useFixture(FakeLogger("maas"))
203 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)214 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
204 register_rackcontroller(system_id=node.system_id)215 register(system_id=node.system_id)
205 self.assertEqual(216 self.assertEqual(
206 "Converting %s into a rack controller." % node.hostname,217 "Found existing node %s as candidate for rack controller.\n"
207 logger.output.strip())218 "Converting %s into a rack controller.\n"
219 % (node.hostname, node.hostname),
220 logger.output)
208221
209 def test_creates_new_rackcontroller(self):222 def test_creates_new_rackcontroller(self):
210 factory.make_Node()223 factory.make_Node()
@@ -218,7 +231,7 @@
218 "enabled": True,231 "enabled": True,
219 }232 }
220 }233 }
221 register_rackcontroller(interfaces=interfaces)234 register(interfaces=interfaces)
222 self.assertEqual(node_count + 1, len(Node.objects.all()))235 self.assertEqual(node_count + 1, len(Node.objects.all()))
223236
224 def test_creates_new_rackcontroller_sets_needs_refresh_to_true(self):237 def test_creates_new_rackcontroller_sets_needs_refresh_to_true(self):
@@ -231,45 +244,84 @@
231 "enabled": True,244 "enabled": True,
232 }245 }
233 }246 }
234 rack_registered = register_rackcontroller(247 rack_registered, needs_refresh = register(interfaces=interfaces)
235 interfaces=interfaces)248 self.assertTrue(needs_refresh)
236 self.assertTrue(rack_registered.needs_refresh)
237249
238 def test_logs_creating_new_rackcontroller(self):250 def test_logs_creating_new_rackcontroller(self):
239 logger = self.useFixture(FakeLogger("maas"))251 logger = self.useFixture(FakeLogger("maas"))
240 hostname = factory.make_name("hostname")252 hostname = factory.make_name("hostname")
241 register_rackcontroller(hostname=hostname)253 register(hostname=hostname)
242 self.assertEqual(254 self.assertEqual(
243 "%s has been created as a new rack controller" % hostname,255 "Created new rack controller %s." % hostname,
244 logger.output.strip())256 logger.output.strip())
245257
246 def test_retries_existing_on_new_integrity_error(self):258 def test_sets_interfaces(self):
247 hostname = factory.make_name("hostname")259 # Interfaces are set on new rack controllers.
248 node = factory.make_Node(hostname=hostname)260 interfaces = {
249 patched_create = self.patch(RackController.objects, 'create')261 factory.make_name("eth0"): {
250 patched_create.side_effect = IntegrityError()262 "type": "physical",
251 rack_registered = register_new_rackcontroller(None, hostname)263 "mac_address": factory.make_mac_address(),
252 self.assertEqual(rack_registered.system_id, node.system_id)264 "parents": [],
253265 "links": [],
254 def test_raises_exception_on_new_and_existing_failure(self):266 "enabled": True,
255 patched_create = self.patch(RackController.objects, 'create')267 }
256 patched_create.side_effect = IntegrityError()268 }
257 self.assertRaises(269 rack_registered, needs_refresh = register(interfaces=interfaces)
258 IntegrityError, register_new_rackcontroller,270 self.assertThat(
259 None, factory.make_name("hostname"))271 rack_registered.interface_set.all(),
260272 MatchesSetwise(*(
261 def test_logs_retrying_existing_on_new_integrity_error(self):273 MatchesAll(
262 logger = self.useFixture(FakeLogger("maas"))274 IsInstance(PhysicalInterface),
263 hostname = factory.make_name("hostname")275 MatchesStructure.byEquality(
264 patched_create = self.patch(RackController.objects, 'create')276 name=name, mac_address=interface["mac_address"],
265 patched_create.side_effect = IntegrityError()277 enabled=interface["enabled"],
266 try:278 ),
267 register_new_rackcontroller(None, hostname)279 first_only=True,
268 except IntegrityError:280 )
269 pass281 for name, interface in interfaces.items()
270 self.assertEqual(282 )))
271 "Rack controller(%s) currently being registered, retrying..." %283
272 hostname, logger.output.strip())284 def test_updates_interfaces(self):
285 # Interfaces are set on existing rack controllers.
286 rack_controller = factory.make_RackController()
287 interfaces = {
288 factory.make_name("eth0"): {
289 "type": "physical",
290 "mac_address": factory.make_mac_address(),
291 "parents": [],
292 "links": [],
293 "enabled": True,
294 }
295 }
296 rack_registered, needs_refresh = register(
297 rack_controller.system_id, interfaces=interfaces)
298 self.assertThat(
299 rack_registered.interface_set.all(),
300 MatchesSetwise(*(
301 MatchesAll(
302 IsInstance(PhysicalInterface),
303 MatchesStructure.byEquality(
304 name=name, mac_address=interface["mac_address"],
305 enabled=interface["enabled"],
306 ),
307 first_only=True,
308 )
309 for name, interface in interfaces.items()
310 )))
311
312 def test_registers_with_rack_registration_lock_held(self):
313 lock_status = []
314
315 def record_lock_status(*args):
316 lock_status.append(locks.rack_registration.is_locked())
317 return None # Simulate that no rack found.
318
319 find = self.patch(rackcontrollers, "find")
320 find.side_effect = record_lock_status
321
322 register()
323
324 self.assertEqual([True], lock_status)
273325
274326
275class TestUpdateForeignDHCP(MAASServerTestCase):327class TestUpdateForeignDHCP(MAASServerTestCase):
276328
=== modified file 'src/maasserver/rpc/tests/test_regionservice.py'
--- src/maasserver/rpc/tests/test_regionservice.py 2016-05-18 09:00:43 +0000
+++ src/maasserver/rpc/tests/test_regionservice.py 2016-05-31 19:19:02 +0000
@@ -522,6 +522,10 @@
522 port=random.randint(1, 400))522 port=random.randint(1, 400))
523 protocol.transport.getHost.return_value = host523 protocol.transport.getHost.return_value = host
524 mock_deferToDatabase = self.patch(regionservice, "deferToDatabase")524 mock_deferToDatabase = self.patch(regionservice, "deferToDatabase")
525 mock_deferToDatabase.side_effect = [
526 succeed((rack_controller, False)),
527 succeed(None),
528 ]
525 yield call_responder(529 yield call_responder(
526 protocol, RegisterRackController, {530 protocol, RegisterRackController, {
527 "system_id": rack_controller.system_id,531 "system_id": rack_controller.system_id,