131
154
["One or more MAC Addresses is invalid."],
132
155
parsed_result['mac_addresses'])
135
class NodeAnonAPITest(TestCase):
157
def test_POST_invalid_architecture_returns_bad_request(self):
158
# If the architecture name provided to create a node is not a valid
159
# architecture name, a 'Bad request' response is returned.
160
response = self.client.post(
161
self.get_uri('nodes/'),
165
'mac_addresses': ['aa:bb:cc:dd:ee:ff'],
166
'architecture': 'invalid-architecture',
168
parsed_result = json.loads(response.content)
170
self.assertEqual(httplib.BAD_REQUEST, response.status_code)
171
self.assertIn('application/json', response['Content-Type'])
172
self.assertItemsEqual(['architecture'], parsed_result)
175
class NodeAnonAPITest(APIv10TestMixin, TestCase):
137
177
def test_anon_nodes_GET(self):
138
178
# Anonymous requests to the API are denied.
139
response = self.client.get('/api/nodes/')
179
response = self.client.get(self.get_uri('nodes/'))
141
181
self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
143
183
def test_anon_api_doc(self):
144
184
# The documentation is accessible to anon users.
145
response = self.client.get('/api/doc/')
185
response = self.client.get(self.get_uri('doc/'))
147
187
self.assertEqual(httplib.OK, response.status_code)
149
189
def test_node_init_user_cannot_access(self):
150
token = NodeKey.objects.create_token(factory.make_node())
190
token = NodeKey.objects.get_token_for_node(factory.make_node())
151
191
client = OAuthAuthenticatedClient(get_node_init_user(), token)
152
response = client.get('/api/nodes/', {'op': 'list'})
192
response = client.get(self.get_uri('nodes/'), {'op': 'list'})
153
193
self.assertEqual(httplib.FORBIDDEN, response.status_code)
156
class APITestCase(TestCase):
196
class APITestCase(APIv10TestMixin, TestCase):
157
197
"""Extension to `TestCase`: log in first.
159
199
:ivar logged_in_user: A user who is currently logged in and can access
214
254
other_node = factory.make_node(
215
255
status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
217
response = self.client.get(self.get_uri(other_node))
257
response = self.client.get(self.get_node_uri(other_node))
219
259
self.assertEqual(httplib.FORBIDDEN, response.status_code)
221
261
def test_GET_refuses_to_access_nonexistent_node(self):
222
262
# When fetching a Node, the api returns a 'Not Found' (404) error
223
263
# if no node is found.
224
response = self.client.get('/api/nodes/invalid-uuid/')
264
response = self.client.get(self.get_uri('nodes/invalid-uuid/'))
226
266
self.assertEqual(httplib.NOT_FOUND, response.status_code)
228
268
def test_POST_stop_checks_permission(self):
229
269
node = factory.make_node()
230
response = self.client.post(self.get_uri(node), {'op': 'stop'})
270
response = self.client.post(self.get_node_uri(node), {'op': 'stop'})
231
271
self.assertEqual(httplib.FORBIDDEN, response.status_code)
233
273
def test_POST_stop_returns_node(self):
234
274
node = factory.make_node(owner=self.logged_in_user)
235
response = self.client.post(self.get_uri(node), {'op': 'stop'})
275
response = self.client.post(self.get_node_uri(node), {'op': 'stop'})
236
276
self.assertEqual(httplib.OK, response.status_code)
237
277
self.assertEqual(
238
278
node.system_id, json.loads(response.content)['system_id'])
240
280
def test_POST_stop_may_be_repeated(self):
241
281
node = factory.make_node(owner=self.logged_in_user)
242
self.client.post(self.get_uri(node), {'op': 'stop'})
243
response = self.client.post(self.get_uri(node), {'op': 'stop'})
282
self.client.post(self.get_node_uri(node), {'op': 'stop'})
283
response = self.client.post(self.get_node_uri(node), {'op': 'stop'})
244
284
self.assertEqual(httplib.OK, response.status_code)
246
286
def test_POST_start_checks_permission(self):
247
287
node = factory.make_node()
248
response = self.client.post(self.get_uri(node), {'op': 'start'})
288
response = self.client.post(self.get_node_uri(node), {'op': 'start'})
249
289
self.assertEqual(httplib.FORBIDDEN, response.status_code)
251
291
def test_POST_start_returns_node(self):
252
292
node = factory.make_node(owner=self.logged_in_user)
253
response = self.client.post(self.get_uri(node), {'op': 'start'})
293
response = self.client.post(self.get_node_uri(node), {'op': 'start'})
254
294
self.assertEqual(httplib.OK, response.status_code)
255
295
self.assertEqual(
256
296
node.system_id, json.loads(response.content)['system_id'])
258
298
def test_POST_start_may_be_repeated(self):
259
299
node = factory.make_node(owner=self.logged_in_user)
260
self.client.post(self.get_uri(node), {'op': 'start'})
261
response = self.client.post(self.get_uri(node), {'op': 'start'})
262
self.assertEqual(httplib.OK, response.status_code)
300
self.client.post(self.get_node_uri(node), {'op': 'start'})
301
response = self.client.post(self.get_node_uri(node), {'op': 'start'})
302
self.assertEqual(httplib.OK, response.status_code)
304
def test_POST_start_stores_user_data(self):
305
node = factory.make_node(owner=self.logged_in_user)
307
b'\xff\x00\xff\xfe\xff\xff\xfe' +
308
factory.getRandomString().encode('ascii'))
309
response = self.client.post(
310
self.get_node_uri(node), {
312
'user_data': b64encode(user_data),
314
self.assertEqual(httplib.OK, response.status_code)
315
self.assertEqual(user_data, NodeUserData.objects.get_user_data(node))
317
def test_POST_release_releases_owned_node(self):
319
NODE_STATUS.RESERVED,
320
NODE_STATUS.ALLOCATED,
323
factory.make_node(owner=self.logged_in_user, status=status)
324
for status in owned_statuses]
326
self.client.post(self.get_node_uri(node), {'op': 'release'})
327
for node in owned_nodes]
329
[httplib.OK] * len(owned_nodes),
330
[response.status_code for response in responses])
331
self.assertItemsEqual(
332
[NODE_STATUS.READY] * len(owned_nodes),
333
[node.status for node in reload_objects(Node, owned_nodes)])
335
def test_POST_release_does_nothing_for_unowned_node(self):
336
node = factory.make_node(status=NODE_STATUS.READY)
337
response = self.client.post(
338
self.get_node_uri(node), {'op': 'release'})
339
self.assertEqual(httplib.OK, response.status_code)
340
self.assertEqual(NODE_STATUS.READY, reload_object(node).status)
342
def test_POST_release_fails_for_other_node_states(self):
343
releasable_statuses = [
344
NODE_STATUS.RESERVED,
345
NODE_STATUS.ALLOCATED,
348
unreleasable_statuses = [
350
for status in map_enum(NODE_STATUS).values()
351
if status not in releasable_statuses]
353
factory.make_node(status=status, owner=self.logged_in_user)
354
for status in unreleasable_statuses]
356
self.client.post(self.get_node_uri(node), {'op': 'release'})
359
[httplib.CONFLICT] * len(unreleasable_statuses),
360
[response.status_code for response in responses])
362
unreleasable_statuses,
363
[node.status for node in reload_objects(Node, nodes)])
365
def test_POST_release_in_wrong_state_reports_current_state(self):
366
node = factory.make_node(
367
status=NODE_STATUS.RETIRED, owner=self.logged_in_user)
368
response = self.client.post(
369
self.get_node_uri(node), {'op': 'release'})
373
"Node cannot be released in its current state ('Retired').",
375
(response.status_code, response.content))
377
def test_POST_release_rejects_request_from_unauthorized_user(self):
378
node = factory.make_node(
379
status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
380
response = self.client.post(
381
self.get_node_uri(node), {'op': 'release'})
382
self.assertEqual(httplib.FORBIDDEN, response.status_code)
383
self.assertEqual(NODE_STATUS.ALLOCATED, reload_object(node).status)
385
def test_POST_release_allows_admin_to_release_anyones_node(self):
386
node = factory.make_node(
387
status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
389
response = self.client.post(
390
self.get_node_uri(node), {'op': 'release'})
391
self.assertEqual(httplib.OK, response.status_code)
392
self.assertEqual(NODE_STATUS.READY, reload_object(node).status)
394
def test_POST_release_combines_with_acquire(self):
395
node = factory.make_node(status=NODE_STATUS.READY)
396
response = self.client.post(
397
self.get_uri('nodes/'), {'op': 'acquire'})
398
self.assertEqual(NODE_STATUS.ALLOCATED, reload_object(node).status)
399
node_uri = json.loads(response.content)['resource_uri']
400
response = self.client.post(node_uri, {'op': 'release'})
401
self.assertEqual(httplib.OK, response.status_code)
402
self.assertEqual(NODE_STATUS.READY, reload_object(node).status)
264
404
def test_PUT_updates_node(self):
265
405
# The api allows to update a Node.
266
406
node = factory.make_node(hostname='diane')
267
407
response = self.client.put(
268
self.get_uri(node), {'hostname': 'francis'})
408
self.get_node_uri(node), {'hostname': 'francis'})
269
409
parsed_result = json.loads(response.content)
271
411
self.assertEqual(httplib.OK, response.status_code)
735
892
self.assertEqual(httplib.NOT_FOUND, response.status_code)
736
893
self.assertIn('text/plain', response['Content-Type'])
737
894
self.assertEqual("File not found", response.content)
897
class MaaSAPIAnonTest(APIv10TestMixin, TestCase):
898
# The MaaS' handler is not accessible to anon users.
900
def test_anon_get_config_forbidden(self):
901
response = self.client.get(
902
self.get_uri('maas/'),
903
{'op': 'get_config'})
905
self.assertEqual(httplib.FORBIDDEN, response.status_code)
907
def test_anon_set_config_forbidden(self):
908
response = self.client.post(
909
self.get_uri('maas/'),
910
{'op': 'set_config'})
912
self.assertEqual(httplib.FORBIDDEN, response.status_code)
915
class MaaSAPITest(APITestCase):
917
def test_simple_user_get_config_forbidden(self):
918
response = self.client.get(
919
self.get_uri('maas/'),
920
{'op': 'get_config'})
922
self.assertEqual(httplib.FORBIDDEN, response.status_code)
924
def test_simple_user_set_config_forbidden(self):
925
response = self.client.post(
926
self.get_uri('maas/'),
927
{'op': 'set_config'})
929
self.assertEqual(httplib.FORBIDDEN, response.status_code)
931
def test_get_config_requires_name_param(self):
933
response = self.client.get(
934
self.get_uri('maas/'),
939
self.assertEqual(httplib.BAD_REQUEST, response.status_code)
940
self.assertEqual("No provided name!", response.content)
942
def test_get_config_returns_config(self):
944
name = factory.getRandomString()
945
value = factory.getRandomString()
946
Config.objects.set_config(name, value)
947
response = self.client.get(
948
self.get_uri('maas/'),
954
self.assertEqual(httplib.OK, response.status_code)
955
parsed_result = json.loads(response.content)
956
self.assertIn('application/json', response['Content-Type'])
957
self.assertEqual(value, parsed_result)
959
def test_set_config_requires_name_param(self):
961
response = self.client.post(
962
self.get_uri('maas/'),
965
'value': factory.getRandomString(),
968
self.assertEqual(httplib.BAD_REQUEST, response.status_code)
969
self.assertEqual("No provided name!", response.content)
971
def test_set_config_requires_string_name_param(self):
973
value = factory.getRandomString()
974
response = self.client.post(
975
self.get_uri('maas/'),
978
'name': '', # Invalid empty name.
982
self.assertEqual(httplib.BAD_REQUEST, response.status_code)
984
"Invalid name: Please enter a value", response.content)
986
def test_set_config_requires_value_param(self):
988
response = self.client.post(
989
self.get_uri('maas/'),
992
'name': factory.getRandomString(),
995
self.assertEqual(httplib.BAD_REQUEST, response.status_code)
996
self.assertEqual("No provided value!", response.content)
998
def test_admin_set_config(self):
1000
name = factory.getRandomString()
1001
value = factory.getRandomString()
1002
response = self.client.post(
1003
self.get_uri('maas/'),
1010
self.assertEqual(httplib.OK, response.status_code)
1011
stored_value = Config.objects.get_config(name)
1012
self.assertEqual(stored_value, value)