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
=== modified file 'src/maasserver/rpc/nodes.py'
--- src/maasserver/rpc/nodes.py 2014-09-05 08:50:13 +0000
+++ src/maasserver/rpc/nodes.py 2014-09-10 14:33:19 +0000
@@ -28,6 +28,7 @@
28 )28 )
29from maasserver.utils.async import transactional29from maasserver.utils.async import transactional
30from provisioningserver.rpc.exceptions import (30from provisioningserver.rpc.exceptions import (
31 NodeAlreadyExists,
31 NoSuchCluster,32 NoSuchCluster,
32 NoSuchNode,33 NoSuchNode,
33 )34 )
@@ -92,6 +93,7 @@
92 node.update_power_state(power_state)93 node.update_power_state(power_state)
9394
9495
96@synchronous
95@transactional97@transactional
96def create_node(cluster_uuid, architecture, power_type,98def create_node(cluster_uuid, architecture, power_type,
97 power_parameters, mac_addresses):99 power_parameters, mac_addresses):
@@ -106,6 +108,13 @@
106 :param mac_addresses: An iterable of MAC addresses that belong to108 :param mac_addresses: An iterable of MAC addresses that belong to
107 the node.109 the node.
108 """110 """
111 # Check that there isn't already a node with one of our MAC
112 # addresses, and bail out early if there is.
113 nodes = Node.objects.filter(macaddress__mac_address__in=mac_addresses)
114 if nodes.count() > 0:
115 raise NodeAlreadyExists(
116 "One of the MACs %s is already in use by a node." %
117 mac_addresses)
109 cluster = NodeGroup.objects.get_by_natural_key(cluster_uuid)118 cluster = NodeGroup.objects.get_by_natural_key(cluster_uuid)
110 data = {119 data = {
111 'power_type': power_type,120 'power_type': power_type,
112121
=== modified file 'src/maasserver/rpc/tests/test_nodes.py'
--- src/maasserver/rpc/tests/test_nodes.py 2014-09-05 11:25:28 +0000
+++ src/maasserver/rpc/tests/test_nodes.py 2014-09-10 14:33:19 +0000
@@ -25,14 +25,15 @@
25 RunningEventLoopFixture,25 RunningEventLoopFixture,
26 )26 )
27from maasserver.testing.factory import factory27from maasserver.testing.factory import factory
28from maastesting.testcase import MAASTestCase28from maasserver.testing.testcase import MAASServerTestCase
29from provisioningserver.drivers import PowerTypeRegistry29from provisioningserver.drivers import PowerTypeRegistry
30from provisioningserver.rpc.cluster import DescribePowerTypes30from provisioningserver.rpc.cluster import DescribePowerTypes
31from provisioningserver.rpc.exceptions import NodeAlreadyExists
31from provisioningserver.rpc.testing import always_succeed_with32from provisioningserver.rpc.testing import always_succeed_with
32from simplejson import dumps33from simplejson import dumps
3334
3435
35class TestCreateNode(MAASTestCase):36class TestCreateNode(MAASServerTestCase):
3637
37 def prepare_cluster_rpc(self, cluster):38 def prepare_cluster_rpc(self, cluster):
38 self.useFixture(RegionEventLoopFixture('rpc'))39 self.useFixture(RegionEventLoopFixture('rpc'))
@@ -73,7 +74,7 @@
73 node.power_type,74 node.power_type,
74 node.power_parameters75 node.power_parameters
75 ))76 ))
76 self.assertItemsEqual(77 self.assertEqual(
77 mac_addresses,78 mac_addresses,
78 [mac.mac_address for mac in node.macaddress_set.all()])79 [mac.mac_address for mac in node.macaddress_set.all()])
7980
@@ -86,4 +87,22 @@
86 ValidationError, create_node, cluster.uuid,87 ValidationError, create_node, cluster.uuid,
87 architecture="spam/eggs", power_type="scrambled",88 architecture="spam/eggs", power_type="scrambled",
88 power_parameters=dumps({}),89 power_parameters=dumps({}),
89 mac_addresses=["this is not a MAC address"])90 mac_addresses=[factory.getRandomMACAddress()])
91
92 def test__raises_error_if_node_already_exists(self):
93 cluster = factory.make_NodeGroup()
94 cluster.accept()
95 self.prepare_cluster_rpc(cluster)
96
97 mac_addresses = [
98 factory.getRandomMACAddress() for _ in range(3)]
99 architecture = make_usable_architecture(self)
100 power_type = random.choice(self.power_types)['name']
101 power_parameters = dumps({})
102
103 create_node(
104 cluster.uuid, architecture, power_type, power_parameters,
105 mac_addresses)
106 self.assertRaises(
107 NodeAlreadyExists, create_node, cluster.uuid, architecture,
108 power_type, power_parameters, [mac_addresses[0]])
90109
=== modified file 'src/provisioningserver/rpc/exceptions.py'
--- src/provisioningserver/rpc/exceptions.py 2014-09-05 19:51:04 +0000
+++ src/provisioningserver/rpc/exceptions.py 2014-09-10 14:33:19 +0000
@@ -93,3 +93,7 @@
93 "All failures must be instances of twisted.python."93 "All failures must be instances of twisted.python."
94 "failure.Failure, not %r" % (failure,))94 "failure.Failure, not %r" % (failure,))
95 super(MultipleFailures, self).__init__(*failures)95 super(MultipleFailures, self).__init__(*failures)
96
97
98class NodeAlreadyExists(Exception):
99 """A node already exists with a given MAC address."""
96100
=== modified file 'src/provisioningserver/rpc/region.py'
--- src/provisioningserver/rpc/region.py 2014-09-05 09:15:57 +0000
+++ src/provisioningserver/rpc/region.py 2014-09-10 14:33:19 +0000
@@ -16,6 +16,7 @@
1616
17__metaclass__ = type17__metaclass__ = type
18__all__ = [18__all__ = [
19 "CreateNode",
19 "Identify",20 "Identify",
20 "MarkNodeFailed",21 "MarkNodeFailed",
21 "ReportBootImages",22 "ReportBootImages",
@@ -29,6 +30,7 @@
29 )30 )
30from provisioningserver.rpc.common import Identify31from provisioningserver.rpc.common import Identify
31from provisioningserver.rpc.exceptions import (32from provisioningserver.rpc.exceptions import (
33 NodeAlreadyExists,
32 NoSuchCluster,34 NoSuchCluster,
33 NoSuchEventType,35 NoSuchEventType,
34 NoSuchNode,36 NoSuchNode,
@@ -291,7 +293,9 @@
291 response = [293 response = [
292 (b'system_id', amp.Unicode()),294 (b'system_id', amp.Unicode()),
293 ]295 ]
294 errors = {}296 errors = {
297 NodeAlreadyExists: b"NodeAlreadyExists",
298 }
295299
296300
297class TimerExpired(amp.Command):301class TimerExpired(amp.Command):
298302
=== modified file 'src/provisioningserver/utils/__init__.py'
--- src/provisioningserver/utils/__init__.py 2014-08-22 21:53:04 +0000
+++ src/provisioningserver/utils/__init__.py 2014-09-10 14:33:19 +0000
@@ -36,16 +36,29 @@
36 )36 )
37from warnings import warn37from warnings import warn
3838
39from apiclient.maas_client import (
40 MAASClient,
41 MAASDispatcher,
42 MAASOAuth,
43 )
44import bson39import bson
45from provisioningserver.auth import get_recorded_api_credentials40from provisioningserver.cluster_config import get_cluster_uuid
46from provisioningserver.cluster_config import get_maas_url41from provisioningserver.logger.log import get_maas_logger
42from provisioningserver.rpc import getRegionClient
43from provisioningserver.rpc.exceptions import (
44 NoConnectionsAvailable,
45 NodeAlreadyExists,
46 )
47from provisioningserver.utils.twisted import (
48 pause,
49 retries,
50 )
47import simplejson as json51import simplejson as json
48import tempita52import tempita
53from twisted.internet import reactor
54from twisted.internet.defer import (
55 inlineCallbacks,
56 returnValue,
57 )
58from twisted.protocols.amp import UnhandledCommand
59
60
61maaslog = get_maas_logger("utils")
4962
5063
51def node_exists(macs, url, client):64def node_exists(macs, url, client):
@@ -66,25 +79,56 @@
66 return len(content) > 079 return len(content) > 0
6780
6881
82@inlineCallbacks
69def create_node(macs, arch, power_type, power_parameters):83def create_node(macs, arch, power_type, power_parameters):
70 api_credentials = get_recorded_api_credentials()84 """Create a Node on the region and return its system_id.
71 if api_credentials is None:
72 raise Exception('Not creating node: no API key yet.')
73 client = MAASClient(
74 MAASOAuth(*api_credentials), MAASDispatcher(),
75 get_maas_url())
7685
77 data = {86 :param macs: A list of MAC addresses belonging to the node.
78 'architecture': arch,87 :param arch: The node's architecture, in the form 'arch/subarch'.
79 'power_type': power_type,88 :param power_type: The node's power type as a string.
80 'power_parameters': json.dumps(power_parameters),89 :param power_parameters: The power parameters for the node, as a
81 'mac_addresses': macs,90 dict.
82 'autodetect_nodegroup': 'true'91 """
83 }92 # Avoid circular dependencies.
84 url = '/api/1.0/nodes/'93 from provisioningserver.rpc.region import CreateNode
85 if node_exists(macs, url, client):94 for elapsed, remaining, wait in retries(15, 5, reactor):
95 try:
96 client = getRegionClient()
97 break
98 except NoConnectionsAvailable:
99 yield pause(wait, reactor)
100 else:
101 maaslog.error(
102 "Can't create node, no RPC connection to region.")
86 return103 return
87 return client.post(url, 'new', **data)104
105 # De-dupe the MAC addresses we pass. We sort here to avoid test
106 # failures.
107 macs = sorted(set(macs))
108 try:
109 response = yield client(
110 CreateNode,
111 cluster_uuid=get_cluster_uuid(),
112 architecture=arch,
113 power_type=power_type,
114 power_parameters=json.dumps(power_parameters),
115 mac_addresses=macs)
116 except NodeAlreadyExists:
117 # The node already exists on the region, so we log the error and
118 # give up.
119 maaslog.error(
120 "A node with one of the mac addressess in %s already exists.",
121 macs)
122 returnValue(None)
123 except UnhandledCommand:
124 # The region hasn't been upgraded to support this method
125 # yet, so give up.
126 maaslog.error(
127 "Unable to create node on region: Region does not "
128 "support the CreateNode RPC method.")
129 returnValue(None)
130 else:
131 returnValue(response['system_id'])
88132
89133
90def locate_config(*path):134def locate_config(*path):
91135
=== modified file 'src/provisioningserver/utils/tests/test_utils.py'
--- src/provisioningserver/utils/tests/test_utils.py 2014-08-13 21:49:35 +0000
+++ src/provisioningserver/utils/tests/test_utils.py 2014-09-10 14:33:19 +0000
@@ -14,27 +14,30 @@
14__metaclass__ = type14__metaclass__ = type
15__all__ = []15__all__ = []
1616
17import httplib
18import json17import json
19import os18import os
20from random import randint19from random import (
20 choice,
21 randint,
22 )
21from textwrap import dedent23from textwrap import dedent
2224
23from apiclient.maas_client import MAASClient
24from apiclient.testing.credentials import make_api_credentials
25from fixtures import EnvironmentVariableFixture25from fixtures import EnvironmentVariableFixture
26from maastesting import root26from maastesting import root
27from maastesting.factory import factory27from maastesting.factory import factory
28from maastesting.matchers import (28from maastesting.matchers import MockCalledOnceWith
29 MockCalledOnceWith,29from maastesting.testcase import (
30 MockNotCalled,30 MAASTestCase,
31 MAASTwistedRunTest,
31 )32 )
32from maastesting.testcase import MAASTestCase
33from mock import (33from mock import (
34 Mock,34 Mock,
35 sentinel,35 sentinel,
36 )36 )
37import provisioningserver37import provisioningserver
38from provisioningserver.rpc import region
39from provisioningserver.rpc.exceptions import NodeAlreadyExists
40from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture
38from provisioningserver.testing.testcase import PservTestCase41from provisioningserver.testing.testcase import PservTestCase
39import provisioningserver.utils42import provisioningserver.utils
40from provisioningserver.utils import (43from provisioningserver.utils import (
@@ -54,6 +57,7 @@
54 DirExists,57 DirExists,
55 EndsWith,58 EndsWith,
56 )59 )
60from twisted.internet import defer
5761
5862
59def get_branch_dir(*path):63def get_branch_dir(*path):
@@ -400,82 +404,139 @@
400404
401class TestCreateNode(PservTestCase):405class TestCreateNode(PservTestCase):
402406
407 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
408
409 def prepare_region_rpc(self):
410 fixture = self.useFixture(MockLiveClusterToRegionRPCFixture())
411 protocol, connecting = fixture.makeEventLoop(region.CreateNode)
412 return protocol, connecting
413
414 @defer.inlineCallbacks
415 def test_calls_create_node_rpc(self):
416 protocol, connecting = self.prepare_region_rpc()
417 self.addCleanup((yield connecting))
418 protocol.CreateNode.return_value = defer.succeed(
419 {"system_id": factory.make_name("system-id")})
420
421 uuid = 'node-' + factory.make_UUID()
422 macs = sorted(factory.getRandomMACAddress() for x in range(3))
423 arch = factory.make_name('architecture')
424 power_type = factory.make_name('power_type')
425 power_parameters = {
426 'power_address': factory.getRandomIPAddress(),
427 'power_user': factory.make_name('power_user'),
428 'power_pass': factory.make_name('power_pass'),
429 'power_control': None,
430 'system_id': uuid
431 }
432 get_cluster_uuid = self.patch(
433 provisioningserver.utils, 'get_cluster_uuid')
434 get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID()
435 yield create_node(
436 macs, arch, power_type, power_parameters)
437 self.assertThat(
438 protocol.CreateNode, MockCalledOnceWith(
439 protocol, cluster_uuid=get_cluster_uuid.return_value,
440 architecture=arch, power_type=power_type,
441 power_parameters=json.dumps(power_parameters),
442 mac_addresses=macs))
443
444 @defer.inlineCallbacks
445 def test_returns_system_id_of_new_node(self):
446 protocol, connecting = self.prepare_region_rpc()
447 self.addCleanup((yield connecting))
448 system_id = factory.make_name("system-id")
449 protocol.CreateNode.return_value = defer.succeed(
450 {"system_id": system_id})
451 get_cluster_uuid = self.patch(
452 provisioningserver.utils, 'get_cluster_uuid')
453 get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID()
454
455 uuid = 'node-' + factory.make_UUID()
456 macs = sorted(factory.getRandomMACAddress() for x in range(3))
457 arch = factory.make_name('architecture')
458 power_type = factory.make_name('power_type')
459 power_parameters = {
460 'power_address': factory.getRandomIPAddress(),
461 'power_user': factory.make_name('power_user'),
462 'power_pass': factory.make_name('power_pass'),
463 'power_control': None,
464 'system_id': uuid
465 }
466 new_system_id = yield create_node(
467 macs, arch, power_type, power_parameters)
468 self.assertEqual(system_id, new_system_id)
469
470 @defer.inlineCallbacks
403 def test_passes_on_no_duplicate_macs(self):471 def test_passes_on_no_duplicate_macs(self):
404 url = '/api/1.0/nodes/'472 protocol, connecting = self.prepare_region_rpc()
405 uuid = 'node-' + factory.make_UUID()473 self.addCleanup((yield connecting))
406 macs = [factory.getRandomMACAddress() for x in range(3)]474 system_id = factory.make_name("system-id")
407 arch = factory.make_name('architecture')475 protocol.CreateNode.return_value = defer.succeed(
408 power_type = factory.make_name('power_type')476 {"system_id": system_id})
409 power_parameters = {477 get_cluster_uuid = self.patch(
410 'power_address': factory.getRandomIPAddress(),478 provisioningserver.utils, 'get_cluster_uuid')
411 'power_user': factory.make_name('power_user'),479 get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID()
412 'power_pass': factory.make_name('power_pass'),480
413 'power_control': None,481 uuid = 'node-' + factory.make_UUID()
414 'system_id': uuid482 arch = factory.make_name('architecture')
415 }483 power_type = factory.make_name('power_type')
416484 power_parameters = {
417 make_api_credentials()485 'power_address': factory.getRandomIPAddress(),
418 provisioningserver.auth.record_api_credentials(486 'power_user': factory.make_name('power_user'),
419 ':'.join(make_api_credentials()))487 'power_pass': factory.make_name('power_pass'),
420 self.set_maas_url()488 'power_control': None,
421 get = self.patch(MAASClient, 'get')489 'system_id': uuid
422 post = self.patch(MAASClient, 'post')490 }
423491
424 # Test for no duplicate macs492 # Create a list of MACs with one random duplicate.
425 get_data = []493 macs = sorted(factory.getRandomMACAddress() for _ in range(3))
426 response = factory.make_response(494 macs_with_duplicate = macs + [choice(macs)]
427 httplib.OK, json.dumps(get_data),495
428 'application/json')496 yield create_node(
429 get.return_value = response497 macs_with_duplicate, arch, power_type, power_parameters)
430 create_node(macs, arch, power_type, power_parameters)498 self.assertThat(
431 post_data = {499 protocol.CreateNode, MockCalledOnceWith(
432 'architecture': arch,500 protocol, cluster_uuid=get_cluster_uuid.return_value,
433 'power_type': power_type,501 architecture=arch, power_type=power_type,
434 'power_parameters': json.dumps(power_parameters),502 power_parameters=json.dumps(power_parameters),
435 'mac_addresses': macs,503 mac_addresses=macs))
436 'autodetect_nodegroup': 'true'504
437 }505 @defer.inlineCallbacks
438 self.assertThat(post, MockCalledOnceWith(url, 'new', **post_data))506 def test_logs_error_on_duplicate_macs(self):
439507 protocol, connecting = self.prepare_region_rpc()
440 def test_errors_on_duplicate_macs(self):508 self.addCleanup((yield connecting))
441 url = '/api/1.0/nodes/'509 system_id = factory.make_name("system-id")
442 uuid = 'node-' + factory.make_UUID()510 maaslog = self.patch(provisioningserver.utils, 'maaslog')
443 macs = [factory.getRandomMACAddress() for x in range(3)]511 get_cluster_uuid = self.patch(
444 arch = factory.make_name('architecture')512 provisioningserver.utils, 'get_cluster_uuid')
445 power_type = factory.make_name('power_type')513 get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID()
446 power_parameters = {514
447 'power_address': factory.getRandomIPAddress(),515 uuid = 'node-' + factory.make_UUID()
448 'power_user': factory.make_name('power_user'),516 macs = sorted(factory.getRandomMACAddress() for _ in range(3))
449 'power_pass': factory.make_name('power_pass'),517 arch = factory.make_name('architecture')
450 'power_control': None,518 power_type = factory.make_name('power_type')
451 'system_id': uuid519 power_parameters = {
452 }520 'power_address': factory.getRandomIPAddress(),
453521 'power_user': factory.make_name('power_user'),
454 make_api_credentials()522 'power_pass': factory.make_name('power_pass'),
455 provisioningserver.auth.record_api_credentials(523 'power_control': None,
456 ':'.join(make_api_credentials()))524 'system_id': uuid
457 self.set_maas_url()525 }
458 get = self.patch(MAASClient, 'get')526
459 post = self.patch(MAASClient, 'post')527 protocol.CreateNode.side_effect = [
460528 defer.succeed({"system_id": system_id}),
461 # Test for a duplicate mac529 defer.fail(NodeAlreadyExists("Node already exists.")),
462 resource_uri1 = url + "%s/macs/%s/" % (uuid, macs[0])530 ]
463 get_data = [531
464 {532 yield create_node(
465 "macaddress_set": [533 macs, arch, power_type, power_parameters)
466 {534 yield create_node(
467 "resource_uri": resource_uri1,535 macs, arch, power_type, power_parameters)
468 "mac_address": macs[0]536 self.assertThat(
469 }537 maaslog.error, MockCalledOnceWith(
470 ]538 "A node with one of the mac addressess in %s already "
471 }539 "exists.", macs))
472 ]
473 response = factory.make_response(
474 httplib.OK, json.dumps(get_data),
475 'application/json')
476 get.return_value = response
477 create_node(macs, arch, power_type, power_parameters)
478 self.assertThat(post, MockNotCalled())
479540
480541
481class TestComposeURL(MAASTestCase):542class TestComposeURL(MAASTestCase):