17
17
from io import BytesIO
19
from maasserver.exceptions import Unauthorized
20
from maasserver.models import (
19
from django.conf import settings
20
from django.core.exceptions import PermissionDenied
21
from django.core.urlresolvers import reverse
22
from maasserver.enum import NODE_STATUS
23
from maasserver.exceptions import (
27
from maasserver.models import SSHKey
24
28
from maasserver.provisioning import get_provisioning_api_proxy
25
29
from maasserver.testing import reload_object
26
30
from maasserver.testing.factory import factory
27
31
from maasserver.testing.oauthclient import OAuthAuthenticatedClient
28
from maastesting.testcase import TestCase
32
from maastesting.djangotestcase import DjangoTestCase
33
from metadataserver import api
29
34
from metadataserver.api import (
31
37
get_node_for_request,
32
39
make_list_response,
33
40
make_text_response,
86
93
get_node_for_request, self.fake_request())
89
class TestViews(TestCase, ProvisioningFakeFactory):
95
def test_get_node_for_mac_refuses_if_anonymous_access_disabled(self):
96
self.patch(settings, 'ALLOW_UNSAFE_METADATA_ACCESS', False)
98
PermissionDenied, get_node_for_mac, factory.getRandomMACAddress())
100
def test_get_node_for_mac_raises_404_for_unknown_mac(self):
102
MAASAPINotFound, get_node_for_mac, factory.getRandomMACAddress())
104
def test_get_node_for_mac_finds_node_by_mac(self):
105
mac = factory.make_mac_address()
106
self.assertEqual(mac.node, get_node_for_mac(mac.mac_address))
108
def test_get_queried_node_looks_up_by_mac_if_given(self):
109
mac = factory.make_mac_address()
112
get_queried_node(object(), for_mac=mac.mac_address))
114
def test_get_queried_node_looks_up_oauth_key_by_default(self):
115
node = factory.make_node()
116
token = NodeKey.objects.get_token_for_node(node)
117
request = self.fake_request(
118
HTTP_AUTHORIZATION=factory.make_oauth_header(
119
oauth_token=token.key))
120
self.assertEqual(node, get_queried_node(request))
123
class TestViews(DjangoTestCase, ProvisioningFakeFactory):
90
124
"""Tests for the API views."""
92
126
def make_node_client(self, node=None):
96
130
token = NodeKey.objects.get_token_for_node(node)
97
131
return OAuthAuthenticatedClient(get_node_init_user(), token)
99
def make_url(self, path):
100
"""Create an absolute URL for `path` on the metadata API.
102
:param path: Path within the metadata API to access. Should start
104
:return: An absolute URL for the given path within the metadata
107
assert path.startswith('/'), "Give absolute metadata API path."
108
# Root of the metadata API service.
109
metadata_root = "/metadata"
110
return metadata_root + path
112
def get(self, path, client=None):
113
"""GET a resource from the metadata API.
115
:param path: Path within the metadata API to access. Should start
117
:param token: If given, authenticate the request using this token.
118
:type token: oauth.oauth.OAuthToken
119
:return: A response to the GET request.
120
:rtype: django.http.HttpResponse
124
return client.get(self.make_url(path))
126
133
def call_signal(self, client=None, version='latest', files={}, **kwargs):
127
134
"""Call the API's signal method.
145
152
for name, content in files.items():
146
153
params[name] = BytesIO(content)
147
154
params[name].name = name
148
return client.post(self.make_url('/%s/' % version), params)
155
url = reverse('metadata-version', args=[version])
156
return client.post(url, params)
150
158
def test_no_anonymous_access(self):
151
self.assertEqual(httplib.UNAUTHORIZED, self.get('/').status_code)
160
httplib.UNAUTHORIZED,
161
self.client.get(reverse('metadata')).status_code)
153
163
def test_metadata_index_shows_latest(self):
154
164
client = self.make_node_client()
155
self.assertIn('latest', self.get('/', client).content)
165
self.assertIn('latest', client.get(reverse('metadata')).content)
157
167
def test_metadata_index_shows_only_known_versions(self):
158
168
client = self.make_node_client()
159
for item in self.get('/', client).content.splitlines():
169
for item in client.get(reverse('metadata')).content.splitlines():
160
170
check_version(item)
161
171
# The test is that we get here without exception.
164
174
def test_version_index_shows_meta_data(self):
165
175
client = self.make_node_client()
166
items = self.get('/latest/', client).content.splitlines()
176
url = reverse('metadata-version', args=['latest'])
177
items = client.get(url).content.splitlines()
167
178
self.assertIn('meta-data', items)
169
180
def test_version_index_does_not_show_user_data_if_not_available(self):
170
181
client = self.make_node_client()
171
items = self.get('/latest/', client).content.splitlines()
182
url = reverse('metadata-version', args=['latest'])
183
items = client.get(url).content.splitlines()
172
184
self.assertNotIn('user-data', items)
174
186
def test_version_index_shows_user_data_if_available(self):
175
187
node = factory.make_node()
176
188
NodeUserData.objects.set_user_data(node, b"User data for node")
177
189
client = self.make_node_client(node)
178
items = self.get('/latest/', client).content.splitlines()
190
url = reverse('metadata-version', args=['latest'])
191
items = client.get(url).content.splitlines()
179
192
self.assertIn('user-data', items)
181
194
def test_meta_data_view_lists_fields(self):
183
196
user, _ = factory.make_user_with_keys(n_keys=2, username='my-user')
184
197
node = factory.make_node(owner=user)
185
198
client = self.make_node_client(node=node)
186
response = self.get('/latest/meta-data/', client)
199
url = reverse('metadata-meta-data', args=['latest', ''])
200
response = client.get(url)
187
201
self.assertIn('text/plain', response['Content-Type'])
188
202
self.assertItemsEqual(
189
203
MetaDataHandler.fields, response.content.split())
191
205
def test_meta_data_view_is_sorted(self):
192
206
client = self.make_node_client()
193
response = self.get('/latest/meta-data/', client)
207
url = reverse('metadata-meta-data', args=['latest', ''])
208
response = client.get(url)
194
209
attributes = response.content.split()
195
210
self.assertEqual(sorted(attributes), attributes)
197
212
def test_meta_data_unknown_item_is_not_found(self):
198
213
client = self.make_node_client()
199
response = self.get('/latest/meta-data/UNKNOWN-ITEM-HA-HA-HA', client)
214
url = reverse('metadata-meta-data', args=['latest', 'UNKNOWN-ITEM'])
215
response = client.get(url)
200
216
self.assertEqual(httplib.NOT_FOUND, response.status_code)
202
218
def test_get_attribute_producer_supports_all_fields(self):
227
245
node = factory.make_node()
228
246
NodeUserData.objects.set_user_data(node, data)
229
247
client = self.make_node_client(node)
230
response = self.get('/latest/user-data', client)
248
response = client.get(reverse('metadata-user-data', args=['latest']))
231
249
self.assertEqual('application/octet-stream', response['Content-Type'])
232
250
self.assertIsInstance(response.content, str)
233
251
self.assertEqual(
234
252
(httplib.OK, data), (response.status_code, response.content))
236
254
def test_user_data_for_node_without_user_data_returns_not_found(self):
237
response = self.get('/latest/user-data', self.make_node_client())
255
client = self.make_node_client()
256
response = client.get(reverse('metadata-user-data', args=['latest']))
238
257
self.assertEqual(httplib.NOT_FOUND, response.status_code)
240
259
def test_public_keys_not_listed_for_node_without_public_keys(self):
241
response = self.get('/latest/meta-data/', self.make_node_client())
260
url = reverse('metadata-meta-data', args=['latest', ''])
261
client = self.make_node_client()
262
response = client.get(url)
242
263
self.assertNotIn(
243
264
'public-keys', response.content.decode('ascii').split('\n'))
245
266
def test_public_keys_listed_for_node_with_public_keys(self):
246
267
user, _ = factory.make_user_with_keys(n_keys=2, username='my-user')
247
268
node = factory.make_node(owner=user)
249
'/latest/meta-data/', self.make_node_client(node=node))
269
url = reverse('metadata-meta-data', args=['latest', ''])
270
client = self.make_node_client(node=node)
271
response = client.get(url)
251
273
'public-keys', response.content.decode('ascii').split('\n'))
253
275
def test_public_keys_for_node_without_public_keys_returns_not_found(self):
255
'/latest/meta-data/public-keys', self.make_node_client())
276
url = reverse('metadata-meta-data', args=['latest', 'public-keys'])
277
client = self.make_node_client()
278
response = client.get(url)
256
279
self.assertEqual(httplib.NOT_FOUND, response.status_code)
258
281
def test_public_keys_for_node_returns_list_of_keys(self):
259
282
user, _ = factory.make_user_with_keys(n_keys=2, username='my-user')
260
283
node = factory.make_node(owner=user)
262
'/latest/meta-data/public-keys', self.make_node_client(node=node))
284
url = reverse('metadata-meta-data', args=['latest', 'public-keys'])
285
client = self.make_node_client(node=node)
286
response = client.get(url)
263
287
self.assertEqual(httplib.OK, response.status_code)
264
288
keys = SSHKey.objects.filter(user=user).values_list('key', flat=True)
265
289
expected_response = '\n'.join(keys)
470
495
stored_data = NodeCommissionResult.objects.get_data(
471
496
node, 'output.txt')
472
497
self.assertEqual(size_limit, len(stored_data))
499
def test_api_retrieves_node_metadata_by_mac(self):
500
mac = factory.make_mac_address()
502
'metadata-meta-data-by-mac',
503
args=['latest', mac.mac_address, 'instance-id'])
504
response = self.client.get(url)
506
(httplib.OK, mac.node.system_id),
507
(response.status_code, response.content))
509
def test_api_retrieves_node_userdata_by_mac(self):
510
mac = factory.make_mac_address()
511
user_data = factory.getRandomString().encode('ascii')
512
NodeUserData.objects.set_user_data(mac.node, user_data)
514
'metadata-user-data-by-mac', args=['latest', mac.mac_address])
515
response = self.client.get(url)
517
(httplib.OK, user_data),
518
(response.status_code, response.content))
520
def test_api_normally_disallows_anonymous_node_metadata_access(self):
521
self.patch(settings, 'ALLOW_UNSAFE_METADATA_ACCESS', False)
522
mac = factory.make_mac_address()
524
'metadata-meta-data-by-mac',
525
args=['latest', mac.mac_address, 'instance-id'])
526
response = self.client.get(url)
527
self.assertEqual(httplib.FORBIDDEN, response.status_code)
529
def test_netboot_off(self):
530
node = factory.make_node(netboot=True)
531
client = self.make_node_client(node=node)
532
url = reverse('metadata-version', args=['latest'])
533
response = client.post(url, {'op': 'netboot_off'})
534
node = reload_object(node)
535
self.assertFalse(node.netboot, response)
537
def test_netboot_on(self):
538
node = factory.make_node(netboot=False)
539
client = self.make_node_client(node=node)
540
url = reverse('metadata-version', args=['latest'])
541
response = client.post(url, {'op': 'netboot_on'})
542
node = reload_object(node)
543
self.assertTrue(node.netboot, response)
545
def test_anonymous_netboot_off(self):
546
node = factory.make_node(netboot=True)
547
anon_netboot_off_url = reverse(
548
'metadata-node-by-id', args=['latest', node.system_id])
549
response = self.client.post(
550
anon_netboot_off_url, {'op': 'netboot_off'})
551
node = reload_object(node)
554
(response.status_code, node.netboot),
557
def test_anonymous_get_enlist_preseed(self):
558
# The preseed for enlistment can be obtained anonymously.
559
anon_enlist_preseed_url = reverse(
560
'metadata-enlist-preseed', args=['latest'])
561
# Fake the preseed so we're just exercising the view.
562
fake_preseed = factory.getRandomString()
563
self.patch(api, "get_enlist_preseed", lambda: fake_preseed)
564
response = self.client.get(
565
anon_enlist_preseed_url, {'op': 'get_enlist_preseed'})
570
(response.status_code,
571
response["Content-Type"],
575
def test_anonymous_get_preseed(self):
576
# The preseed for a node can be obtained anonymously.
577
node = factory.make_node()
578
anon_node_url = reverse(
579
'metadata-node-by-id',
580
args=['latest', node.system_id])
581
# Fake the preseed so we're just exercising the view.
582
fake_preseed = factory.getRandomString()
583
self.patch(api, "get_preseed", lambda node: fake_preseed)
584
response = self.client.get(
585
anon_node_url, {'op': 'get_preseed'})
590
(response.status_code,
591
response["Content-Type"],