Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / tests.py @ ce55f211

History | View | Annotate | Download (42.9 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from __future__ import with_statement
35

    
36
from collections import defaultdict
37
from email.utils import parsedate
38
from random import choice, randint, sample
39
from time import mktime
40

    
41
from django.conf import settings
42
from django.utils import simplejson as json
43
from django.test import TestCase
44
from django.test.client import Client
45

    
46
from synnefo.db.models import *
47
from synnefo.logic.utils import get_rsapi_state
48

    
49
import mock
50

    
51
class AaiClient(Client):
52
    def request(self, **request):
53
        request['HTTP_X_AUTH_TOKEN'] = '0000'
54
        return super(AaiClient, self).request(**request)
55

    
56

    
57
class TestQuota(TestCase):
58

    
59
    fixtures = ['users', 'flavors']
60
    astakos_response_user = 'test'
61

    
62
    def setUp(self):
63

    
64
        self.astakos_response_user = 'test'
65
        def get_user_mock(request, *Args, **kwargs):
66
            if request.META.get('HTTP_X_AUTH_TOKEN', None) == '0000':
67
                request.user_uniq = self.astakos_response_user
68
                request.user = {'uniq': self.astakos_response_user}
69

    
70
        def get_image_mock(request, *Args, **kwargs):
71
            return {'backend_id':'1234', 'location':'pithos://dummyimage',
72
                    'disk_format': 'plain'}
73

    
74
        self.rapi_patch = mock.patch('synnefo.logic.backend.rapi')
75
        self.rapi_mock = self.rapi_patch.start()
76

    
77
        self.pithos_patch = mock.patch('synnefo.api.util.get_image')
78
        self.pithos_mock = self.rapi_patch.start()
79

    
80
        # mock the astakos authentication function
81
        from synnefo.api import util
82
        util.get_user = get_user_mock
83
        util.get_image = get_image_mock
84

    
85
        settings.SKIP_SSH_VALIDATION = True
86
        self.client = AaiClient()
87
        self.user = 'test'
88

    
89
    def test_vms_quota(self):
90
        request = {
91
                    "server": {
92
                        "name": "new-server-test",
93
                        "userid": "test",
94
                        "imageRef": 1,
95
                        "flavorRef": 1,
96
                        "metadata": {
97
                            "My Server Name": "Apache1"
98
                        },
99
                        "personality": []
100
                    }
101
        }
102

    
103
        def create_server(for_user='test'):
104
            self.astakos_response_user = for_user
105
            return self.client.post('/api/v1.1/servers', json.dumps(request),
106
                content_type="application/json")
107

    
108
        def user_vms_count(user='test'):
109
            return VirtualMachine.objects.filter(userid=user).count()
110

    
111
        # admin sets max vms per user to 2
112
        settings.MAX_VMS_PER_USER = 2
113
        create_server()
114
        create_server()
115
        self.assertEqual(user_vms_count(), 2)
116

    
117
        # third creation fails
118
        resp = create_server()
119
        self.assertEqual(resp.status_code, 413)
120
        self.assertEqual(user_vms_count(), 2)
121

    
122
        # setting changed, no additional servers can get created
123
        settings.MAX_VMS_PER_USER = 1
124
        resp = create_server()
125
        self.assertEqual(resp.status_code, 413)
126
        self.assertEqual(user_vms_count(), 2)
127

    
128
        # admin wants test user to create 4 vms, now test user can create
129
        # one additional vm, but no more
130
        settings.VMS_USER_QUOTA = {'test':3}
131
        create_server()
132
        self.assertEqual(user_vms_count(), 3)
133
        resp = create_server()
134
        self.assertEqual(resp.status_code, 413)
135
        self.assertEqual(user_vms_count(), 3)
136
        # other users still apply to the global quota
137
        create_server("testuser2")
138
        self.assertEqual(user_vms_count("testuser2"), 1)
139
        resp = create_server("testuser2")
140
        self.assertEqual(resp.status_code, 413)
141
        self.assertEqual(user_vms_count("testuser2"), 1)
142

    
143

    
144
    def test_networks_quota(self):
145

    
146
        def create_network(for_user='test'):
147
            request = json.dumps({'network': {'name': 'user %s network' %
148
                for_user}})
149
            self.astakos_response_user = for_user
150
            return self.client.post('/api/v1.1/networks', request,
151
                content_type="application/json")
152

    
153
        def user_networks_count(user='test'):
154
            return Network.objects.filter(userid=user).count()
155

    
156
        settings.MAX_NETWORKS_PER_USER = 1
157
        create_network()
158
        self.assertEqual(user_networks_count(),1)
159
        resp = create_network()
160
        self.assertEqual(resp.status_code, 413)
161
        self.assertEqual(user_networks_count(), 1)
162

    
163
        settings.NETWORKS_USER_QUOTA = {'test':2}
164
        create_network()
165
        self.assertEqual(user_networks_count(),2)
166
        resp = create_network()
167
        self.assertEqual(resp.status_code, 413)
168
        self.assertEqual(user_networks_count(), 2)
169

    
170
        create_network("testuser2")
171
        self.assertEqual(user_networks_count("testuser2"),1)
172
        resp = create_network("testuser2")
173
        self.assertEqual(resp.status_code, 413)
174
        self.assertEqual(user_networks_count("testuser2"), 1)
175

    
176
        settings.GANETI_MAX_LINK_NUMBER = 3
177
        settings.NETWORKS_USER_QUOTA = {'test':10}
178
        resp = create_network()
179
        self.assertEqual(Network.objects.count(), 4)
180
        self.assertEqual('No networks available.' in resp.content, True)
181
        self.assertEqual(user_networks_count(), 2)
182

    
183

    
184

    
185

    
186
class APITestCase(TestCase):
187
    fixtures = ['users', 'api_test_data']
188
    test_server_id = 1001
189
    test_image_id = 1
190
    test_flavor_id = 1
191
    test_group_id = 1
192
    test_wrong_server_id = 99999999
193
    test_wrong_image_id = 99999999
194
    test_wrong_flavor_id = 99999999
195
    test_wrong_group_id = 99999999
196
    #make the testing with these id's
197

    
198
    def setUp(self):
199
        self.client = AaiClient()
200
        settings.MAX_VMS_PER_USER = 5
201

    
202
    def test_api_version(self):
203
        """Check API version."""
204

    
205
        response = self.client.get('/api/v1.1/')
206
        self.assertEqual(response.status_code, 200)
207
        api_version = json.loads(response.content)['version']
208
        self.assertEqual(api_version['id'], 'v1.1')
209
        self.assertEqual(api_version['status'], 'CURRENT')
210

    
211
    def test_server_list(self):
212
        """Test if the expected list of servers is returned."""
213

    
214
        response = self.client.get('/api/v1.1/servers')
215
        vms_from_api = json.loads(response.content)['servers']['values']
216
        vms_from_db = VirtualMachine.objects.filter(deleted=False)
217
        self.assertEqual(len(vms_from_api), len(vms_from_db))
218
        self.assertTrue(response.status_code in [200, 203])
219
        for vm_from_api in vms_from_api:
220
            vm_from_db = VirtualMachine.objects.get(id=vm_from_api['id'])
221
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
222
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
223

    
224
    def test_server_details(self):
225
        """Test if the expected server is returned."""
226

    
227
        response = self.client.get('/api/v1.1/servers/%d' % self.test_server_id)
228
        vm_from_api = json.loads(response.content)['server']
229
        vm_from_db = VirtualMachine.objects.get(id=self.test_server_id)
230
        self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
231
        self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
232
        self.assertEqual(vm_from_api['id'], vm_from_db.id)
233
        self.assertEqual(vm_from_api['imageRef'], vm_from_db.imageid)
234
        self.assertEqual(vm_from_api['name'], vm_from_db.name)
235
        self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
236
        self.assertTrue(response.status_code in [200, 203])
237

    
238
    def test_servers_details(self):
239
        """Test if the servers details are returned."""
240

    
241
        response = self.client.get('/api/v1.1/servers/detail')
242

    
243
        # Make sure both DB and API responses are sorted by id,
244
        # to allow for 1-1 comparisons
245
        vms_from_db = VirtualMachine.objects.filter(deleted=False).order_by('id')
246
        vms_from_api = json.loads(response.content)['servers']['values']
247
        vms_from_api = sorted(vms_from_api, key=lambda vm: vm['id'])
248
        self.assertEqual(len(vms_from_db), len(vms_from_api))
249

    
250
        id_list = [vm.id for vm in vms_from_db]
251
        number = 0
252
        for vm_id in id_list:
253
            vm_from_api = vms_from_api[number]
254
            vm_from_db = VirtualMachine.objects.get(id=vm_id)
255
            self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
256
            self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
257
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
258
            self.assertEqual(vm_from_api['imageRef'], vm_from_db.imageid)
259
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
260
            self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
261
            number += 1
262
        for vm_from_api in vms_from_api:
263
            vm_from_db = VirtualMachine.objects.get(id=vm_from_api['id'])
264
            self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
265
            self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
266
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
267
            self.assertEqual(vm_from_api['imageRef'], vm_from_db.imageid)
268
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
269
            self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
270
        self.assertTrue(response.status_code in [200, 203])
271

    
272
    def test_wrong_server(self):
273
        """Test 404 response if server does not exist."""
274

    
275
        response = self.client.get('/api/v1.1/servers/%d' % self.test_wrong_server_id)
276
        self.assertEqual(response.status_code, 404)
277

    
278
    def test_create_server_empty(self):
279
        """Test if the create server call returns a 400 badRequest if
280
           no attributes are specified."""
281

    
282
        response = self.client.post('/api/v1.1/servers', {})
283
        self.assertEqual(response.status_code, 400)
284

    
285
    def test_create_server(self):
286
        """Test if the create server call returns the expected response
287
           if a valid request has been speficied."""
288

    
289
        request = {
290
                    "server": {
291
                        "name": "new-server-test",
292
                        "userid": "test",
293
                        "imageRef": 1,
294
                        "flavorRef": 1,
295
                        "metadata": {
296
                            "My Server Name": "Apache1"
297
                        },
298
                        "personality": []
299
                    }
300
        }
301
        response = self.client.post('/api/v1.1/servers', json.dumps(request),
302
                                    content_type='application/json')
303
        self.assertEqual(response.status_code, 202)
304
        #TODO: check response.content
305
        #TODO: check create server with wrong options (eg non existing flavor)
306

    
307
    def test_server_polling(self):
308
        """Test if the server polling works as expected."""
309

    
310
        response = self.client.get('/api/v1.1/servers/detail')
311
        vms_from_api_initial = json.loads(response.content)['servers']['values']
312
        ts = mktime(parsedate(response['Date']))
313
        since = datetime.datetime.fromtimestamp(ts).isoformat() + 'Z'
314
        response = self.client.get('/api/v1.1/servers/detail?changes-since=%s' % since)
315
        self.assertEqual(len(response.content), 0)
316

    
317
        #now create a machine. Then check if it is on the list
318
        request = {
319
                    "server": {
320
                        "name": "new-server-test",
321
                        "imageRef": 1,
322
                        "flavorRef": 1,
323
                        "metadata": {
324
                            "My Server Name": "Apache1"
325
                        },
326
                        "personality": []
327
                    }
328
        }
329

    
330
        path = '/api/v1.1/servers'
331
        response = self.client.post(path, json.dumps(request), content_type='application/json')
332
        self.assertEqual(response.status_code, 202)
333

    
334
        response = self.client.get('/api/v1.1/servers/detail?changes-since=%s' % since)
335
        self.assertEqual(response.status_code, 200)
336
        vms_from_api_after = json.loads(response.content)['servers']['values']
337
        #make sure the newly created server is included on the updated list
338
        self.assertEqual(len(vms_from_api_after), 1)
339

    
340
    def test_reboot_server(self):
341
        """Test if the specified server is rebooted."""
342
        request = {'reboot': {'type': 'HARD'}}
343
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
344
        response = self.client.post(path, json.dumps(request), content_type='application/json')
345
        self.assertEqual(response.status_code, 202)
346
        # server id that does not exist
347
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
348
        response = self.client.post(path, json.dumps(request), content_type='application/json')
349
        self.assertEqual(response.status_code, 404)
350

    
351
    def test_shutdown_server(self):
352
        """Test if the specified server is shutdown."""
353

    
354
        request = {'shutdown': {}}
355
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
356
        response = self.client.post(path, json.dumps(request), content_type='application/json')
357
        self.assertEqual(response.status_code, 202)
358
        # server id that does not exist
359
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
360
        response = self.client.post(path, json.dumps(request), content_type='application/json')
361
        self.assertEqual(response.status_code, 404)
362

    
363
    def test_start_server(self):
364
        """Test if the specified server is started."""
365

    
366
        request = {'start': {}}
367
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
368
        response = self.client.post(path, json.dumps(request), content_type='application/json')
369
        self.assertEqual(response.status_code, 202)
370
        # server id that does not exist
371
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
372
        response = self.client.post(path, json.dumps(request), content_type='application/json')
373
        self.assertEqual(response.status_code, 404)
374

    
375
    def test_delete_server(self):
376
        """Test if the specified server is deleted."""
377
        response = self.client.delete('/api/v1.1/servers/%d' % self.test_server_id)
378
        self.assertEqual(response.status_code, 204)
379
        # server id that does not exist
380
        response = self.client.delete('/api/v1.1/servers/%d' % self.test_wrong_server_id)
381
        self.assertEqual(response.status_code, 404)
382

    
383
    def test_flavor_list(self):
384
        """Test if the expected list of flavors is returned by."""
385

    
386
        response = self.client.get('/api/v1.1/flavors')
387
        flavors_from_api = json.loads(response.content)['flavors']['values']
388
        flavors_from_db = Flavor.objects.all()
389
        self.assertEqual(len(flavors_from_api), len(flavors_from_db))
390
        self.assertTrue(response.status_code in [200, 203])
391
        for flavor_from_api in flavors_from_api:
392
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
393
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
394
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
395

    
396
    def test_flavors_details(self):
397
        """Test if the flavors details are returned."""
398

    
399
        response = self.client.get('/api/v1.1/flavors/detail')
400
        flavors_from_db = Flavor.objects.all()
401
        flavors_from_api = json.loads(response.content)['flavors']['values']
402

    
403
        # Assert that all flavors in the db appear inthe API call result
404
        for i in range(0, len(flavors_from_db)):
405
            flavor_from_api = flavors_from_api[i]
406
            flavor_from_db = Flavor.objects.get(id=flavors_from_db[i].id)
407
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
408
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
409
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
410
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
411
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
412

    
413
        # Assert that all flavors returned by the API also exist in the db
414
        for flavor_from_api in flavors_from_api:
415
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
416
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
417
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
418
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
419
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
420
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
421

    
422
        # Check if we have the right status_code
423
        self.assertTrue(response.status_code in [200, 203])
424

    
425
    def test_flavor_details(self):
426
        """Test if the expected flavor is returned."""
427

    
428
        response = self.client.get('/api/v1.1/flavors/%d' % self.test_flavor_id)
429
        flavor_from_api = json.loads(response.content)['flavor']
430
        flavor_from_db = Flavor.objects.get(id=self.test_flavor_id)
431
        self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
432
        self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
433
        self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
434
        self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
435
        self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
436
        self.assertTrue(response.status_code in [200, 203])
437

    
438
    def test_wrong_flavor(self):
439
        """Test 404 result when requesting a flavor that does not exist."""
440

    
441
        response = self.client.get('/api/v1.1/flavors/%d' % self.test_wrong_flavor_id)
442
        self.assertTrue(response.status_code in [404, 503])
443

    
444
    def test_image_list(self):
445
        """Test if the expected list of images is returned by the API."""
446

    
447
        response = self.client.get('/api/v1.1/images')
448
        images_from_api = json.loads(response.content)['images']['values']
449
        images_from_db = Image.objects.all()
450
        self.assertEqual(len(images_from_api), len(images_from_db))
451
        self.assertTrue(response.status_code in [200, 203])
452
        for image_from_api in images_from_api:
453
            image_from_db = Image.objects.get(id=image_from_api['id'])
454
            self.assertEqual(image_from_api['id'], image_from_db.id)
455
            self.assertEqual(image_from_api['name'], image_from_db.name)
456

    
457
    def test_wrong_image(self):
458
        """Test 404 result if a non existent image is requested."""
459

    
460
        response = self.client.get('/api/v1.1/images/%d' % self.test_wrong_image_id)
461
        self.assertEqual(response.status_code, 404)
462

    
463
    def test_server_metadata(self):
464
        """Test server's metadata (add, edit)."""
465

    
466
        key = 'name'
467
        request = {'meta': {key: 'a fancy name'}}
468

    
469
        path = '/api/v1.1/servers/%d/meta/%s' % (self.test_server_id, key)
470
        response = self.client.put(path, json.dumps(request), content_type='application/json')
471
        self.assertEqual(response.status_code, 201)
472

    
473

    
474
def create_flavors(n=1):
475
    for i in range(n):
476
        Flavor.objects.create(
477
            cpu=randint(1, 4),
478
            ram=randint(1, 8) * 512,
479
            disk=randint(1, 40))
480

    
481

    
482
def create_images(n=1):
483
    for i in range(n):
484
        Image.objects.create(
485
            name='Image %d' % (i + 1),
486
            state='ACTIVE',
487
            owner='test')
488

    
489

    
490
def create_image_metadata(n=1):
491
    images = Image.objects.all()
492
    for i in range(n):
493
        ImageMetadata.objects.create(
494
            meta_key='Key%d' % (i + 1),
495
            meta_value='Value %d' % (i + 1),
496
            image=choice(images))
497

    
498

    
499
def create_servers(n=1):
500
    flavors = Flavor.objects.all()
501
    images = Image.objects.all()
502
    for i in range(n):
503
        VirtualMachine.objects.create(
504
            name='Server %d' % (i + 1),
505
            owner='test',
506
            imageid=choice(images).id,
507
            hostid=str(i),
508
            flavor=choice(flavors))
509

    
510

    
511
def create_server_metadata(n=1):
512
    servers = VirtualMachine.objects.all()
513
    for i in range(n):
514
        VirtualMachineMetadata.objects.create(
515
            meta_key='Key%d' % (i + 1),
516
            meta_value='Value %d' % (i + 1),
517
            vm=choice(servers))
518

    
519

    
520
class AssertInvariant(object):
521
    def __init__(self, callable, *args, **kwargs):
522
        self.callable = callable
523
        self.args = args
524
        self.kwargs = kwargs
525

    
526
    def __enter__(self):
527
        self.value = self.callable(*self.args, **self.kwargs)
528
        return self.value
529

    
530
    def __exit__(self, type, value, tb):
531
        assert self.value == self.callable(*self.args, **self.kwargs)
532

    
533

    
534
class BaseTestCase(TestCase):
535
    FLAVORS = 1
536
    IMAGES = 1
537
    SERVERS = 1
538
    SERVER_METADATA = 0
539
    IMAGE_METADATA = 0
540
    fixtures = ['users']
541

    
542
    def setUp(self):
543
        self.client = AaiClient()
544
        create_flavors(self.FLAVORS)
545
        create_images(self.IMAGES)
546
        create_image_metadata(self.IMAGE_METADATA)
547
        create_servers(self.SERVERS)
548
        create_server_metadata(self.SERVER_METADATA)
549

    
550
    def assertFault(self, response, status_code, name):
551
        self.assertEqual(response.status_code, status_code)
552
        fault = json.loads(response.content)
553
        self.assertEqual(fault.keys(), [name])
554

    
555
    def assertBadRequest(self, response):
556
        self.assertFault(response, 400, 'badRequest')
557

    
558
    def assertItemNotFound(self, response):
559
        self.assertFault(response, 404, 'itemNotFound')
560

    
561
    def list_images(self, detail=False):
562
        path = '/api/v1.1/images'
563
        if detail:
564
            path += '/detail'
565
        response = self.client.get(path)
566
        self.assertTrue(response.status_code in (200, 203))
567
        reply = json.loads(response.content)
568
        self.assertEqual(reply.keys(), ['images'])
569
        self.assertEqual(reply['images'].keys(), ['values'])
570
        return reply['images']['values']
571

    
572
    def list_metadata(self, path):
573
        response = self.client.get(path)
574
        self.assertTrue(response.status_code in (200, 203))
575
        reply = json.loads(response.content)
576
        self.assertEqual(reply.keys(), ['metadata'])
577
        self.assertEqual(reply['metadata'].keys(), ['values'])
578
        return reply['metadata']['values']
579

    
580
    def list_server_metadata(self, server_id):
581
        path = '/api/v1.1/servers/%d/meta' % server_id
582
        return self.list_metadata(path)
583

    
584
    def list_image_metadata(self, image_id):
585
        path = '/api/v1.1/images/%d/meta' % image_id
586
        return self.list_metadata(path)
587

    
588
    def update_metadata(self, path, metadata):
589
        data = json.dumps({'metadata': metadata})
590
        response = self.client.post(path, data, content_type='application/json')
591
        self.assertEqual(response.status_code, 201)
592
        reply = json.loads(response.content)
593
        self.assertEqual(reply.keys(), ['metadata'])
594
        return reply['metadata']
595

    
596
    def update_server_metadata(self, server_id, metadata):
597
        path = '/api/v1.1/servers/%d/meta' % server_id
598
        return self.update_metadata(path, metadata)
599

    
600
    def update_image_metadata(self, image_id, metadata):
601
        path = '/api/v1.1/images/%d/meta' % image_id
602
        return self.update_metadata(path, metadata)
603

    
604
    def create_server_meta(self, server_id, meta):
605
        key = meta.keys()[0]
606
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
607
        data = json.dumps({'meta': meta})
608
        response = self.client.put(path, data, content_type='application/json')
609
        self.assertEqual(response.status_code, 201)
610
        reply = json.loads(response.content)
611
        self.assertEqual(reply.keys(), ['meta'])
612
        response_meta = reply['meta']
613
        self.assertEqual(response_meta, meta)
614

    
615
    def get_all_server_metadata(self):
616
        metadata = defaultdict(dict)
617
        for m in VirtualMachineMetadata.objects.all():
618
            metadata[m.vm.id][m.meta_key] = m.meta_value
619
        return metadata
620

    
621
    def get_all_image_metadata(self):
622
        metadata = defaultdict(dict)
623
        for m in ImageMetadata.objects.all():
624
            metadata[m.image.id][m.meta_key] = m.meta_value
625
        return metadata
626

    
627
    def list_networks(self, detail=False):
628
        path = '/api/v1.1/networks'
629
        if detail:
630
            path += '/detail'
631
        response = self.client.get(path)
632
        self.assertTrue(response.status_code in (200, 203))
633
        reply = json.loads(response.content)
634
        self.assertEqual(reply.keys(), ['networks'])
635
        self.assertEqual(reply['networks'].keys(), ['values'])
636
        return reply['networks']['values']
637

    
638
    def create_network(self, name):
639
        path = '/api/v1.1/networks'
640
        data = json.dumps({'network': {'name': name}})
641
        response = self.client.post(path, data, content_type='application/json')
642
        self.assertEqual(response.status_code, 202)
643
        reply = json.loads(response.content)
644
        self.assertEqual(reply.keys(), ['network'])
645
        return reply
646

    
647
    def get_network_details(self, network_id):
648
        path = '/api/v1.1/networks/%s' % network_id
649
        response = self.client.get(path)
650
        self.assertEqual(response.status_code, 200)
651
        reply = json.loads(response.content)
652
        self.assertEqual(reply.keys(), ['network'])
653
        return reply['network']
654

    
655
    def update_network_name(self, network_id, new_name):
656
        path = '/api/v1.1/networks/%s' % network_id
657
        data = json.dumps({'network': {'name': new_name}})
658
        response = self.client.put(path, data, content_type='application/json')
659
        self.assertEqual(response.status_code, 204)
660

    
661
    def delete_network(self, network_id):
662
        path = '/api/v1.1/networks/%s' % network_id
663
        response = self.client.delete(path)
664
        self.assertEqual(response.status_code, 204)
665

    
666
    def add_to_network(self, network_id, server_id):
667
        path = '/api/v1.1/networks/%s/action' % network_id
668
        data = json.dumps({'add': {'serverRef': server_id}})
669
        response = self.client.post(path, data, content_type='application/json')
670
        self.assertEqual(response.status_code, 202)
671

    
672
    def remove_from_network(self, network_id, server_id):
673
        path = '/api/v1.1/networks/%s/action' % network_id
674
        data = json.dumps({'remove': {'serverRef': server_id}})
675
        response = self.client.post(path, data, content_type='application/json')
676
        self.assertEqual(response.status_code, 202)
677

    
678

    
679
def popdict(l, **kwargs):
680
    """Pops a dict from list `l` based on the predicates given as `kwargs`."""
681

    
682
    for i in range(len(l)):
683
        item = l[i]
684
        match = True
685
        for key, val in kwargs.items():
686
            if item[key] != val:
687
                match = False
688
                break
689
        if match:
690
            del l[i]
691
            return item
692
    return None
693

    
694

    
695
class ListImages(BaseTestCase):
696
    IMAGES = 10
697

    
698
    def test_list_images(self):
699
        images = self.list_images()
700
        keys = set(['id', 'name'])
701
        for img in Image.objects.all():
702
            image = popdict(images, id=img.id)
703
            self.assertTrue(image is not None)
704
            self.assertEqual(set(image.keys()), keys)
705
            self.assertEqual(image['id'], img.id)
706
            self.assertEqual(image['name'], img.name)
707
        self.assertEqual(images, [])
708

    
709
    def test_list_images_detail(self):
710
        images = self.list_images(detail=True)
711
        keys = set(['id', 'name', 'updated', 'created', 'status', 'progress'])
712
        for img in Image.objects.all():
713
            image = popdict(images, id=img.id)
714
            self.assertTrue(image is not None)
715
            self.assertEqual(set(image.keys()), keys)
716
            self.assertEqual(image['id'], img.id)
717
            self.assertEqual(image['name'], img.name)
718
            self.assertEqual(image['status'], img.state)
719
            self.assertEqual(image['progress'], 100 if img.state == 'ACTIVE' else 0)
720
        self.assertEqual(images, [])
721

    
722

    
723
class ListServerMetadata(BaseTestCase):
724
    SERVERS = 5
725
    SERVER_METADATA = 100
726

    
727
    def test_list_metadata(self):
728
        with AssertInvariant(self.get_all_server_metadata) as metadata:
729
            for vm in VirtualMachine.objects.all():
730
                response_metadata = self.list_server_metadata(vm.id)
731
                self.assertEqual(response_metadata, metadata[vm.id])
732

    
733
    def test_invalid_server(self):
734
        with AssertInvariant(self.get_all_server_metadata):
735
            response = self.client.get('/api/v1.1/servers/0/meta')
736
            self.assertItemNotFound(response)
737

    
738

    
739
class UpdateServerMetadata(BaseTestCase):
740
    SERVER_METADATA = 10
741

    
742
    def test_update_metadata(self):
743
        metadata = self.get_all_server_metadata()
744
        server_id = choice(metadata.keys())
745
        new_metadata = {}
746
        for key in sample(metadata[server_id].keys(), 3):
747
            new_metadata[key] = 'New %s value' % key
748
        response_metadata = self.update_server_metadata(server_id,
749
                                                        new_metadata)
750
        metadata[server_id].update(new_metadata)
751
        self.assertEqual(response_metadata, metadata[server_id])
752
        self.assertEqual(metadata, self.get_all_server_metadata())
753

    
754
    def test_invalid_data(self):
755
        with AssertInvariant(self.get_all_server_metadata) as metadata:
756
            server_id = choice(metadata.keys())
757
            path = '/api/v1.1/servers/%d/meta' % server_id
758
            response = self.client.post(path, 'metadata', content_type='application/json')
759
            self.assertBadRequest(response)
760

    
761
    def test_invalid_server(self):
762
        with AssertInvariant(self.get_all_server_metadata):
763
            path = '/api/v1.1/servers/0/meta'
764
            data = json.dumps({'metadata': {'Key1': 'A Value'}})
765
            response = self.client.post(path, data, content_type='application/json')
766
            self.assertItemNotFound(response)
767

    
768

    
769
class GetServerMetadataItem(BaseTestCase):
770
    SERVERS = 5
771
    SERVER_METADATA = 100
772

    
773
    def test_get_metadata_item(self):
774
        with AssertInvariant(self.get_all_server_metadata) as metadata:
775
            server_id = choice(metadata.keys())
776
            key = choice(metadata[server_id].keys())
777
            path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
778
            response = self.client.get(path)
779
            self.assertTrue(response.status_code in (200, 203))
780
            reply = json.loads(response.content)
781
            self.assertEqual(reply['meta'], {key: metadata[server_id][key]})
782

    
783
    def test_invalid_key(self):
784
        with AssertInvariant(self.get_all_server_metadata) as metadata:
785
            server_id = choice(metadata.keys())
786
            response = self.client.get('/api/v1.1/servers/%d/meta/foo' % server_id)
787
            self.assertItemNotFound(response)
788

    
789
    def test_invalid_server(self):
790
        with AssertInvariant(self.get_all_server_metadata):
791
            response = self.client.get('/api/v1.1/servers/0/meta/foo')
792
            self.assertItemNotFound(response)
793

    
794

    
795
class CreateServerMetadataItem(BaseTestCase):
796
    SERVER_METADATA = 10
797

    
798
    def test_create_metadata(self):
799
        metadata = self.get_all_server_metadata()
800
        server_id = choice(metadata.keys())
801
        meta = {'Foo': 'Bar'}
802
        self.create_server_meta(server_id, meta)
803
        metadata[server_id].update(meta)
804
        self.assertEqual(metadata, self.get_all_server_metadata())
805

    
806
    def test_update_metadata(self):
807
        metadata = self.get_all_server_metadata()
808
        server_id = choice(metadata.keys())
809
        key = choice(metadata[server_id].keys())
810
        meta = {key: 'New Value'}
811
        self.create_server_meta(server_id, meta)
812
        metadata[server_id].update(meta)
813
        self.assertEqual(metadata, self.get_all_server_metadata())
814

    
815
    def test_invalid_server(self):
816
        with AssertInvariant(self.get_all_server_metadata):
817
            path = '/api/v1.1/servers/0/meta/foo'
818
            data = json.dumps({'meta': {'foo': 'bar'}})
819
            response = self.client.put(path, data, content_type='application/json')
820
            self.assertItemNotFound(response)
821

    
822
    def test_invalid_key(self):
823
        with AssertInvariant(self.get_all_server_metadata) as metadata:
824
            server_id = choice(metadata.keys())
825
            path = '/api/v1.1/servers/%d/meta/baz' % server_id
826
            data = json.dumps({'meta': {'foo': 'bar'}})
827
            response = self.client.put(path, data, content_type='application/json')
828
            self.assertBadRequest(response)
829

    
830
    def test_invalid_data(self):
831
        with AssertInvariant(self.get_all_server_metadata) as metadata:
832
            server_id = choice(metadata.keys())
833
            path = '/api/v1.1/servers/%d/meta/foo' % server_id
834
            response = self.client.put(path, 'meta', content_type='application/json')
835
            self.assertBadRequest(response)
836

    
837

    
838
class DeleteServerMetadataItem(BaseTestCase):
839
    SERVER_METADATA = 10
840

    
841
    def test_delete_metadata(self):
842
        metadata = self.get_all_server_metadata()
843
        server_id = choice(metadata.keys())
844
        key = choice(metadata[server_id].keys())
845
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
846
        response = self.client.delete(path)
847
        self.assertEqual(response.status_code, 204)
848
        metadata[server_id].pop(key)
849
        self.assertEqual(metadata, self.get_all_server_metadata())
850

    
851
    def test_invalid_server(self):
852
        with AssertInvariant(self.get_all_server_metadata):
853
            response = self.client.delete('/api/v1.1/servers/9/meta/Key1')
854
            self.assertItemNotFound(response)
855

    
856
    def test_invalid_key(self):
857
        with AssertInvariant(self.get_all_server_metadata) as metadata:
858
            server_id = choice(metadata.keys())
859
            path = '/api/v1.1/servers/%d/meta/foo' % server_id
860
            response = self.client.delete(path)
861
            self.assertItemNotFound(response)
862

    
863

    
864
class ListImageMetadata(BaseTestCase):
865
    IMAGES = 5
866
    IMAGE_METADATA = 100
867

    
868
    def test_list_metadata(self):
869
        with AssertInvariant(self.get_all_image_metadata) as metadata:
870
            for image in Image.objects.all():
871
                response_metadata = self.list_image_metadata(image.id)
872
                self.assertEqual(response_metadata, metadata[image.id])
873

    
874
    def test_invalid_image(self):
875
        with AssertInvariant(self.get_all_image_metadata):
876
            response = self.client.get('/api/v1.1/images/0/meta')
877
            self.assertItemNotFound(response)
878

    
879

    
880
class UpdateImageMetadata(BaseTestCase):
881
    IMAGE_METADATA = 10
882

    
883
    def test_update_metadata(self):
884
        metadata = self.get_all_image_metadata()
885
        image_id = choice(metadata.keys())
886
        new_metadata = {}
887
        for key in sample(metadata[image_id].keys(), 3):
888
            new_metadata[key] = 'New %s value' % key
889
        response_metadata = self.update_image_metadata(image_id, new_metadata)
890
        metadata[image_id].update(new_metadata)
891
        self.assertEqual(response_metadata, metadata[image_id])
892
        self.assertEqual(metadata, self.get_all_image_metadata())
893

    
894
    def test_invalid_data(self):
895
        with AssertInvariant(self.get_all_image_metadata) as metadata:
896
            image_id = choice(metadata.keys())
897
            path = '/api/v1.1/images/%d/meta' % image_id
898
            response = self.client.post(path, 'metadata', content_type='application/json')
899
            self.assertBadRequest(response)
900

    
901
    def test_invalid_server(self):
902
        with AssertInvariant(self.get_all_image_metadata):
903
            path = '/api/v1.1/images/0/meta'
904
            data = json.dumps({'metadata': {'Key1': 'A Value'}})
905
            response = self.client.post(path, data, content_type='application/json')
906
            self.assertItemNotFound(response)
907

    
908

    
909
class ServerVNCConsole(BaseTestCase):
910
    SERVERS = 1
911

    
912
    def test_not_active_server(self):
913
        """Test console req for server not in ACTIVE state returns badRequest"""
914
        server_id = choice(VirtualMachine.objects.all()).id
915
        path = '/api/v1.1/servers/%d/action' % server_id
916
        data = json.dumps({'console': {'type': 'vnc'}})
917
        response = self.client.post(path, data, content_type='application/json')
918
        self.assertBadRequest(response)
919

    
920
    def test_active_server(self):
921
        """Test console req for ACTIVE server"""
922
        server_id = choice(VirtualMachine.objects.all()).id
923
        # FIXME: Start the server properly, instead of tampering with the DB
924
        vm = choice(VirtualMachine.objects.all())
925
        vm.operstate = 'STARTED'
926
        vm.save()
927
        server_id = vm.id
928

    
929
        path = '/api/v1.1/servers/%d/action' % server_id
930
        data = json.dumps({'console': {'type': 'vnc'}})
931
        response = self.client.post(path, data, content_type='application/json')
932
        self.assertEqual(response.status_code, 200)
933
        reply = json.loads(response.content)
934
        self.assertEqual(reply.keys(), ['console'])
935
        console = reply['console']
936
        self.assertEqual(console['type'], 'vnc')
937
        self.assertEqual(set(console.keys()), set(['type', 'host', 'port', 'password']))
938

    
939

    
940
class AaiTestCase(TestCase):
941
    fixtures = ['users', 'api_test_data', 'auth_test_data']
942
    apibase = '/api/v1.1'
943

    
944
    def setUp(self):
945
        self.client = Client()
946

    
947
    def test_fail_oapi_auth(self):
948
        """ test authentication from not registered user using OpenAPI
949
        """
950
        response = self.client.get(self.apibase + '/servers', {},
951
                                   **{'X-Auth-User': 'notme',
952
                                      'X-Auth-Key': '0xdeadbabe',
953
                                      'TEST-AAI': 'true'})
954
        self.assertEquals(response.status_code, 401)
955

    
956
    def test_oapi_auth(self):
957
        """authentication with user registration
958
        """
959
        response = self.client.get(self.apibase + '/index.html', {},
960
                                   **{'X-Auth-User': 'testdbuser',
961
                                      'X-Auth-Key': 'test@synnefo.gr',
962
                                      'TEST-AAI': 'true'})
963
        self.assertEquals(response.status_code, 204)
964
        self.assertNotEqual(response['X-Auth-Token'], None)
965
        self.assertEquals(response['X-Server-Management-Url'], '')
966
        self.assertEquals(response['X-Storage-Url'], '')
967
        self.assertEquals(response['X-CDN-Management-Url'], '')
968

    
969
    def test_unauthorized_call(self):
970
        request = {'reboot': {'type': 'HARD'}}
971
        path = '/api/v1.1/servers/%d/action' % 1
972
        response = self.client.post(path, json.dumps(request),
973
                                    content_type='application/json')
974
        self.assertEquals(response.status_code, 401)
975

    
976

    
977
class ListNetworks(BaseTestCase):
978
    SERVERS = 5
979

    
980
    def setUp(self):
981
        BaseTestCase.setUp(self)
982

    
983
        for i in range(5):
984
            self.create_network('net%d' % i)
985

    
986
        machines = VirtualMachine.objects.all()
987
        for network in Network.objects.all():
988
            n = randint(0, self.SERVERS - 1)
989
            for machine in sample(machines, n):
990
                machine.nics.create(network=network)
991

    
992
    def test_list_networks(self):
993
        networks = self.list_networks()
994
        for net in Network.objects.all():
995
            net_id = str(net.id) if not net.public else 'public'
996
            network = popdict(networks, id=net_id)
997
            self.assertEqual(network['name'], net.name)
998
        self.assertEqual(networks, [])
999

    
1000
    def test_list_networks_detail(self):
1001
        networks = self.list_networks(detail=True)
1002
        for net in Network.objects.all():
1003
            net_id = str(net.id) if not net.public else 'public'
1004
            network = popdict(networks, id=net_id)
1005
            self.assertEqual(network['name'], net.name)
1006
            machines = set(vm.id for vm in net.machines.all())
1007
            self.assertEqual(set(network['servers']['values']), machines)
1008
        self.assertEqual(networks, [])
1009

    
1010

    
1011
class CreateNetwork(BaseTestCase):
1012
    def test_create_network(self):
1013
        before = self.list_networks()
1014
        self.create_network('net')
1015
        after = self.list_networks()
1016
        self.assertEqual(len(after) - len(before), 1)
1017
        found = False
1018
        for network in after:
1019
            if network['name'] == 'net':
1020
                found = True
1021
                break
1022
        self.assertTrue(found)
1023

    
1024

    
1025
class GetNetworkDetails(BaseTestCase):
1026
    SERVERS = 5
1027

    
1028
    def test_get_network_details(self):
1029
        name = 'net'
1030
        self.create_network(name)
1031

    
1032
        servers = VirtualMachine.objects.all()
1033
        network = Network.objects.all()[1]
1034

    
1035
        net = self.get_network_details(network.id)
1036
        self.assertEqual(net['name'], name)
1037
        self.assertEqual(net['servers']['values'], [])
1038

    
1039
        server_id = choice(servers).id
1040
        self.add_to_network(network.id, server_id)
1041
        net = self.get_network_details(network.id)
1042
        self.assertEqual(net['name'], network.name)
1043

    
1044

    
1045
class UpdateNetworkName(BaseTestCase):
1046
    def test_update_network_name(self):
1047
        name = 'net'
1048
        self.create_network(name)
1049
        networks = self.list_networks(detail=True)
1050
        priv = [net for net in networks if net['id'] != 'public']
1051
        network = choice(priv)
1052
        network_id = network['id']
1053
        new_name = network['name'] + '_2'
1054
        self.update_network_name(network_id, new_name)
1055

    
1056
        network['name'] = new_name
1057
        del network['updated']
1058
        net = self.get_network_details(network_id)
1059
        del net['updated']
1060
        self.assertEqual(net, network)
1061

    
1062

    
1063
class DeleteNetwork(BaseTestCase):
1064
    def test_delete_network(self):
1065
        for i in range(5):
1066
            self.create_network('net%d' % i)
1067

    
1068
        networks = self.list_networks()
1069
        priv = [net for net in networks if net['id'] != 'public']
1070
        network = choice(priv)
1071
        network_id = network['id']
1072
        self.delete_network(network_id)
1073

    
1074
        net = self.get_network_details(network_id)
1075
        self.assertEqual(net['status'], 'DELETED')
1076

    
1077
        priv.remove(network)
1078
        networks = self.list_networks()
1079
        new_priv = [net for net in networks if net['id'] != 'public']
1080
        self.assertEqual(priv, new_priv)
1081

    
1082

    
1083
class NetworkActions(BaseTestCase):
1084
    SERVERS = 20
1085

    
1086
    def test_add_remove_server(self):
1087
        self.create_network('net')
1088

    
1089
        server_ids = [vm.id for vm in VirtualMachine.objects.all()]
1090
        network = self.list_networks(detail=True)[1]
1091
        network_id = network['id']
1092

    
1093
        to_add = set(sample(server_ids, 10))
1094
        for server_id in to_add:
1095
            self.add_to_network(network_id, server_id)
1096

    
1097
        to_remove = set(sample(to_add, 5))
1098
        for server_id in to_remove:
1099
            self.remove_from_network(network_id, server_id)