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