Merge lp:~gmb/maas/create-node-to-use-RPC into lp:~maas-committers/maas/trunk
- create-node-to-use-RPC
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Graham Binns |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2945 |
Proposed branch: | lp:~gmb/maas/create-node-to-use-RPC |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
511 lines (+252/-111) 6 files modified
src/maasserver/rpc/nodes.py (+9/-0) src/maasserver/rpc/tests/test_nodes.py (+23/-4) src/provisioningserver/rpc/exceptions.py (+4/-0) src/provisioningserver/rpc/region.py (+5/-1) src/provisioningserver/utils/__init__.py (+67/-23) src/provisioningserver/utils/tests/test_utils.py (+144/-83) |
To merge this branch: | bzr merge lp:~gmb/maas/create-node-to-use-RPC |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+233899@code.launchpad.net |
Commit message
Convert the create_node utility to use the CreateNode RPC call.
Previously, CreateNode didn't raise any errors. Now it raises a NodeAlreadyExists error if a node with one of the MACs passed to CreateNode is already registered.
The create_node utility function has been converted from a REST API-calling function to an RPC-calling one; the overall logic remains the same.
Description of the change
MAAS Lander (maas-lander) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~gmb/maas/create-node-to-use-RPC into lp:maas failed. Below is the output from the failed tests.
Ign http://
Get:1 http://
Get:2 http://
Ign http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Hit http://
Get:6 http://
Hit http://
Get:7 http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:8 http://
Hit http://
Hit http://
Ign http://
Ign http://
Get:9 http://
Get:10 http://
Get:11 http://
Get:12 http://
Hit http://
Hit http://
Fetched 1,071 kB in 0s (1,794 kB/s)
Reading package lists...
sudo DEBIAN_
--
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~gmb/maas/create-node-to-use-RPC into lp:maas failed. Below is the output from the failed tests.
Ign http://
Ign http://
Get:1 http://
Get:2 http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Get:6 http://
Hit http://
Hit http://
Get:7 http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:8 http://
Hit http://
Hit http://
Ign http://
Ign http://
Get:9 http://
Get:10 http://
Get:11 http://
Get:12 http://
Hit http://
Hit http://
Fetched 1,071 kB in 0s (1,824 kB/s)
Reading package lists...
sudo DEBIAN_
--
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~gmb/maas/create-node-to-use-RPC into lp:maas failed. Below is the output from the failed tests.
Ign http://
Ign http://
Get:1 http://
Get:2 http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Hit http://
Get:6 http://
Get:7 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:8 http://
Hit http://
Hit http://
Ign http://
Ign http://
Get:9 http://
Get:10 http://
Get:11 http://
Get:12 http://
Hit http://
Hit http://
Fetched 1,079 kB in 0s (1,788 kB/s)
Reading package lists...
sudo DEBIAN_
--
Preview Diff
1 | === modified file 'src/maasserver/rpc/nodes.py' |
2 | --- src/maasserver/rpc/nodes.py 2014-09-05 08:50:13 +0000 |
3 | +++ src/maasserver/rpc/nodes.py 2014-09-10 14:33:19 +0000 |
4 | @@ -28,6 +28,7 @@ |
5 | ) |
6 | from maasserver.utils.async import transactional |
7 | from provisioningserver.rpc.exceptions import ( |
8 | + NodeAlreadyExists, |
9 | NoSuchCluster, |
10 | NoSuchNode, |
11 | ) |
12 | @@ -92,6 +93,7 @@ |
13 | node.update_power_state(power_state) |
14 | |
15 | |
16 | +@synchronous |
17 | @transactional |
18 | def create_node(cluster_uuid, architecture, power_type, |
19 | power_parameters, mac_addresses): |
20 | @@ -106,6 +108,13 @@ |
21 | :param mac_addresses: An iterable of MAC addresses that belong to |
22 | the node. |
23 | """ |
24 | + # Check that there isn't already a node with one of our MAC |
25 | + # addresses, and bail out early if there is. |
26 | + nodes = Node.objects.filter(macaddress__mac_address__in=mac_addresses) |
27 | + if nodes.count() > 0: |
28 | + raise NodeAlreadyExists( |
29 | + "One of the MACs %s is already in use by a node." % |
30 | + mac_addresses) |
31 | cluster = NodeGroup.objects.get_by_natural_key(cluster_uuid) |
32 | data = { |
33 | 'power_type': power_type, |
34 | |
35 | === modified file 'src/maasserver/rpc/tests/test_nodes.py' |
36 | --- src/maasserver/rpc/tests/test_nodes.py 2014-09-05 11:25:28 +0000 |
37 | +++ src/maasserver/rpc/tests/test_nodes.py 2014-09-10 14:33:19 +0000 |
38 | @@ -25,14 +25,15 @@ |
39 | RunningEventLoopFixture, |
40 | ) |
41 | from maasserver.testing.factory import factory |
42 | -from maastesting.testcase import MAASTestCase |
43 | +from maasserver.testing.testcase import MAASServerTestCase |
44 | from provisioningserver.drivers import PowerTypeRegistry |
45 | from provisioningserver.rpc.cluster import DescribePowerTypes |
46 | +from provisioningserver.rpc.exceptions import NodeAlreadyExists |
47 | from provisioningserver.rpc.testing import always_succeed_with |
48 | from simplejson import dumps |
49 | |
50 | |
51 | -class TestCreateNode(MAASTestCase): |
52 | +class TestCreateNode(MAASServerTestCase): |
53 | |
54 | def prepare_cluster_rpc(self, cluster): |
55 | self.useFixture(RegionEventLoopFixture('rpc')) |
56 | @@ -73,7 +74,7 @@ |
57 | node.power_type, |
58 | node.power_parameters |
59 | )) |
60 | - self.assertItemsEqual( |
61 | + self.assertEqual( |
62 | mac_addresses, |
63 | [mac.mac_address for mac in node.macaddress_set.all()]) |
64 | |
65 | @@ -86,4 +87,22 @@ |
66 | ValidationError, create_node, cluster.uuid, |
67 | architecture="spam/eggs", power_type="scrambled", |
68 | power_parameters=dumps({}), |
69 | - mac_addresses=["this is not a MAC address"]) |
70 | + mac_addresses=[factory.getRandomMACAddress()]) |
71 | + |
72 | + def test__raises_error_if_node_already_exists(self): |
73 | + cluster = factory.make_NodeGroup() |
74 | + cluster.accept() |
75 | + self.prepare_cluster_rpc(cluster) |
76 | + |
77 | + mac_addresses = [ |
78 | + factory.getRandomMACAddress() for _ in range(3)] |
79 | + architecture = make_usable_architecture(self) |
80 | + power_type = random.choice(self.power_types)['name'] |
81 | + power_parameters = dumps({}) |
82 | + |
83 | + create_node( |
84 | + cluster.uuid, architecture, power_type, power_parameters, |
85 | + mac_addresses) |
86 | + self.assertRaises( |
87 | + NodeAlreadyExists, create_node, cluster.uuid, architecture, |
88 | + power_type, power_parameters, [mac_addresses[0]]) |
89 | |
90 | === modified file 'src/provisioningserver/rpc/exceptions.py' |
91 | --- src/provisioningserver/rpc/exceptions.py 2014-09-05 19:51:04 +0000 |
92 | +++ src/provisioningserver/rpc/exceptions.py 2014-09-10 14:33:19 +0000 |
93 | @@ -93,3 +93,7 @@ |
94 | "All failures must be instances of twisted.python." |
95 | "failure.Failure, not %r" % (failure,)) |
96 | super(MultipleFailures, self).__init__(*failures) |
97 | + |
98 | + |
99 | +class NodeAlreadyExists(Exception): |
100 | + """A node already exists with a given MAC address.""" |
101 | |
102 | === modified file 'src/provisioningserver/rpc/region.py' |
103 | --- src/provisioningserver/rpc/region.py 2014-09-05 09:15:57 +0000 |
104 | +++ src/provisioningserver/rpc/region.py 2014-09-10 14:33:19 +0000 |
105 | @@ -16,6 +16,7 @@ |
106 | |
107 | __metaclass__ = type |
108 | __all__ = [ |
109 | + "CreateNode", |
110 | "Identify", |
111 | "MarkNodeFailed", |
112 | "ReportBootImages", |
113 | @@ -29,6 +30,7 @@ |
114 | ) |
115 | from provisioningserver.rpc.common import Identify |
116 | from provisioningserver.rpc.exceptions import ( |
117 | + NodeAlreadyExists, |
118 | NoSuchCluster, |
119 | NoSuchEventType, |
120 | NoSuchNode, |
121 | @@ -291,7 +293,9 @@ |
122 | response = [ |
123 | (b'system_id', amp.Unicode()), |
124 | ] |
125 | - errors = {} |
126 | + errors = { |
127 | + NodeAlreadyExists: b"NodeAlreadyExists", |
128 | + } |
129 | |
130 | |
131 | class TimerExpired(amp.Command): |
132 | |
133 | === modified file 'src/provisioningserver/utils/__init__.py' |
134 | --- src/provisioningserver/utils/__init__.py 2014-08-22 21:53:04 +0000 |
135 | +++ src/provisioningserver/utils/__init__.py 2014-09-10 14:33:19 +0000 |
136 | @@ -36,16 +36,29 @@ |
137 | ) |
138 | from warnings import warn |
139 | |
140 | -from apiclient.maas_client import ( |
141 | - MAASClient, |
142 | - MAASDispatcher, |
143 | - MAASOAuth, |
144 | - ) |
145 | import bson |
146 | -from provisioningserver.auth import get_recorded_api_credentials |
147 | -from provisioningserver.cluster_config import get_maas_url |
148 | +from provisioningserver.cluster_config import get_cluster_uuid |
149 | +from provisioningserver.logger.log import get_maas_logger |
150 | +from provisioningserver.rpc import getRegionClient |
151 | +from provisioningserver.rpc.exceptions import ( |
152 | + NoConnectionsAvailable, |
153 | + NodeAlreadyExists, |
154 | + ) |
155 | +from provisioningserver.utils.twisted import ( |
156 | + pause, |
157 | + retries, |
158 | + ) |
159 | import simplejson as json |
160 | import tempita |
161 | +from twisted.internet import reactor |
162 | +from twisted.internet.defer import ( |
163 | + inlineCallbacks, |
164 | + returnValue, |
165 | + ) |
166 | +from twisted.protocols.amp import UnhandledCommand |
167 | + |
168 | + |
169 | +maaslog = get_maas_logger("utils") |
170 | |
171 | |
172 | def node_exists(macs, url, client): |
173 | @@ -66,25 +79,56 @@ |
174 | return len(content) > 0 |
175 | |
176 | |
177 | +@inlineCallbacks |
178 | def create_node(macs, arch, power_type, power_parameters): |
179 | - api_credentials = get_recorded_api_credentials() |
180 | - if api_credentials is None: |
181 | - raise Exception('Not creating node: no API key yet.') |
182 | - client = MAASClient( |
183 | - MAASOAuth(*api_credentials), MAASDispatcher(), |
184 | - get_maas_url()) |
185 | + """Create a Node on the region and return its system_id. |
186 | |
187 | - data = { |
188 | - 'architecture': arch, |
189 | - 'power_type': power_type, |
190 | - 'power_parameters': json.dumps(power_parameters), |
191 | - 'mac_addresses': macs, |
192 | - 'autodetect_nodegroup': 'true' |
193 | - } |
194 | - url = '/api/1.0/nodes/' |
195 | - if node_exists(macs, url, client): |
196 | + :param macs: A list of MAC addresses belonging to the node. |
197 | + :param arch: The node's architecture, in the form 'arch/subarch'. |
198 | + :param power_type: The node's power type as a string. |
199 | + :param power_parameters: The power parameters for the node, as a |
200 | + dict. |
201 | + """ |
202 | + # Avoid circular dependencies. |
203 | + from provisioningserver.rpc.region import CreateNode |
204 | + for elapsed, remaining, wait in retries(15, 5, reactor): |
205 | + try: |
206 | + client = getRegionClient() |
207 | + break |
208 | + except NoConnectionsAvailable: |
209 | + yield pause(wait, reactor) |
210 | + else: |
211 | + maaslog.error( |
212 | + "Can't create node, no RPC connection to region.") |
213 | return |
214 | - return client.post(url, 'new', **data) |
215 | + |
216 | + # De-dupe the MAC addresses we pass. We sort here to avoid test |
217 | + # failures. |
218 | + macs = sorted(set(macs)) |
219 | + try: |
220 | + response = yield client( |
221 | + CreateNode, |
222 | + cluster_uuid=get_cluster_uuid(), |
223 | + architecture=arch, |
224 | + power_type=power_type, |
225 | + power_parameters=json.dumps(power_parameters), |
226 | + mac_addresses=macs) |
227 | + except NodeAlreadyExists: |
228 | + # The node already exists on the region, so we log the error and |
229 | + # give up. |
230 | + maaslog.error( |
231 | + "A node with one of the mac addressess in %s already exists.", |
232 | + macs) |
233 | + returnValue(None) |
234 | + except UnhandledCommand: |
235 | + # The region hasn't been upgraded to support this method |
236 | + # yet, so give up. |
237 | + maaslog.error( |
238 | + "Unable to create node on region: Region does not " |
239 | + "support the CreateNode RPC method.") |
240 | + returnValue(None) |
241 | + else: |
242 | + returnValue(response['system_id']) |
243 | |
244 | |
245 | def locate_config(*path): |
246 | |
247 | === modified file 'src/provisioningserver/utils/tests/test_utils.py' |
248 | --- src/provisioningserver/utils/tests/test_utils.py 2014-08-13 21:49:35 +0000 |
249 | +++ src/provisioningserver/utils/tests/test_utils.py 2014-09-10 14:33:19 +0000 |
250 | @@ -14,27 +14,30 @@ |
251 | __metaclass__ = type |
252 | __all__ = [] |
253 | |
254 | -import httplib |
255 | import json |
256 | import os |
257 | -from random import randint |
258 | +from random import ( |
259 | + choice, |
260 | + randint, |
261 | + ) |
262 | from textwrap import dedent |
263 | |
264 | -from apiclient.maas_client import MAASClient |
265 | -from apiclient.testing.credentials import make_api_credentials |
266 | from fixtures import EnvironmentVariableFixture |
267 | from maastesting import root |
268 | from maastesting.factory import factory |
269 | -from maastesting.matchers import ( |
270 | - MockCalledOnceWith, |
271 | - MockNotCalled, |
272 | +from maastesting.matchers import MockCalledOnceWith |
273 | +from maastesting.testcase import ( |
274 | + MAASTestCase, |
275 | + MAASTwistedRunTest, |
276 | ) |
277 | -from maastesting.testcase import MAASTestCase |
278 | from mock import ( |
279 | Mock, |
280 | sentinel, |
281 | ) |
282 | import provisioningserver |
283 | +from provisioningserver.rpc import region |
284 | +from provisioningserver.rpc.exceptions import NodeAlreadyExists |
285 | +from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture |
286 | from provisioningserver.testing.testcase import PservTestCase |
287 | import provisioningserver.utils |
288 | from provisioningserver.utils import ( |
289 | @@ -54,6 +57,7 @@ |
290 | DirExists, |
291 | EndsWith, |
292 | ) |
293 | +from twisted.internet import defer |
294 | |
295 | |
296 | def get_branch_dir(*path): |
297 | @@ -400,82 +404,139 @@ |
298 | |
299 | class TestCreateNode(PservTestCase): |
300 | |
301 | + run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) |
302 | + |
303 | + def prepare_region_rpc(self): |
304 | + fixture = self.useFixture(MockLiveClusterToRegionRPCFixture()) |
305 | + protocol, connecting = fixture.makeEventLoop(region.CreateNode) |
306 | + return protocol, connecting |
307 | + |
308 | + @defer.inlineCallbacks |
309 | + def test_calls_create_node_rpc(self): |
310 | + protocol, connecting = self.prepare_region_rpc() |
311 | + self.addCleanup((yield connecting)) |
312 | + protocol.CreateNode.return_value = defer.succeed( |
313 | + {"system_id": factory.make_name("system-id")}) |
314 | + |
315 | + uuid = 'node-' + factory.make_UUID() |
316 | + macs = sorted(factory.getRandomMACAddress() for x in range(3)) |
317 | + arch = factory.make_name('architecture') |
318 | + power_type = factory.make_name('power_type') |
319 | + power_parameters = { |
320 | + 'power_address': factory.getRandomIPAddress(), |
321 | + 'power_user': factory.make_name('power_user'), |
322 | + 'power_pass': factory.make_name('power_pass'), |
323 | + 'power_control': None, |
324 | + 'system_id': uuid |
325 | + } |
326 | + get_cluster_uuid = self.patch( |
327 | + provisioningserver.utils, 'get_cluster_uuid') |
328 | + get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() |
329 | + yield create_node( |
330 | + macs, arch, power_type, power_parameters) |
331 | + self.assertThat( |
332 | + protocol.CreateNode, MockCalledOnceWith( |
333 | + protocol, cluster_uuid=get_cluster_uuid.return_value, |
334 | + architecture=arch, power_type=power_type, |
335 | + power_parameters=json.dumps(power_parameters), |
336 | + mac_addresses=macs)) |
337 | + |
338 | + @defer.inlineCallbacks |
339 | + def test_returns_system_id_of_new_node(self): |
340 | + protocol, connecting = self.prepare_region_rpc() |
341 | + self.addCleanup((yield connecting)) |
342 | + system_id = factory.make_name("system-id") |
343 | + protocol.CreateNode.return_value = defer.succeed( |
344 | + {"system_id": system_id}) |
345 | + get_cluster_uuid = self.patch( |
346 | + provisioningserver.utils, 'get_cluster_uuid') |
347 | + get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() |
348 | + |
349 | + uuid = 'node-' + factory.make_UUID() |
350 | + macs = sorted(factory.getRandomMACAddress() for x in range(3)) |
351 | + arch = factory.make_name('architecture') |
352 | + power_type = factory.make_name('power_type') |
353 | + power_parameters = { |
354 | + 'power_address': factory.getRandomIPAddress(), |
355 | + 'power_user': factory.make_name('power_user'), |
356 | + 'power_pass': factory.make_name('power_pass'), |
357 | + 'power_control': None, |
358 | + 'system_id': uuid |
359 | + } |
360 | + new_system_id = yield create_node( |
361 | + macs, arch, power_type, power_parameters) |
362 | + self.assertEqual(system_id, new_system_id) |
363 | + |
364 | + @defer.inlineCallbacks |
365 | def test_passes_on_no_duplicate_macs(self): |
366 | - url = '/api/1.0/nodes/' |
367 | - uuid = 'node-' + factory.make_UUID() |
368 | - macs = [factory.getRandomMACAddress() for x in range(3)] |
369 | - arch = factory.make_name('architecture') |
370 | - power_type = factory.make_name('power_type') |
371 | - power_parameters = { |
372 | - 'power_address': factory.getRandomIPAddress(), |
373 | - 'power_user': factory.make_name('power_user'), |
374 | - 'power_pass': factory.make_name('power_pass'), |
375 | - 'power_control': None, |
376 | - 'system_id': uuid |
377 | - } |
378 | - |
379 | - make_api_credentials() |
380 | - provisioningserver.auth.record_api_credentials( |
381 | - ':'.join(make_api_credentials())) |
382 | - self.set_maas_url() |
383 | - get = self.patch(MAASClient, 'get') |
384 | - post = self.patch(MAASClient, 'post') |
385 | - |
386 | - # Test for no duplicate macs |
387 | - get_data = [] |
388 | - response = factory.make_response( |
389 | - httplib.OK, json.dumps(get_data), |
390 | - 'application/json') |
391 | - get.return_value = response |
392 | - create_node(macs, arch, power_type, power_parameters) |
393 | - post_data = { |
394 | - 'architecture': arch, |
395 | - 'power_type': power_type, |
396 | - 'power_parameters': json.dumps(power_parameters), |
397 | - 'mac_addresses': macs, |
398 | - 'autodetect_nodegroup': 'true' |
399 | - } |
400 | - self.assertThat(post, MockCalledOnceWith(url, 'new', **post_data)) |
401 | - |
402 | - def test_errors_on_duplicate_macs(self): |
403 | - url = '/api/1.0/nodes/' |
404 | - uuid = 'node-' + factory.make_UUID() |
405 | - macs = [factory.getRandomMACAddress() for x in range(3)] |
406 | - arch = factory.make_name('architecture') |
407 | - power_type = factory.make_name('power_type') |
408 | - power_parameters = { |
409 | - 'power_address': factory.getRandomIPAddress(), |
410 | - 'power_user': factory.make_name('power_user'), |
411 | - 'power_pass': factory.make_name('power_pass'), |
412 | - 'power_control': None, |
413 | - 'system_id': uuid |
414 | - } |
415 | - |
416 | - make_api_credentials() |
417 | - provisioningserver.auth.record_api_credentials( |
418 | - ':'.join(make_api_credentials())) |
419 | - self.set_maas_url() |
420 | - get = self.patch(MAASClient, 'get') |
421 | - post = self.patch(MAASClient, 'post') |
422 | - |
423 | - # Test for a duplicate mac |
424 | - resource_uri1 = url + "%s/macs/%s/" % (uuid, macs[0]) |
425 | - get_data = [ |
426 | - { |
427 | - "macaddress_set": [ |
428 | - { |
429 | - "resource_uri": resource_uri1, |
430 | - "mac_address": macs[0] |
431 | - } |
432 | - ] |
433 | - } |
434 | - ] |
435 | - response = factory.make_response( |
436 | - httplib.OK, json.dumps(get_data), |
437 | - 'application/json') |
438 | - get.return_value = response |
439 | - create_node(macs, arch, power_type, power_parameters) |
440 | - self.assertThat(post, MockNotCalled()) |
441 | + protocol, connecting = self.prepare_region_rpc() |
442 | + self.addCleanup((yield connecting)) |
443 | + system_id = factory.make_name("system-id") |
444 | + protocol.CreateNode.return_value = defer.succeed( |
445 | + {"system_id": system_id}) |
446 | + get_cluster_uuid = self.patch( |
447 | + provisioningserver.utils, 'get_cluster_uuid') |
448 | + get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() |
449 | + |
450 | + uuid = 'node-' + factory.make_UUID() |
451 | + arch = factory.make_name('architecture') |
452 | + power_type = factory.make_name('power_type') |
453 | + power_parameters = { |
454 | + 'power_address': factory.getRandomIPAddress(), |
455 | + 'power_user': factory.make_name('power_user'), |
456 | + 'power_pass': factory.make_name('power_pass'), |
457 | + 'power_control': None, |
458 | + 'system_id': uuid |
459 | + } |
460 | + |
461 | + # Create a list of MACs with one random duplicate. |
462 | + macs = sorted(factory.getRandomMACAddress() for _ in range(3)) |
463 | + macs_with_duplicate = macs + [choice(macs)] |
464 | + |
465 | + yield create_node( |
466 | + macs_with_duplicate, arch, power_type, power_parameters) |
467 | + self.assertThat( |
468 | + protocol.CreateNode, MockCalledOnceWith( |
469 | + protocol, cluster_uuid=get_cluster_uuid.return_value, |
470 | + architecture=arch, power_type=power_type, |
471 | + power_parameters=json.dumps(power_parameters), |
472 | + mac_addresses=macs)) |
473 | + |
474 | + @defer.inlineCallbacks |
475 | + def test_logs_error_on_duplicate_macs(self): |
476 | + protocol, connecting = self.prepare_region_rpc() |
477 | + self.addCleanup((yield connecting)) |
478 | + system_id = factory.make_name("system-id") |
479 | + maaslog = self.patch(provisioningserver.utils, 'maaslog') |
480 | + get_cluster_uuid = self.patch( |
481 | + provisioningserver.utils, 'get_cluster_uuid') |
482 | + get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() |
483 | + |
484 | + uuid = 'node-' + factory.make_UUID() |
485 | + macs = sorted(factory.getRandomMACAddress() for _ in range(3)) |
486 | + arch = factory.make_name('architecture') |
487 | + power_type = factory.make_name('power_type') |
488 | + power_parameters = { |
489 | + 'power_address': factory.getRandomIPAddress(), |
490 | + 'power_user': factory.make_name('power_user'), |
491 | + 'power_pass': factory.make_name('power_pass'), |
492 | + 'power_control': None, |
493 | + 'system_id': uuid |
494 | + } |
495 | + |
496 | + protocol.CreateNode.side_effect = [ |
497 | + defer.succeed({"system_id": system_id}), |
498 | + defer.fail(NodeAlreadyExists("Node already exists.")), |
499 | + ] |
500 | + |
501 | + yield create_node( |
502 | + macs, arch, power_type, power_parameters) |
503 | + yield create_node( |
504 | + macs, arch, power_type, power_parameters) |
505 | + self.assertThat( |
506 | + maaslog.error, MockCalledOnceWith( |
507 | + "A node with one of the mac addressess in %s already " |
508 | + "exists.", macs)) |
509 | |
510 | |
511 | class TestComposeURL(MAASTestCase): |
Looks good. Only one comment inline.