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 | 28 | ) | 28 | ) |
6 | 29 | from maasserver.utils.async import transactional | 29 | from maasserver.utils.async import transactional |
7 | 30 | from provisioningserver.rpc.exceptions import ( | 30 | from provisioningserver.rpc.exceptions import ( |
8 | 31 | NodeAlreadyExists, | ||
9 | 31 | NoSuchCluster, | 32 | NoSuchCluster, |
10 | 32 | NoSuchNode, | 33 | NoSuchNode, |
11 | 33 | ) | 34 | ) |
12 | @@ -92,6 +93,7 @@ | |||
13 | 92 | node.update_power_state(power_state) | 93 | node.update_power_state(power_state) |
14 | 93 | 94 | ||
15 | 94 | 95 | ||
16 | 96 | @synchronous | ||
17 | 95 | @transactional | 97 | @transactional |
18 | 96 | def create_node(cluster_uuid, architecture, power_type, | 98 | def create_node(cluster_uuid, architecture, power_type, |
19 | 97 | power_parameters, mac_addresses): | 99 | power_parameters, mac_addresses): |
20 | @@ -106,6 +108,13 @@ | |||
21 | 106 | :param mac_addresses: An iterable of MAC addresses that belong to | 108 | :param mac_addresses: An iterable of MAC addresses that belong to |
22 | 107 | the node. | 109 | the node. |
23 | 108 | """ | 110 | """ |
24 | 111 | # Check that there isn't already a node with one of our MAC | ||
25 | 112 | # addresses, and bail out early if there is. | ||
26 | 113 | nodes = Node.objects.filter(macaddress__mac_address__in=mac_addresses) | ||
27 | 114 | if nodes.count() > 0: | ||
28 | 115 | raise NodeAlreadyExists( | ||
29 | 116 | "One of the MACs %s is already in use by a node." % | ||
30 | 117 | mac_addresses) | ||
31 | 109 | cluster = NodeGroup.objects.get_by_natural_key(cluster_uuid) | 118 | cluster = NodeGroup.objects.get_by_natural_key(cluster_uuid) |
32 | 110 | data = { | 119 | data = { |
33 | 111 | 'power_type': power_type, | 120 | 'power_type': power_type, |
34 | 112 | 121 | ||
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 | 25 | RunningEventLoopFixture, | 25 | RunningEventLoopFixture, |
40 | 26 | ) | 26 | ) |
41 | 27 | from maasserver.testing.factory import factory | 27 | from maasserver.testing.factory import factory |
43 | 28 | from maastesting.testcase import MAASTestCase | 28 | from maasserver.testing.testcase import MAASServerTestCase |
44 | 29 | from provisioningserver.drivers import PowerTypeRegistry | 29 | from provisioningserver.drivers import PowerTypeRegistry |
45 | 30 | from provisioningserver.rpc.cluster import DescribePowerTypes | 30 | from provisioningserver.rpc.cluster import DescribePowerTypes |
46 | 31 | from provisioningserver.rpc.exceptions import NodeAlreadyExists | ||
47 | 31 | from provisioningserver.rpc.testing import always_succeed_with | 32 | from provisioningserver.rpc.testing import always_succeed_with |
48 | 32 | from simplejson import dumps | 33 | from simplejson import dumps |
49 | 33 | 34 | ||
50 | 34 | 35 | ||
52 | 35 | class TestCreateNode(MAASTestCase): | 36 | class TestCreateNode(MAASServerTestCase): |
53 | 36 | 37 | ||
54 | 37 | def prepare_cluster_rpc(self, cluster): | 38 | def prepare_cluster_rpc(self, cluster): |
55 | 38 | self.useFixture(RegionEventLoopFixture('rpc')) | 39 | self.useFixture(RegionEventLoopFixture('rpc')) |
56 | @@ -73,7 +74,7 @@ | |||
57 | 73 | node.power_type, | 74 | node.power_type, |
58 | 74 | node.power_parameters | 75 | node.power_parameters |
59 | 75 | )) | 76 | )) |
61 | 76 | self.assertItemsEqual( | 77 | self.assertEqual( |
62 | 77 | mac_addresses, | 78 | mac_addresses, |
63 | 78 | [mac.mac_address for mac in node.macaddress_set.all()]) | 79 | [mac.mac_address for mac in node.macaddress_set.all()]) |
64 | 79 | 80 | ||
65 | @@ -86,4 +87,22 @@ | |||
66 | 86 | ValidationError, create_node, cluster.uuid, | 87 | ValidationError, create_node, cluster.uuid, |
67 | 87 | architecture="spam/eggs", power_type="scrambled", | 88 | architecture="spam/eggs", power_type="scrambled", |
68 | 88 | power_parameters=dumps({}), | 89 | power_parameters=dumps({}), |
70 | 89 | mac_addresses=["this is not a MAC address"]) | 90 | mac_addresses=[factory.getRandomMACAddress()]) |
71 | 91 | |||
72 | 92 | def test__raises_error_if_node_already_exists(self): | ||
73 | 93 | cluster = factory.make_NodeGroup() | ||
74 | 94 | cluster.accept() | ||
75 | 95 | self.prepare_cluster_rpc(cluster) | ||
76 | 96 | |||
77 | 97 | mac_addresses = [ | ||
78 | 98 | factory.getRandomMACAddress() for _ in range(3)] | ||
79 | 99 | architecture = make_usable_architecture(self) | ||
80 | 100 | power_type = random.choice(self.power_types)['name'] | ||
81 | 101 | power_parameters = dumps({}) | ||
82 | 102 | |||
83 | 103 | create_node( | ||
84 | 104 | cluster.uuid, architecture, power_type, power_parameters, | ||
85 | 105 | mac_addresses) | ||
86 | 106 | self.assertRaises( | ||
87 | 107 | NodeAlreadyExists, create_node, cluster.uuid, architecture, | ||
88 | 108 | power_type, power_parameters, [mac_addresses[0]]) | ||
89 | 90 | 109 | ||
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 | 93 | "All failures must be instances of twisted.python." | 93 | "All failures must be instances of twisted.python." |
95 | 94 | "failure.Failure, not %r" % (failure,)) | 94 | "failure.Failure, not %r" % (failure,)) |
96 | 95 | super(MultipleFailures, self).__init__(*failures) | 95 | super(MultipleFailures, self).__init__(*failures) |
97 | 96 | |||
98 | 97 | |||
99 | 98 | class NodeAlreadyExists(Exception): | ||
100 | 99 | """A node already exists with a given MAC address.""" | ||
101 | 96 | 100 | ||
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 | 16 | 16 | ||
107 | 17 | __metaclass__ = type | 17 | __metaclass__ = type |
108 | 18 | __all__ = [ | 18 | __all__ = [ |
109 | 19 | "CreateNode", | ||
110 | 19 | "Identify", | 20 | "Identify", |
111 | 20 | "MarkNodeFailed", | 21 | "MarkNodeFailed", |
112 | 21 | "ReportBootImages", | 22 | "ReportBootImages", |
113 | @@ -29,6 +30,7 @@ | |||
114 | 29 | ) | 30 | ) |
115 | 30 | from provisioningserver.rpc.common import Identify | 31 | from provisioningserver.rpc.common import Identify |
116 | 31 | from provisioningserver.rpc.exceptions import ( | 32 | from provisioningserver.rpc.exceptions import ( |
117 | 33 | NodeAlreadyExists, | ||
118 | 32 | NoSuchCluster, | 34 | NoSuchCluster, |
119 | 33 | NoSuchEventType, | 35 | NoSuchEventType, |
120 | 34 | NoSuchNode, | 36 | NoSuchNode, |
121 | @@ -291,7 +293,9 @@ | |||
122 | 291 | response = [ | 293 | response = [ |
123 | 292 | (b'system_id', amp.Unicode()), | 294 | (b'system_id', amp.Unicode()), |
124 | 293 | ] | 295 | ] |
126 | 294 | errors = {} | 296 | errors = { |
127 | 297 | NodeAlreadyExists: b"NodeAlreadyExists", | ||
128 | 298 | } | ||
129 | 295 | 299 | ||
130 | 296 | 300 | ||
131 | 297 | class TimerExpired(amp.Command): | 301 | class TimerExpired(amp.Command): |
132 | 298 | 302 | ||
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 | 36 | ) | 36 | ) |
138 | 37 | from warnings import warn | 37 | from warnings import warn |
139 | 38 | 38 | ||
140 | 39 | from apiclient.maas_client import ( | ||
141 | 40 | MAASClient, | ||
142 | 41 | MAASDispatcher, | ||
143 | 42 | MAASOAuth, | ||
144 | 43 | ) | ||
145 | 44 | import bson | 39 | import bson |
148 | 45 | from provisioningserver.auth import get_recorded_api_credentials | 40 | from provisioningserver.cluster_config import get_cluster_uuid |
149 | 46 | from provisioningserver.cluster_config import get_maas_url | 41 | from provisioningserver.logger.log import get_maas_logger |
150 | 42 | from provisioningserver.rpc import getRegionClient | ||
151 | 43 | from provisioningserver.rpc.exceptions import ( | ||
152 | 44 | NoConnectionsAvailable, | ||
153 | 45 | NodeAlreadyExists, | ||
154 | 46 | ) | ||
155 | 47 | from provisioningserver.utils.twisted import ( | ||
156 | 48 | pause, | ||
157 | 49 | retries, | ||
158 | 50 | ) | ||
159 | 47 | import simplejson as json | 51 | import simplejson as json |
160 | 48 | import tempita | 52 | import tempita |
161 | 53 | from twisted.internet import reactor | ||
162 | 54 | from twisted.internet.defer import ( | ||
163 | 55 | inlineCallbacks, | ||
164 | 56 | returnValue, | ||
165 | 57 | ) | ||
166 | 58 | from twisted.protocols.amp import UnhandledCommand | ||
167 | 59 | |||
168 | 60 | |||
169 | 61 | maaslog = get_maas_logger("utils") | ||
170 | 49 | 62 | ||
171 | 50 | 63 | ||
172 | 51 | def node_exists(macs, url, client): | 64 | def node_exists(macs, url, client): |
173 | @@ -66,25 +79,56 @@ | |||
174 | 66 | return len(content) > 0 | 79 | return len(content) > 0 |
175 | 67 | 80 | ||
176 | 68 | 81 | ||
177 | 82 | @inlineCallbacks | ||
178 | 69 | def create_node(macs, arch, power_type, power_parameters): | 83 | def create_node(macs, arch, power_type, power_parameters): |
185 | 70 | api_credentials = get_recorded_api_credentials() | 84 | """Create a Node on the region and return its system_id. |
180 | 71 | if api_credentials is None: | ||
181 | 72 | raise Exception('Not creating node: no API key yet.') | ||
182 | 73 | client = MAASClient( | ||
183 | 74 | MAASOAuth(*api_credentials), MAASDispatcher(), | ||
184 | 75 | get_maas_url()) | ||
186 | 76 | 85 | ||
196 | 77 | data = { | 86 | :param macs: A list of MAC addresses belonging to the node. |
197 | 78 | 'architecture': arch, | 87 | :param arch: The node's architecture, in the form 'arch/subarch'. |
198 | 79 | 'power_type': power_type, | 88 | :param power_type: The node's power type as a string. |
199 | 80 | 'power_parameters': json.dumps(power_parameters), | 89 | :param power_parameters: The power parameters for the node, as a |
200 | 81 | 'mac_addresses': macs, | 90 | dict. |
201 | 82 | 'autodetect_nodegroup': 'true' | 91 | """ |
202 | 83 | } | 92 | # Avoid circular dependencies. |
203 | 84 | url = '/api/1.0/nodes/' | 93 | from provisioningserver.rpc.region import CreateNode |
204 | 85 | if node_exists(macs, url, client): | 94 | for elapsed, remaining, wait in retries(15, 5, reactor): |
205 | 95 | try: | ||
206 | 96 | client = getRegionClient() | ||
207 | 97 | break | ||
208 | 98 | except NoConnectionsAvailable: | ||
209 | 99 | yield pause(wait, reactor) | ||
210 | 100 | else: | ||
211 | 101 | maaslog.error( | ||
212 | 102 | "Can't create node, no RPC connection to region.") | ||
213 | 86 | return | 103 | return |
215 | 87 | return client.post(url, 'new', **data) | 104 | |
216 | 105 | # De-dupe the MAC addresses we pass. We sort here to avoid test | ||
217 | 106 | # failures. | ||
218 | 107 | macs = sorted(set(macs)) | ||
219 | 108 | try: | ||
220 | 109 | response = yield client( | ||
221 | 110 | CreateNode, | ||
222 | 111 | cluster_uuid=get_cluster_uuid(), | ||
223 | 112 | architecture=arch, | ||
224 | 113 | power_type=power_type, | ||
225 | 114 | power_parameters=json.dumps(power_parameters), | ||
226 | 115 | mac_addresses=macs) | ||
227 | 116 | except NodeAlreadyExists: | ||
228 | 117 | # The node already exists on the region, so we log the error and | ||
229 | 118 | # give up. | ||
230 | 119 | maaslog.error( | ||
231 | 120 | "A node with one of the mac addressess in %s already exists.", | ||
232 | 121 | macs) | ||
233 | 122 | returnValue(None) | ||
234 | 123 | except UnhandledCommand: | ||
235 | 124 | # The region hasn't been upgraded to support this method | ||
236 | 125 | # yet, so give up. | ||
237 | 126 | maaslog.error( | ||
238 | 127 | "Unable to create node on region: Region does not " | ||
239 | 128 | "support the CreateNode RPC method.") | ||
240 | 129 | returnValue(None) | ||
241 | 130 | else: | ||
242 | 131 | returnValue(response['system_id']) | ||
243 | 88 | 132 | ||
244 | 89 | 133 | ||
245 | 90 | def locate_config(*path): | 134 | def locate_config(*path): |
246 | 91 | 135 | ||
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 | 14 | __metaclass__ = type | 14 | __metaclass__ = type |
252 | 15 | __all__ = [] | 15 | __all__ = [] |
253 | 16 | 16 | ||
254 | 17 | import httplib | ||
255 | 18 | import json | 17 | import json |
256 | 19 | import os | 18 | import os |
258 | 20 | from random import randint | 19 | from random import ( |
259 | 20 | choice, | ||
260 | 21 | randint, | ||
261 | 22 | ) | ||
262 | 21 | from textwrap import dedent | 23 | from textwrap import dedent |
263 | 22 | 24 | ||
264 | 23 | from apiclient.maas_client import MAASClient | ||
265 | 24 | from apiclient.testing.credentials import make_api_credentials | ||
266 | 25 | from fixtures import EnvironmentVariableFixture | 25 | from fixtures import EnvironmentVariableFixture |
267 | 26 | from maastesting import root | 26 | from maastesting import root |
268 | 27 | from maastesting.factory import factory | 27 | from maastesting.factory import factory |
272 | 28 | from maastesting.matchers import ( | 28 | from maastesting.matchers import MockCalledOnceWith |
273 | 29 | MockCalledOnceWith, | 29 | from maastesting.testcase import ( |
274 | 30 | MockNotCalled, | 30 | MAASTestCase, |
275 | 31 | MAASTwistedRunTest, | ||
276 | 31 | ) | 32 | ) |
277 | 32 | from maastesting.testcase import MAASTestCase | ||
278 | 33 | from mock import ( | 33 | from mock import ( |
279 | 34 | Mock, | 34 | Mock, |
280 | 35 | sentinel, | 35 | sentinel, |
281 | 36 | ) | 36 | ) |
282 | 37 | import provisioningserver | 37 | import provisioningserver |
283 | 38 | from provisioningserver.rpc import region | ||
284 | 39 | from provisioningserver.rpc.exceptions import NodeAlreadyExists | ||
285 | 40 | from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture | ||
286 | 38 | from provisioningserver.testing.testcase import PservTestCase | 41 | from provisioningserver.testing.testcase import PservTestCase |
287 | 39 | import provisioningserver.utils | 42 | import provisioningserver.utils |
288 | 40 | from provisioningserver.utils import ( | 43 | from provisioningserver.utils import ( |
289 | @@ -54,6 +57,7 @@ | |||
290 | 54 | DirExists, | 57 | DirExists, |
291 | 55 | EndsWith, | 58 | EndsWith, |
292 | 56 | ) | 59 | ) |
293 | 60 | from twisted.internet import defer | ||
294 | 57 | 61 | ||
295 | 58 | 62 | ||
296 | 59 | def get_branch_dir(*path): | 63 | def get_branch_dir(*path): |
297 | @@ -400,82 +404,139 @@ | |||
298 | 400 | 404 | ||
299 | 401 | class TestCreateNode(PservTestCase): | 405 | class TestCreateNode(PservTestCase): |
300 | 402 | 406 | ||
301 | 407 | run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) | ||
302 | 408 | |||
303 | 409 | def prepare_region_rpc(self): | ||
304 | 410 | fixture = self.useFixture(MockLiveClusterToRegionRPCFixture()) | ||
305 | 411 | protocol, connecting = fixture.makeEventLoop(region.CreateNode) | ||
306 | 412 | return protocol, connecting | ||
307 | 413 | |||
308 | 414 | @defer.inlineCallbacks | ||
309 | 415 | def test_calls_create_node_rpc(self): | ||
310 | 416 | protocol, connecting = self.prepare_region_rpc() | ||
311 | 417 | self.addCleanup((yield connecting)) | ||
312 | 418 | protocol.CreateNode.return_value = defer.succeed( | ||
313 | 419 | {"system_id": factory.make_name("system-id")}) | ||
314 | 420 | |||
315 | 421 | uuid = 'node-' + factory.make_UUID() | ||
316 | 422 | macs = sorted(factory.getRandomMACAddress() for x in range(3)) | ||
317 | 423 | arch = factory.make_name('architecture') | ||
318 | 424 | power_type = factory.make_name('power_type') | ||
319 | 425 | power_parameters = { | ||
320 | 426 | 'power_address': factory.getRandomIPAddress(), | ||
321 | 427 | 'power_user': factory.make_name('power_user'), | ||
322 | 428 | 'power_pass': factory.make_name('power_pass'), | ||
323 | 429 | 'power_control': None, | ||
324 | 430 | 'system_id': uuid | ||
325 | 431 | } | ||
326 | 432 | get_cluster_uuid = self.patch( | ||
327 | 433 | provisioningserver.utils, 'get_cluster_uuid') | ||
328 | 434 | get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() | ||
329 | 435 | yield create_node( | ||
330 | 436 | macs, arch, power_type, power_parameters) | ||
331 | 437 | self.assertThat( | ||
332 | 438 | protocol.CreateNode, MockCalledOnceWith( | ||
333 | 439 | protocol, cluster_uuid=get_cluster_uuid.return_value, | ||
334 | 440 | architecture=arch, power_type=power_type, | ||
335 | 441 | power_parameters=json.dumps(power_parameters), | ||
336 | 442 | mac_addresses=macs)) | ||
337 | 443 | |||
338 | 444 | @defer.inlineCallbacks | ||
339 | 445 | def test_returns_system_id_of_new_node(self): | ||
340 | 446 | protocol, connecting = self.prepare_region_rpc() | ||
341 | 447 | self.addCleanup((yield connecting)) | ||
342 | 448 | system_id = factory.make_name("system-id") | ||
343 | 449 | protocol.CreateNode.return_value = defer.succeed( | ||
344 | 450 | {"system_id": system_id}) | ||
345 | 451 | get_cluster_uuid = self.patch( | ||
346 | 452 | provisioningserver.utils, 'get_cluster_uuid') | ||
347 | 453 | get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() | ||
348 | 454 | |||
349 | 455 | uuid = 'node-' + factory.make_UUID() | ||
350 | 456 | macs = sorted(factory.getRandomMACAddress() for x in range(3)) | ||
351 | 457 | arch = factory.make_name('architecture') | ||
352 | 458 | power_type = factory.make_name('power_type') | ||
353 | 459 | power_parameters = { | ||
354 | 460 | 'power_address': factory.getRandomIPAddress(), | ||
355 | 461 | 'power_user': factory.make_name('power_user'), | ||
356 | 462 | 'power_pass': factory.make_name('power_pass'), | ||
357 | 463 | 'power_control': None, | ||
358 | 464 | 'system_id': uuid | ||
359 | 465 | } | ||
360 | 466 | new_system_id = yield create_node( | ||
361 | 467 | macs, arch, power_type, power_parameters) | ||
362 | 468 | self.assertEqual(system_id, new_system_id) | ||
363 | 469 | |||
364 | 470 | @defer.inlineCallbacks | ||
365 | 403 | def test_passes_on_no_duplicate_macs(self): | 471 | def test_passes_on_no_duplicate_macs(self): |
441 | 404 | url = '/api/1.0/nodes/' | 472 | protocol, connecting = self.prepare_region_rpc() |
442 | 405 | uuid = 'node-' + factory.make_UUID() | 473 | self.addCleanup((yield connecting)) |
443 | 406 | macs = [factory.getRandomMACAddress() for x in range(3)] | 474 | system_id = factory.make_name("system-id") |
444 | 407 | arch = factory.make_name('architecture') | 475 | protocol.CreateNode.return_value = defer.succeed( |
445 | 408 | power_type = factory.make_name('power_type') | 476 | {"system_id": system_id}) |
446 | 409 | power_parameters = { | 477 | get_cluster_uuid = self.patch( |
447 | 410 | 'power_address': factory.getRandomIPAddress(), | 478 | provisioningserver.utils, 'get_cluster_uuid') |
448 | 411 | 'power_user': factory.make_name('power_user'), | 479 | get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() |
449 | 412 | 'power_pass': factory.make_name('power_pass'), | 480 | |
450 | 413 | 'power_control': None, | 481 | uuid = 'node-' + factory.make_UUID() |
451 | 414 | 'system_id': uuid | 482 | arch = factory.make_name('architecture') |
452 | 415 | } | 483 | power_type = factory.make_name('power_type') |
453 | 416 | 484 | power_parameters = { | |
454 | 417 | make_api_credentials() | 485 | 'power_address': factory.getRandomIPAddress(), |
455 | 418 | provisioningserver.auth.record_api_credentials( | 486 | 'power_user': factory.make_name('power_user'), |
456 | 419 | ':'.join(make_api_credentials())) | 487 | 'power_pass': factory.make_name('power_pass'), |
457 | 420 | self.set_maas_url() | 488 | 'power_control': None, |
458 | 421 | get = self.patch(MAASClient, 'get') | 489 | 'system_id': uuid |
459 | 422 | post = self.patch(MAASClient, 'post') | 490 | } |
460 | 423 | 491 | ||
461 | 424 | # Test for no duplicate macs | 492 | # Create a list of MACs with one random duplicate. |
462 | 425 | get_data = [] | 493 | macs = sorted(factory.getRandomMACAddress() for _ in range(3)) |
463 | 426 | response = factory.make_response( | 494 | macs_with_duplicate = macs + [choice(macs)] |
464 | 427 | httplib.OK, json.dumps(get_data), | 495 | |
465 | 428 | 'application/json') | 496 | yield create_node( |
466 | 429 | get.return_value = response | 497 | macs_with_duplicate, arch, power_type, power_parameters) |
467 | 430 | create_node(macs, arch, power_type, power_parameters) | 498 | self.assertThat( |
468 | 431 | post_data = { | 499 | protocol.CreateNode, MockCalledOnceWith( |
469 | 432 | 'architecture': arch, | 500 | protocol, cluster_uuid=get_cluster_uuid.return_value, |
470 | 433 | 'power_type': power_type, | 501 | architecture=arch, power_type=power_type, |
471 | 434 | 'power_parameters': json.dumps(power_parameters), | 502 | power_parameters=json.dumps(power_parameters), |
472 | 435 | 'mac_addresses': macs, | 503 | mac_addresses=macs)) |
473 | 436 | 'autodetect_nodegroup': 'true' | 504 | |
474 | 437 | } | 505 | @defer.inlineCallbacks |
475 | 438 | self.assertThat(post, MockCalledOnceWith(url, 'new', **post_data)) | 506 | def test_logs_error_on_duplicate_macs(self): |
476 | 439 | 507 | protocol, connecting = self.prepare_region_rpc() | |
477 | 440 | def test_errors_on_duplicate_macs(self): | 508 | self.addCleanup((yield connecting)) |
478 | 441 | url = '/api/1.0/nodes/' | 509 | system_id = factory.make_name("system-id") |
479 | 442 | uuid = 'node-' + factory.make_UUID() | 510 | maaslog = self.patch(provisioningserver.utils, 'maaslog') |
480 | 443 | macs = [factory.getRandomMACAddress() for x in range(3)] | 511 | get_cluster_uuid = self.patch( |
481 | 444 | arch = factory.make_name('architecture') | 512 | provisioningserver.utils, 'get_cluster_uuid') |
482 | 445 | power_type = factory.make_name('power_type') | 513 | get_cluster_uuid.return_value = 'cluster-' + factory.make_UUID() |
483 | 446 | power_parameters = { | 514 | |
484 | 447 | 'power_address': factory.getRandomIPAddress(), | 515 | uuid = 'node-' + factory.make_UUID() |
485 | 448 | 'power_user': factory.make_name('power_user'), | 516 | macs = sorted(factory.getRandomMACAddress() for _ in range(3)) |
486 | 449 | 'power_pass': factory.make_name('power_pass'), | 517 | arch = factory.make_name('architecture') |
487 | 450 | 'power_control': None, | 518 | power_type = factory.make_name('power_type') |
488 | 451 | 'system_id': uuid | 519 | power_parameters = { |
489 | 452 | } | 520 | 'power_address': factory.getRandomIPAddress(), |
490 | 453 | 521 | 'power_user': factory.make_name('power_user'), | |
491 | 454 | make_api_credentials() | 522 | 'power_pass': factory.make_name('power_pass'), |
492 | 455 | provisioningserver.auth.record_api_credentials( | 523 | 'power_control': None, |
493 | 456 | ':'.join(make_api_credentials())) | 524 | 'system_id': uuid |
494 | 457 | self.set_maas_url() | 525 | } |
495 | 458 | get = self.patch(MAASClient, 'get') | 526 | |
496 | 459 | post = self.patch(MAASClient, 'post') | 527 | protocol.CreateNode.side_effect = [ |
497 | 460 | 528 | defer.succeed({"system_id": system_id}), | |
498 | 461 | # Test for a duplicate mac | 529 | defer.fail(NodeAlreadyExists("Node already exists.")), |
499 | 462 | resource_uri1 = url + "%s/macs/%s/" % (uuid, macs[0]) | 530 | ] |
500 | 463 | get_data = [ | 531 | |
501 | 464 | { | 532 | yield create_node( |
502 | 465 | "macaddress_set": [ | 533 | macs, arch, power_type, power_parameters) |
503 | 466 | { | 534 | yield create_node( |
504 | 467 | "resource_uri": resource_uri1, | 535 | macs, arch, power_type, power_parameters) |
505 | 468 | "mac_address": macs[0] | 536 | self.assertThat( |
506 | 469 | } | 537 | maaslog.error, MockCalledOnceWith( |
507 | 470 | ] | 538 | "A node with one of the mac addressess in %s already " |
508 | 471 | } | 539 | "exists.", macs)) |
434 | 472 | ] | ||
435 | 473 | response = factory.make_response( | ||
436 | 474 | httplib.OK, json.dumps(get_data), | ||
437 | 475 | 'application/json') | ||
438 | 476 | get.return_value = response | ||
439 | 477 | create_node(macs, arch, power_type, power_parameters) | ||
440 | 478 | self.assertThat(post, MockNotCalled()) | ||
509 | 479 | 540 | ||
510 | 480 | 541 | ||
511 | 481 | class TestComposeURL(MAASTestCase): | 542 | class TestComposeURL(MAASTestCase): |
Looks good. Only one comment inline.