Merge lp:~gmb/maas/create-node-to-use-RPC into lp:~maas-committers/maas/trunk

Proposed by Graham Binns
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
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.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Only one comment inline.

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

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (23.7 KiB)

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://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [59.7 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [59.7 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [43.9 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [10.8 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [139 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [47.2 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [116 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [82.7 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [312 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [199 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Fetched 1,071 kB in 0s (1,794 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openss...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (20.4 KiB)

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://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [59.7 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [59.7 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [43.9 kB]
Get:6 http://security.ubuntu.com trusty-security/universe Sources [10.8 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [139 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [47.2 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [116 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [82.7 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [312 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [199 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Fetched 1,071 kB in 0s (1,824 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openss...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (20.2 KiB)

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://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [59.7 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [59.7 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [43.9 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [10.8 kB]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [138 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [47.2 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [117 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [83.6 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [316 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [201 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Fetched 1,079 kB in 0s (1,788 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openss...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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):