~ubuntu-branches/ubuntu/utopic/maas/utopic-security

« back to all changes in this revision

Viewing changes to src/maasserver/api/tests/test_boot_resources.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez, Jeroen Vermeulen, Andres Rodriguez, Jason Hobbs, Raphaël Badin, Louis Bouchard, Gavin Panella
  • Date: 2014-08-21 19:36:30 UTC
  • mfrom: (1.3.1)
  • Revision ID: package-import@ubuntu.com-20140821193630-kertpu5hd8yyss8h
Tags: 1.7.0~beta7+bzr3266-0ubuntu1
* New Upstream Snapshot, Beta 7 bzr3266

[ Jeroen Vermeulen ]
* debian/extras/99-maas-sudoers
  debian/maas-dhcp.postinst
  debian/rules
  - Add second DHCP server instance for IPv6.
* debian/maas-region-controller-min.install
  debian/maas-region-controller-min.lintian-overrides
  - Install deployment user-data: maas_configure_interfaces.py script.
* debian/maas-cluster-controller.links
  debian/maas-cluster-controller.install
  debian/maas-cluster-controller.postinst
  - Reflect Celery removal changes made in trunk r3067.
  - Don't install celeryconfig_cluster.py any longer. 
  - Don't install maas_local_celeryconfig_cluster.py any longer.
  - Don't symlink maas_local_celeryconfig_cluster.py from /etc to /usr.
  - Don't insert UUID into maas_local_celeryconfig_cluster.py.

[ Andres Rodriguez ]
* debian/maas-region-controller-min.postrm: Cleanup lefover files.
* debian/maas-dhcp.postrm: Clean leftover configs.
* Provide new maas-proxy package that replaces the usage of
  squid-deb-proxy:
  - debian/control: New maas-proxy package that replaces the usage
    of squid-deb-proxy; Drop depends on squid-deb-proxy.
  - Add upstrart job.
  - Ensure squid3 is stopped as maas-proxy uses a caching proxy.
* Remove Celery references to cluster controller:
  - Rename upstart job from maas-pserv to maas-cluster; rename
    maas-cluster-celery to maas-cluster-register. Ensure services
    are stopped on upgrade.
  - debian/maintscript: Cleanup config files.
  - Remove all references to the MAAS celery daemon and config
    files as we don't use it like that anymore
* Move some entries in debian/maintscript to
  debian/maas-cluster-controller.maintscript
* Remove usage of txlongpoll and rabbitmq-server. Handle upgrades
  to ensure these are removed correctly.

[ Jason Hobbs ]
* debian/maas-region-controller-min.install: Install
  maas-generate-winrm-cert script.

[ Raphaël Badin ]
* debian/extras/maas-region-admin: Bypass django-admin as it prints
  spurious messages to stdout (LP: #1365130).

[Louis Bouchard]
* debian/maas-cluster-controller.postinst:
  - Exclude /var/log/maas/rsyslog when changing ownership
    (LP: #1346703)

[Gavin Panella]
* debian/maas-cluster-controller.maas-clusterd.upstart:
  - Don't start-up the cluster controller unless a shared-secret has
    been installed.
* debian/maas-cluster-controller.maas-cluster-register.upstart: Drop.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Tests for the `Boot Resources` API."""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
str = None
 
13
 
 
14
__metaclass__ = type
 
15
__all__ = []
 
16
 
 
17
import httplib
 
18
import json
 
19
import random
 
20
 
 
21
from django.core.urlresolvers import reverse
 
22
from maasserver.api import boot_resources
 
23
from maasserver.api.boot_resources import (
 
24
    boot_resource_file_to_dict,
 
25
    boot_resource_set_to_dict,
 
26
    boot_resource_to_dict,
 
27
    )
 
28
from maasserver.enum import (
 
29
    BOOT_RESOURCE_FILE_TYPE,
 
30
    BOOT_RESOURCE_TYPE,
 
31
    BOOT_RESOURCE_TYPE_CHOICES_DICT,
 
32
    )
 
33
from maasserver.fields import LargeObjectFile
 
34
from maasserver.models import (
 
35
    BootResource,
 
36
    LargeFile,
 
37
    )
 
38
from maasserver.testing.api import APITestCase
 
39
from maasserver.testing.architecture import make_usable_architecture
 
40
from maasserver.testing.factory import factory
 
41
from maasserver.testing.orm import reload_object
 
42
from maasserver.testing.testcase import MAASServerTestCase
 
43
from maastesting.matchers import MockCalledOnceWith
 
44
from maastesting.utils import sample_binary_data
 
45
from mock import MagicMock
 
46
from testtools.matchers import ContainsAll
 
47
 
 
48
 
 
49
def get_boot_resource_uri(resource):
 
50
    """Return a boot resource's URI on the API."""
 
51
    return reverse(
 
52
        'boot_resource_handler',
 
53
        args=[resource.id])
 
54
 
 
55
 
 
56
class TestHelpers(MAASServerTestCase):
 
57
 
 
58
    def test_boot_resource_file_to_dict(self):
 
59
        size = random.randint(512, 1023)
 
60
        total_size = random.randint(1024, 2048)
 
61
        content = factory.make_string(size)
 
62
        largefile = factory.make_LargeFile(content=content, size=total_size)
 
63
        resource = factory.make_BootResource(
 
64
            rtype=BOOT_RESOURCE_TYPE.UPLOADED)
 
65
        resource_set = factory.make_BootResourceSet(resource)
 
66
        rfile = factory.make_BootResourceFile(resource_set, largefile)
 
67
        dict_representation = boot_resource_file_to_dict(rfile)
 
68
        self.assertEqual(rfile.filename, dict_representation['filename'])
 
69
        self.assertEqual(rfile.filetype, dict_representation['filetype'])
 
70
        self.assertEqual(rfile.largefile.sha256, dict_representation['sha256'])
 
71
        self.assertEqual(total_size, dict_representation['size'])
 
72
        self.assertEqual(False, dict_representation['complete'])
 
73
        self.assertEqual(
 
74
            rfile.largefile.progress, dict_representation['progress'])
 
75
        self.assertEqual(
 
76
            reverse(
 
77
                'boot_resource_file_upload_handler',
 
78
                args=[resource.id, rfile.id]),
 
79
            dict_representation['upload_uri'])
 
80
 
 
81
    def test_boot_resource_set_to_dict(self):
 
82
        resource = factory.make_BootResource()
 
83
        resource_set = factory.make_BootResourceSet(resource)
 
84
        total_size = random.randint(1024, 2048)
 
85
        content = factory.make_string(random.randint(512, 1023))
 
86
        largefile = factory.make_LargeFile(content=content, size=total_size)
 
87
        rfile = factory.make_BootResourceFile(resource_set, largefile)
 
88
        dict_representation = boot_resource_set_to_dict(resource_set)
 
89
        self.assertEqual(resource_set.version, dict_representation['version'])
 
90
        self.assertEqual(resource_set.label, dict_representation['label'])
 
91
        self.assertEqual(resource_set.total_size, dict_representation['size'])
 
92
        self.assertEqual(False, dict_representation['complete'])
 
93
        self.assertEqual(
 
94
            resource_set.progress, dict_representation['progress'])
 
95
        self.assertItemsEqual(
 
96
            boot_resource_file_to_dict(rfile),
 
97
            dict_representation['files'][rfile.filename])
 
98
 
 
99
    def test_boot_resource_to_dict_without_sets(self):
 
100
        resource = factory.make_BootResource()
 
101
        factory.make_BootResourceSet(resource)
 
102
        dict_representation = boot_resource_to_dict(resource, with_sets=False)
 
103
        self.assertEqual(resource.id, dict_representation['id'])
 
104
        self.assertEqual(
 
105
            BOOT_RESOURCE_TYPE_CHOICES_DICT[resource.rtype],
 
106
            dict_representation['type'])
 
107
        self.assertEqual(resource.name, dict_representation['name'])
 
108
        self.assertEqual(
 
109
            resource.architecture, dict_representation['architecture'])
 
110
        self.assertEqual(
 
111
            get_boot_resource_uri(resource),
 
112
            dict_representation['resource_uri'])
 
113
        self.assertFalse('sets' in dict_representation)
 
114
 
 
115
    def test_boot_resource_to_dict_with_sets(self):
 
116
        resource = factory.make_BootResource()
 
117
        resource_set = factory.make_BootResourceSet(resource)
 
118
        dict_representation = boot_resource_to_dict(resource, with_sets=True)
 
119
        self.assertItemsEqual(
 
120
            boot_resource_set_to_dict(resource_set),
 
121
            dict_representation['sets'][resource_set.version])
 
122
 
 
123
 
 
124
class TestBootResourcesAPI(APITestCase):
 
125
    """Test the the boot resource API."""
 
126
 
 
127
    def test_handler_path(self):
 
128
        self.assertEqual(
 
129
            '/api/1.0/boot-resources/',
 
130
            reverse('boot_resources_handler'))
 
131
 
 
132
    def test_GET_returns_boot_resources_list(self):
 
133
        resources = [
 
134
            factory.make_BootResource() for _ in range(3)]
 
135
        response = self.client.get(
 
136
            reverse('boot_resources_handler'))
 
137
        self.assertEqual(httplib.OK, response.status_code, response.content)
 
138
        parsed_result = json.loads(response.content)
 
139
        self.assertItemsEqual(
 
140
            [resource.id for resource in resources],
 
141
            [resource.get('id') for resource in parsed_result])
 
142
 
 
143
    def test_GET_synced_returns_synced_boot_resources(self):
 
144
        resources = [
 
145
            factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.SYNCED)
 
146
            for _ in range(3)
 
147
            ]
 
148
        factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.GENERATED)
 
149
        factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED)
 
150
        response = self.client.get(
 
151
            reverse('boot_resources_handler'), {'type': 'synced'})
 
152
        self.assertEqual(httplib.OK, response.status_code, response.content)
 
153
        parsed_result = json.loads(response.content)
 
154
        self.assertItemsEqual(
 
155
            [resource.id for resource in resources],
 
156
            [resource.get('id') for resource in parsed_result])
 
157
 
 
158
    def test_GET_generated_returns_generated_boot_resources(self):
 
159
        resources = [
 
160
            factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.GENERATED)
 
161
            for _ in range(3)
 
162
            ]
 
163
        factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.SYNCED)
 
164
        factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED)
 
165
        response = self.client.get(
 
166
            reverse('boot_resources_handler'), {'type': 'generated'})
 
167
        self.assertEqual(httplib.OK, response.status_code, response.content)
 
168
        parsed_result = json.loads(response.content)
 
169
        self.assertItemsEqual(
 
170
            [resource.id for resource in resources],
 
171
            [resource.get('id') for resource in parsed_result])
 
172
 
 
173
    def test_GET_uploaded_returns_uploaded_boot_resources(self):
 
174
        resources = [
 
175
            factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED)
 
176
            for _ in range(3)
 
177
            ]
 
178
        factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.SYNCED)
 
179
        factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.GENERATED)
 
180
        response = self.client.get(
 
181
            reverse('boot_resources_handler'), {'type': 'uploaded'})
 
182
        self.assertEqual(httplib.OK, response.status_code, response.content)
 
183
        parsed_result = json.loads(response.content)
 
184
        self.assertItemsEqual(
 
185
            [resource.id for resource in resources],
 
186
            [resource.get('id') for resource in parsed_result])
 
187
 
 
188
    def test_GET_doesnt_include_full_definition_of_boot_resource(self):
 
189
        factory.make_BootResource()
 
190
        response = self.client.get(
 
191
            reverse('boot_resources_handler'))
 
192
        self.assertEqual(httplib.OK, response.status_code, response.content)
 
193
        parsed_result = json.loads(response.content)
 
194
        self.assertFalse('sets' in parsed_result[0])
 
195
 
 
196
    def test_POST_requires_admin(self):
 
197
        params = {
 
198
            'name': factory.make_name('name'),
 
199
            'architecture': make_usable_architecture(self),
 
200
            'content': (
 
201
                factory.make_file_upload(content=sample_binary_data)),
 
202
        }
 
203
        response = self.client.post(
 
204
            reverse('boot_resources_handler'), params)
 
205
        self.assertEqual(httplib.FORBIDDEN, response.status_code)
 
206
 
 
207
    def pick_filetype(self):
 
208
        upload_type = random.choice([
 
209
            'tgz', 'ddtgz'])
 
210
        if upload_type == 'tgz':
 
211
            filetype = BOOT_RESOURCE_FILE_TYPE.ROOT_TGZ
 
212
        elif upload_type == 'ddtgz':
 
213
            filetype = BOOT_RESOURCE_FILE_TYPE.ROOT_DD
 
214
        return upload_type, filetype
 
215
 
 
216
    def test_POST_creates_boot_resource(self):
 
217
        self.become_admin()
 
218
 
 
219
        name = factory.make_name('name')
 
220
        architecture = make_usable_architecture(self)
 
221
        upload_type, filetype = self.pick_filetype()
 
222
        params = {
 
223
            'name': name,
 
224
            'architecture': architecture,
 
225
            'filetype': upload_type,
 
226
            'content': (
 
227
                factory.make_file_upload(content=sample_binary_data)),
 
228
        }
 
229
        response = self.client.post(
 
230
            reverse('boot_resources_handler'), params)
 
231
        self.assertEqual(httplib.CREATED, response.status_code)
 
232
        parsed_result = json.loads(response.content)
 
233
 
 
234
        resource = BootResource.objects.get(id=parsed_result['id'])
 
235
        resource_set = resource.sets.first()
 
236
        rfile = resource_set.files.first()
 
237
        self.assertEqual(name, resource.name)
 
238
        self.assertEqual(architecture, resource.architecture)
 
239
        self.assertEqual('uploaded', resource_set.label)
 
240
        self.assertEqual(filetype, rfile.filename)
 
241
        self.assertEqual(filetype, rfile.filetype)
 
242
        with rfile.largefile.content.open('rb') as stream:
 
243
            written_data = stream.read()
 
244
        self.assertEqual(sample_binary_data, written_data)
 
245
 
 
246
    def test_POST_creates_boot_resource_with_default_filetype(self):
 
247
        self.become_admin()
 
248
 
 
249
        name = factory.make_name('name')
 
250
        architecture = make_usable_architecture(self)
 
251
        params = {
 
252
            'name': name,
 
253
            'architecture': architecture,
 
254
            'content': (
 
255
                factory.make_file_upload(content=sample_binary_data)),
 
256
        }
 
257
        response = self.client.post(
 
258
            reverse('boot_resources_handler'), params)
 
259
        self.assertEqual(httplib.CREATED, response.status_code)
 
260
        parsed_result = json.loads(response.content)
 
261
 
 
262
        resource = BootResource.objects.get(id=parsed_result['id'])
 
263
        resource_set = resource.sets.first()
 
264
        rfile = resource_set.files.first()
 
265
        self.assertEqual(BOOT_RESOURCE_FILE_TYPE.ROOT_TGZ, rfile.filetype)
 
266
 
 
267
    def test_POST_creates_boot_resource_with_already_existing_largefile(self):
 
268
        self.become_admin()
 
269
 
 
270
        largefile = factory.make_LargeFile()
 
271
        name = factory.make_name('name')
 
272
        architecture = make_usable_architecture(self)
 
273
        params = {
 
274
            'name': name,
 
275
            'architecture': architecture,
 
276
            'sha256': largefile.sha256,
 
277
            'size': largefile.total_size,
 
278
        }
 
279
        response = self.client.post(
 
280
            reverse('boot_resources_handler'), params)
 
281
        self.assertEqual(httplib.CREATED, response.status_code)
 
282
        parsed_result = json.loads(response.content)
 
283
 
 
284
        resource = BootResource.objects.get(id=parsed_result['id'])
 
285
        resource_set = resource.sets.first()
 
286
        rfile = resource_set.files.first()
 
287
        self.assertEqual(largefile, rfile.largefile)
 
288
 
 
289
    def test_POST_creates_boot_resource_with_empty_largefile(self):
 
290
        self.become_admin()
 
291
 
 
292
        # Create a largefile to get a random sha256 and size. We delete it
 
293
        # immediately so the new resource does not pick it up.
 
294
        largefile = factory.make_LargeFile()
 
295
        largefile.delete()
 
296
 
 
297
        name = factory.make_name('name')
 
298
        architecture = make_usable_architecture(self)
 
299
        params = {
 
300
            'name': name,
 
301
            'architecture': architecture,
 
302
            'sha256': largefile.sha256,
 
303
            'size': largefile.total_size,
 
304
        }
 
305
        response = self.client.post(
 
306
            reverse('boot_resources_handler'), params)
 
307
        self.assertEqual(httplib.CREATED, response.status_code)
 
308
        parsed_result = json.loads(response.content)
 
309
 
 
310
        resource = BootResource.objects.get(id=parsed_result['id'])
 
311
        resource_set = resource.sets.first()
 
312
        rfile = resource_set.files.first()
 
313
        self.assertEqual(
 
314
            (largefile.sha256, largefile.total_size, False),
 
315
            (rfile.largefile.sha256, rfile.largefile.total_size,
 
316
                rfile.largefile.complete))
 
317
 
 
318
    def test_POST_validates_size_matches_total_size_for_largefile(self):
 
319
        self.become_admin()
 
320
 
 
321
        largefile = factory.make_LargeFile()
 
322
        name = factory.make_name('name')
 
323
        architecture = make_usable_architecture(self)
 
324
        params = {
 
325
            'name': name,
 
326
            'architecture': architecture,
 
327
            'sha256': largefile.sha256,
 
328
            'size': largefile.total_size + 1,
 
329
        }
 
330
        response = self.client.post(
 
331
            reverse('boot_resources_handler'), params)
 
332
        self.assertEqual(httplib.BAD_REQUEST, response.status_code)
 
333
 
 
334
    def test_POST_returns_full_definition_of_boot_resource(self):
 
335
        self.become_admin()
 
336
 
 
337
        name = factory.make_name('name')
 
338
        architecture = make_usable_architecture(self)
 
339
        params = {
 
340
            'name': name,
 
341
            'architecture': architecture,
 
342
            'content': (
 
343
                factory.make_file_upload(content=sample_binary_data)),
 
344
        }
 
345
        response = self.client.post(
 
346
            reverse('boot_resources_handler'), params)
 
347
        self.assertEqual(httplib.CREATED, response.status_code)
 
348
        parsed_result = json.loads(response.content)
 
349
        self.assertTrue('sets' in parsed_result)
 
350
 
 
351
    def test_POST_validates_boot_resource(self):
 
352
        self.become_admin()
 
353
 
 
354
        params = {
 
355
            'name': factory.make_name('name'),
 
356
        }
 
357
        response = self.client.post(
 
358
            reverse('boot_resources_handler'), params)
 
359
        self.assertEqual(httplib.BAD_REQUEST, response.status_code)
 
360
 
 
361
    def test_POST_calls_import_boot_images_on_all_clusters(self):
 
362
        self.become_admin()
 
363
 
 
364
        nodegroup = MagicMock()
 
365
        self.patch(boot_resources, 'NodeGroup', nodegroup)
 
366
 
 
367
        name = factory.make_name('name')
 
368
        architecture = make_usable_architecture(self)
 
369
        params = {
 
370
            'name': name,
 
371
            'architecture': architecture,
 
372
            'content': (
 
373
                factory.make_file_upload(content=sample_binary_data)),
 
374
        }
 
375
        response = self.client.post(
 
376
            reverse('boot_resources_handler'), params)
 
377
        self.assertEqual(httplib.CREATED, response.status_code)
 
378
        self.assertThat(
 
379
            nodegroup.objects.import_boot_images_on_accepted_clusters,
 
380
            MockCalledOnceWith())
 
381
 
 
382
    def test_import_requires_admin(self):
 
383
        response = self.client.post(
 
384
            reverse('boot_resources_handler'), {'op': 'import'})
 
385
        self.assertEqual(httplib.FORBIDDEN, response.status_code)
 
386
 
 
387
 
 
388
class TestBootResourceAPI(APITestCase):
 
389
 
 
390
    def test_handler_path(self):
 
391
        self.assertEqual(
 
392
            '/api/1.0/boot-resources/3/',
 
393
            reverse('boot_resource_handler', args=['3']))
 
394
 
 
395
    def test_GET_returns_boot_resource(self):
 
396
        resource = factory.make_usable_boot_resource()
 
397
        response = self.client.get(get_boot_resource_uri(resource))
 
398
        self.assertEqual(httplib.OK, response.status_code)
 
399
        returned_resource = json.loads(response.content)
 
400
        # The returned object contains a 'resource_uri' field.
 
401
        self.assertEqual(
 
402
            reverse(
 
403
                'boot_resource_handler',
 
404
                args=[resource.id]
 
405
            ),
 
406
            returned_resource['resource_uri'])
 
407
        self.assertThat(
 
408
            returned_resource,
 
409
            ContainsAll(['id', 'type', 'name', 'architecture']))
 
410
 
 
411
    def test_DELETE_deletes_boot_resource(self):
 
412
        self.become_admin()
 
413
        resource = factory.make_BootResource()
 
414
        response = self.client.delete(get_boot_resource_uri(resource))
 
415
        self.assertEqual(httplib.NO_CONTENT, response.status_code)
 
416
        self.assertIsNone(reload_object(resource))
 
417
 
 
418
    def test_DELETE_requires_admin(self):
 
419
        resource = factory.make_BootResource()
 
420
        response = self.client.delete(get_boot_resource_uri(resource))
 
421
        self.assertEqual(httplib.FORBIDDEN, response.status_code)
 
422
 
 
423
 
 
424
class TestBootResourceFileUploadAPI(APITestCase):
 
425
 
 
426
    def get_boot_resource_file_upload_uri(self, rfile):
 
427
        """Return a boot resource file's URI on the API."""
 
428
        return reverse(
 
429
            'boot_resource_file_upload_handler',
 
430
            args=[rfile.resource_set.resource.id, rfile.id])
 
431
 
 
432
    def make_empty_resource_file(self, rtype=None, content=None):
 
433
        # Create a largefile to use the generated content,
 
434
        # sha256, and total_size.
 
435
        if content is None:
 
436
            content = factory.make_bytes(1024)
 
437
        total_size = len(content)
 
438
        largefile = factory.make_LargeFile(content=content, size=total_size)
 
439
        sha256 = largefile.sha256
 
440
        with largefile.content.open('rb') as stream:
 
441
            content = stream.read()
 
442
        largefile.delete()
 
443
 
 
444
        # Empty largefile
 
445
        largeobject = LargeObjectFile()
 
446
        largeobject.open().close()
 
447
        largefile = LargeFile.objects.create(
 
448
            sha256=sha256, total_size=total_size, content=largeobject)
 
449
 
 
450
        if rtype is None:
 
451
            rtype = BOOT_RESOURCE_TYPE.UPLOADED
 
452
        resource = factory.make_BootResource(rtype=rtype)
 
453
        resource_set = factory.make_BootResourceSet(resource)
 
454
        rfile = factory.make_BootResourceFile(resource_set, largefile)
 
455
        return rfile, content
 
456
 
 
457
    def read_content(self, rfile):
 
458
        """Return the content saved in resource file."""
 
459
        with rfile.largefile.content.open('rb') as stream:
 
460
            return stream.read()
 
461
 
 
462
    def test_handler_path(self):
 
463
        self.assertEqual(
 
464
            '/api/1.0/boot-resources/3/upload/5/',
 
465
            reverse('boot_resource_file_upload_handler', args=['3', '5']))
 
466
 
 
467
    def test_PUT_resource_file_writes_content(self):
 
468
        self.become_admin()
 
469
        rfile, content = self.make_empty_resource_file()
 
470
        response = self.client.put(
 
471
            self.get_boot_resource_file_upload_uri(rfile), data=content)
 
472
        self.assertEqual(httplib.OK, response.status_code, response.content)
 
473
        self.assertEqual(content, self.read_content(rfile))
 
474
 
 
475
    def test_PUT_requires_admin(self):
 
476
        rfile, content = self.make_empty_resource_file()
 
477
        response = self.client.put(
 
478
            self.get_boot_resource_file_upload_uri(rfile), data=content)
 
479
        self.assertEqual(
 
480
            httplib.FORBIDDEN, response.status_code, response.content)
 
481
 
 
482
    def test_PUT_returns_bad_request_when_no_content(self):
 
483
        self.become_admin()
 
484
        rfile, _ = self.make_empty_resource_file()
 
485
        response = self.client.put(
 
486
            self.get_boot_resource_file_upload_uri(rfile))
 
487
        self.assertEqual(
 
488
            httplib.BAD_REQUEST, response.status_code, response.content)
 
489
 
 
490
    def test_PUT_returns_forbidden_when_resource_is_synced(self):
 
491
        self.become_admin()
 
492
        rfile, content = self.make_empty_resource_file(
 
493
            BOOT_RESOURCE_TYPE.SYNCED)
 
494
        response = self.client.put(
 
495
            self.get_boot_resource_file_upload_uri(rfile), data=content)
 
496
        self.assertEqual(
 
497
            httplib.FORBIDDEN, response.status_code, response.content)
 
498
 
 
499
    def test_PUT_returns_bad_request_when_resource_file_is_complete(self):
 
500
        self.become_admin()
 
501
        rfile, content = self.make_empty_resource_file()
 
502
        with rfile.largefile.content.open('wb') as stream:
 
503
            stream.write(content)
 
504
 
 
505
        response = self.client.put(
 
506
            self.get_boot_resource_file_upload_uri(rfile), data=content)
 
507
        self.assertEqual(
 
508
            httplib.BAD_REQUEST, response.status_code, response.content)
 
509
 
 
510
    def test_PUT_returns_bad_request_when_content_is_too_large(self):
 
511
        self.become_admin()
 
512
        rfile, content = self.make_empty_resource_file()
 
513
        content = factory.make_bytes(len(content) + 1)
 
514
        response = self.client.put(
 
515
            self.get_boot_resource_file_upload_uri(rfile), data=content)
 
516
        self.assertEqual(
 
517
            httplib.BAD_REQUEST, response.status_code, response.content)
 
518
 
 
519
    def test_PUT_returns_bad_request_when_content_doesnt_match_sha256(self):
 
520
        self.become_admin()
 
521
        rfile, content = self.make_empty_resource_file()
 
522
        content = factory.make_bytes(size=len(content))
 
523
        response = self.client.put(
 
524
            self.get_boot_resource_file_upload_uri(rfile), data=content)
 
525
        self.assertEqual(
 
526
            httplib.BAD_REQUEST, response.status_code, response.content)
 
527
 
 
528
    def test_PUT_on_complete_calls_clusters_to_import_boot_images(self):
 
529
        self.become_admin()
 
530
        nodegroup = MagicMock()
 
531
        self.patch(boot_resources, 'NodeGroup', nodegroup)
 
532
 
 
533
        rfile, content = self.make_empty_resource_file()
 
534
        response = self.client.put(
 
535
            self.get_boot_resource_file_upload_uri(rfile), data=content)
 
536
        self.assertEqual(
 
537
            httplib.OK, response.status_code, response.content)
 
538
        self.assertThat(
 
539
            nodegroup.objects.import_boot_images_on_accepted_clusters,
 
540
            MockCalledOnceWith())
 
541
 
 
542
    def test_PUT_with_multiple_requests_and_large_content(self):
 
543
        self.become_admin()
 
544
 
 
545
        # Get large amount of data to test with
 
546
        content = factory.make_bytes(1 << 24)  # 16MB
 
547
        rfile, _ = self.make_empty_resource_file(content=content)
 
548
        split_content = [
 
549
            content[i:i + (1 << 22)]
 
550
            for i in range(0, len(content), 1 << 22)  # Loop a total of 4 times
 
551
            ]
 
552
 
 
553
        for send_content in split_content:
 
554
            response = self.client.put(
 
555
                self.get_boot_resource_file_upload_uri(rfile),
 
556
                data=send_content)
 
557
            self.assertEqual(
 
558
                httplib.OK, response.status_code, response.content)
 
559
        self.assertEqual(content, self.read_content(rfile))