Statistics
| Branch: | Tag: | Revision:

root / api / tests.py @ adee02b8

History | View | Annotate | Download (39.6 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.utils import simplejson as json
42
from django.test import TestCase
43
from django.test.client import Client
44

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

    
48

    
49
class AaiClient(Client):
50
    def request(self, **request):
51
        request['HTTP_X_AUTH_TOKEN'] = '46e427d657b20defe352804f0eb6f8a2'
52
        return super(AaiClient, self).request(**request)
53

    
54

    
55
class APITestCase(TestCase):
56
    fixtures = ['api_test_data']
57
    test_server_id = 1001
58
    test_image_id = 1
59
    test_flavor_id = 1
60
    test_group_id = 1
61
    test_wrong_server_id = 99999999
62
    test_wrong_image_id = 99999999
63
    test_wrong_flavor_id = 99999999
64
    test_wrong_group_id = 99999999
65
    #make the testing with these id's
66

    
67
    def setUp(self):
68
        self.client = AaiClient()
69

    
70
    def test_api_version(self):
71
        """Check API version."""
72

    
73
        response = self.client.get('/api/v1.1/')
74
        self.assertEqual(response.status_code, 200)
75
        api_version = json.loads(response.content)['version']
76
        self.assertEqual(api_version['id'], 'v1.1')
77
        self.assertEqual(api_version['status'], 'CURRENT')
78

    
79
    def test_server_list(self):
80
        """Test if the expected list of servers is returned."""
81

    
82
        response = self.client.get('/api/v1.1/servers')
83
        vms_from_api = json.loads(response.content)['servers']['values']
84
        vms_from_db = VirtualMachine.objects.filter(deleted=False)
85
        self.assertEqual(len(vms_from_api), len(vms_from_db))
86
        self.assertTrue(response.status_code in [200, 203])
87
        for vm_from_api in vms_from_api:
88
            vm_from_db = VirtualMachine.objects.get(id=vm_from_api['id'])
89
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
90
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
91

    
92
    def test_server_details(self):
93
        """Test if the expected server is returned."""
94

    
95
        response = self.client.get('/api/v1.1/servers/%d' % self.test_server_id)
96
        vm_from_api = json.loads(response.content)['server']
97
        vm_from_db = VirtualMachine.objects.get(id=self.test_server_id)
98
        self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
99
        self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
100
        self.assertEqual(vm_from_api['id'], vm_from_db.id)
101
        self.assertEqual(vm_from_api['imageRef'], vm_from_db.flavor.id)
102
        self.assertEqual(vm_from_api['name'], vm_from_db.name)
103
        self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
104
        self.assertTrue(response.status_code in [200, 203])
105

    
106
    def test_servers_details(self):
107
        """Test if the servers details are returned."""
108

    
109
        response = self.client.get('/api/v1.1/servers/detail')
110

    
111
        # Make sure both DB and API responses are sorted by id,
112
        # to allow for 1-1 comparisons
113
        vms_from_db = VirtualMachine.objects.filter(deleted=False).order_by('id')
114
        vms_from_api = json.loads(response.content)['servers']['values']
115
        vms_from_api = sorted(vms_from_api, key=lambda vm: vm['id'])
116
        self.assertEqual(len(vms_from_db), len(vms_from_api))
117

    
118
        id_list = [vm.id for vm in vms_from_db]
119
        number = 0
120
        for vm_id in id_list:
121
            vm_from_api = vms_from_api[number]
122
            vm_from_db = VirtualMachine.objects.get(id=vm_id)
123
            self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
124
            self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
125
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
126
            self.assertEqual(vm_from_api['imageRef'], vm_from_db.flavor.id)
127
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
128
            self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
129
            number += 1
130
        for vm_from_api in vms_from_api:
131
            vm_from_db = VirtualMachine.objects.get(id=vm_from_api['id'])
132
            self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
133
            self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
134
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
135
            self.assertEqual(vm_from_api['imageRef'], vm_from_db.flavor.id)
136
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
137
            self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
138
        self.assertTrue(response.status_code in [200,203])
139

    
140
    def test_wrong_server(self):
141
        """Test 404 response if server does not exist."""
142

    
143
        response = self.client.get('/api/v1.1/servers/%d' % self.test_wrong_server_id)
144
        self.assertEqual(response.status_code, 404)
145

    
146
    def test_create_server_empty(self):
147
        """Test if the create server call returns a 400 badRequest if
148
           no attributes are specified."""
149

    
150
        response = self.client.post('/api/v1.1/servers', {})
151
        self.assertEqual(response.status_code, 400)
152

    
153
    def test_create_server(self):
154
        """Test if the create server call returns the expected response
155
           if a valid request has been speficied."""
156

    
157
        request = {
158
                    "server": {
159
                        "name": "new-server-test",
160
                        "owner": 1,
161
                        "imageRef": 1,
162
                        "flavorRef": 1,
163
                        "metadata": {
164
                            "My Server Name": "Apache1"
165
                        },
166
                        "personality": []
167
                    }
168
        }
169
        response = self.client.post('/api/v1.1/servers', json.dumps(request),
170
                                    content_type='application/json')
171
        self.assertEqual(response.status_code, 202)
172
        #TODO: check response.content
173
        #TODO: check create server with wrong options (eg non existing flavor)
174

    
175
    def test_server_polling(self):
176
        """Test if the server polling works as expected."""
177

    
178
        response = self.client.get('/api/v1.1/servers/detail')
179
        vms_from_api_initial = json.loads(response.content)['servers']['values']
180
        ts = mktime(parsedate(response['Date']))
181
        since = datetime.datetime.fromtimestamp(ts).isoformat() + 'Z'
182
        response = self.client.get('/api/v1.1/servers/detail?changes-since=%s' % since)
183
        self.assertEqual(len(response.content), 0)
184

    
185
        #now create a machine. Then check if it is on the list
186
        request = {
187
                    "server": {
188
                        "name": "new-server-test",
189
                        "imageRef": 1,
190
                        "flavorRef": 1,
191
                        "metadata": {
192
                            "My Server Name": "Apache1"
193
                        },
194
                        "personality": []
195
                    }
196
        }
197

    
198
        path = '/api/v1.1/servers'
199
        response = self.client.post(path, json.dumps(request), content_type='application/json')
200
        self.assertEqual(response.status_code, 202)
201

    
202
        response = self.client.get('/api/v1.1/servers/detail?changes-since=%s' % since)
203
        self.assertEqual(response.status_code, 200)
204
        vms_from_api_after = json.loads(response.content)['servers']['values']
205
        #make sure the newly created server is included on the updated list
206
        self.assertEqual(len(vms_from_api_after), 1)
207

    
208
    def test_reboot_server(self):
209
        """Test if the specified server is rebooted."""
210
        request = {'reboot': {'type': 'HARD'}}
211
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
212
        response = self.client.post(path, json.dumps(request), content_type='application/json')
213
        self.assertEqual(response.status_code, 202)
214
        #server id that does not exist
215
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
216
        response = self.client.post(path, json.dumps(request), content_type='application/json')
217
        self.assertEqual(response.status_code, 404)
218

    
219
    def test_shutdown_server(self):
220
        """Test if the specified server is shutdown."""
221

    
222
        request = {'shutdown': {}}
223
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
224
        response = self.client.post(path, json.dumps(request), content_type='application/json')
225
        self.assertEqual(response.status_code, 202)
226
        #server id that does not exist
227
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
228
        response = self.client.post(path, json.dumps(request), content_type='application/json')
229
        self.assertEqual(response.status_code, 404)
230

    
231
    def test_start_server(self):
232
        """Test if the specified server is started."""
233

    
234
        request = {'start': {}}
235
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
236
        response = self.client.post(path, json.dumps(request), content_type='application/json')
237
        self.assertEqual(response.status_code, 202)
238
        #server id that does not exist
239
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
240
        response = self.client.post(path, json.dumps(request), content_type='application/json')
241
        self.assertEqual(response.status_code, 404)
242

    
243
    def test_delete_server(self):
244
        """Test if the specified server is deleted."""
245
        response = self.client.delete('/api/v1.1/servers/%d' % self.test_server_id)
246
        self.assertEqual(response.status_code, 204)
247
        #server id that does not exist
248
        response = self.client.delete('/api/v1.1/servers/%d' % self.test_wrong_server_id)
249
        self.assertEqual(response.status_code, 404)
250

    
251
    def test_flavor_list(self):
252
        """Test if the expected list of flavors is returned by."""
253

    
254
        response = self.client.get('/api/v1.1/flavors')
255
        flavors_from_api = json.loads(response.content)['flavors']['values']
256
        flavors_from_db = Flavor.objects.all()
257
        self.assertEqual(len(flavors_from_api), len(flavors_from_db))
258
        self.assertTrue(response.status_code in [200, 203])
259
        for flavor_from_api in flavors_from_api:
260
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
261
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
262
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
263

    
264
    def test_flavors_details(self):
265
        """Test if the flavors details are returned."""
266

    
267
        response = self.client.get('/api/v1.1/flavors/detail')
268
        flavors_from_db = Flavor.objects.all()
269
        flavors_from_api = json.loads(response.content)['flavors']['values']
270

    
271
        # Assert that all flavors in the db appear inthe API call result
272
        for i in range(0, len(flavors_from_db)):
273
            flavor_from_api = flavors_from_api[i]
274
            flavor_from_db = Flavor.objects.get(id=flavors_from_db[i].id)
275
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
276
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
277
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
278
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
279
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
280

    
281
        # Assert that all flavors returned by the API also exist in the db
282
        for flavor_from_api in flavors_from_api:
283
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
284
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
285
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
286
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
287
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
288
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
289

    
290
        # Check if we have the right status_code
291
        self.assertTrue(response.status_code in [200, 203])
292

    
293
    def test_flavor_details(self):
294
        """Test if the expected flavor is returned."""
295

    
296
        response = self.client.get('/api/v1.1/flavors/%d' % self.test_flavor_id)
297
        flavor_from_api = json.loads(response.content)['flavor']
298
        flavor_from_db = Flavor.objects.get(id=self.test_flavor_id)
299
        self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
300
        self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
301
        self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
302
        self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
303
        self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
304
        self.assertTrue(response.status_code in [200, 203])
305

    
306
    def test_wrong_flavor(self):
307
        """Test 404 result when requesting a flavor that does not exist."""
308

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

    
312
    def test_image_list(self):
313
        """Test if the expected list of images is returned by the API."""
314

    
315
        response = self.client.get('/api/v1.1/images')
316
        images_from_api = json.loads(response.content)['images']['values']
317
        images_from_db = Image.objects.all()
318
        self.assertEqual(len(images_from_api), len(images_from_db))
319
        self.assertTrue(response.status_code in [200, 203])
320
        for image_from_api in images_from_api:
321
            image_from_db = Image.objects.get(id=image_from_api['id'])
322
            self.assertEqual(image_from_api['id'], image_from_db.id)
323
            self.assertEqual(image_from_api['name'], image_from_db.name)
324

    
325
    def test_wrong_image(self):
326
        """Test 404 result if a non existent image is requested."""
327

    
328
        response = self.client.get('/api/v1.1/images/%d' % self.test_wrong_image_id)
329
        self.assertEqual(response.status_code, 404)
330

    
331
    def test_server_metadata(self):
332
        """Test server's metadata (add, edit)."""
333

    
334
        key = 'name'
335
        request = {'meta': {key: 'a fancy name'}}
336

    
337
        path = '/api/v1.1/servers/%d/meta/%s' % (self.test_server_id, key)
338
        response = self.client.put(path, json.dumps(request), content_type='application/json')
339
        self.assertEqual(response.status_code, 201)
340

    
341

    
342
def create_users(n=1):
343
    for i in range(n):
344
        SynnefoUser.objects.create(
345
            name='User %d' % i,
346
            credit=0)
347

    
348
def create_flavors(n=1):
349
    for i in range(n):
350
        Flavor.objects.create(
351
            cpu=randint(1, 4),
352
            ram=randint(1, 8) * 512,
353
            disk=randint(1, 40))
354

    
355
def create_images(n=1):
356
    users = SynnefoUser.objects.all()
357
    for i in range(n):
358
        Image.objects.create(
359
            name='Image %d' % (i + 1),
360
            state='ACTIVE',
361
            owner=choice(users))
362

    
363
def create_image_metadata(n=1):
364
    images = Image.objects.all()
365
    for i in range(n):
366
        ImageMetadata.objects.create(
367
            meta_key='Key%d' % (i + 1),
368
            meta_value='Value %d' % (i + 1),
369
            image = choice(images))
370

    
371
def create_servers(n=1):
372
    users = SynnefoUser.objects.all()
373
    flavors = Flavor.objects.all()
374
    images = Image.objects.all()
375
    for i in range(n):
376
        VirtualMachine.objects.create(
377
            name='Server %d' % (i + 1),
378
            owner=choice(users),
379
            sourceimage=choice(images),
380
            hostid=str(i),
381
            flavor=choice(flavors))
382

    
383
def create_server_metadata(n=1):
384
    servers = VirtualMachine.objects.all()
385
    for i in range(n):
386
        VirtualMachineMetadata.objects.create(
387
            meta_key='Key%d' % (i + 1),
388
            meta_value='Value %d' % (i + 1),
389
            vm = choice(servers))
390

    
391

    
392
class AssertInvariant(object):
393
    def __init__(self, callable, *args, **kwargs):
394
        self.callable = callable
395
        self.args = args
396
        self.kwargs = kwargs
397

    
398
    def __enter__(self):
399
        self.value = self.callable(*self.args, **self.kwargs)
400
        return self.value
401

    
402
    def __exit__(self, type, value, tb):
403
        assert self.value == self.callable(*self.args, **self.kwargs)
404

    
405

    
406
class BaseTestCase(TestCase):
407
    USERS = 0
408
    FLAVORS = 1
409
    IMAGES = 1
410
    SERVERS = 1
411
    SERVER_METADATA = 0
412
    IMAGE_METADATA = 0
413

    
414
    def setUp(self):
415
        self.client = AaiClient()
416
        create_users(self.USERS)
417
        create_flavors(self.FLAVORS)
418
        create_images(self.IMAGES)
419
        create_image_metadata(self.IMAGE_METADATA)
420
        create_servers(self.SERVERS)
421
        create_server_metadata(self.SERVER_METADATA)
422

    
423
    def assertFault(self, response, status_code, name):
424
        self.assertEqual(response.status_code, status_code)
425
        fault = json.loads(response.content)
426
        self.assertEqual(fault.keys(), [name])
427

    
428
    def assertBadRequest(self, response):
429
        self.assertFault(response, 400, 'badRequest')
430

    
431
    def assertItemNotFound(self, response):
432
        self.assertFault(response, 404, 'itemNotFound')
433

    
434

    
435
    def list_images(self, detail=False):
436
        path = '/api/v1.1/images'
437
        if detail:
438
            path += '/detail'
439
        response = self.client.get(path)
440
        self.assertTrue(response.status_code in (200, 203))
441
        reply = json.loads(response.content)
442
        self.assertEqual(reply.keys(), ['images'])
443
        self.assertEqual(reply['images'].keys(), ['values'])
444
        return reply['images']['values']
445

    
446
    def list_metadata(self, path):
447
        response = self.client.get(path)
448
        self.assertTrue(response.status_code in (200, 203))
449
        reply = json.loads(response.content)
450
        self.assertEqual(reply.keys(), ['metadata'])
451
        self.assertEqual(reply['metadata'].keys(), ['values'])
452
        return reply['metadata']['values']
453

    
454
    def list_server_metadata(self, server_id):
455
        path = '/api/v1.1/servers/%d/meta' % server_id
456
        return self.list_metadata(path)
457

    
458
    def list_image_metadata(self, image_id):
459
        path = '/api/v1.1/images/%d/meta' % image_id
460
        return self.list_metadata(path)
461

    
462
    def update_metadata(self, path, metadata):
463
        data = json.dumps({'metadata': metadata})
464
        response = self.client.post(path, data, content_type='application/json')
465
        self.assertEqual(response.status_code, 201)
466
        reply = json.loads(response.content)
467
        self.assertEqual(reply.keys(), ['metadata'])
468
        return reply['metadata']
469

    
470
    def update_server_metadata(self, server_id, metadata):
471
        path = '/api/v1.1/servers/%d/meta' % server_id
472
        return self.update_metadata(path, metadata)
473

    
474
    def update_image_metadata(self, image_id, metadata):
475
        path = '/api/v1.1/images/%d/meta' % image_id
476
        return self.update_metadata(path, metadata)
477

    
478
    def create_server_meta(self, server_id, meta):
479
        key = meta.keys()[0]
480
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
481
        data = json.dumps({'meta': meta})
482
        response = self.client.put(path, data, content_type='application/json')
483
        self.assertEqual(response.status_code, 201)
484
        reply = json.loads(response.content)
485
        self.assertEqual(reply.keys(), ['meta'])
486
        response_meta = reply['meta']
487
        self.assertEqual(response_meta, meta)
488

    
489
    def get_all_server_metadata(self):
490
        metadata = defaultdict(dict)
491
        for m in VirtualMachineMetadata.objects.all():
492
            metadata[m.vm.id][m.meta_key] = m.meta_value
493
        return metadata
494

    
495
    def get_all_image_metadata(self):
496
        metadata = defaultdict(dict)
497
        for m in ImageMetadata.objects.all():
498
            metadata[m.image.id][m.meta_key] = m.meta_value
499
        return metadata
500

    
501
    def list_networks(self, detail=False):
502
        path = '/api/v1.1/networks'
503
        if detail:
504
            path += '/detail'
505
        response = self.client.get(path)
506
        self.assertTrue(response.status_code in (200, 203))
507
        reply = json.loads(response.content)
508
        self.assertEqual(reply.keys(), ['networks'])
509
        self.assertEqual(reply['networks'].keys(), ['values'])
510
        return reply['networks']['values']
511

    
512
    def create_network(self, name):
513
        path = '/api/v1.1/networks'
514
        data = json.dumps({'network': {'name': name}})
515
        response = self.client.post(path, data, content_type='application/json')
516
        self.assertEqual(response.status_code, 202)
517
        reply = json.loads(response.content)
518
        self.assertEqual(reply.keys(), ['network'])
519
        return reply
520

    
521
    def get_network_details(self, network_id):
522
        path = '/api/v1.1/networks/%s' % network_id
523
        response = self.client.get(path)
524
        self.assertEqual(response.status_code, 200)
525
        reply = json.loads(response.content)
526
        self.assertEqual(reply.keys(), ['network'])
527
        return reply['network']
528

    
529
    def update_network_name(self, network_id, new_name):
530
        path = '/api/v1.1/networks/%s' % network_id
531
        data = json.dumps({'network': {'name': new_name}})
532
        response = self.client.put(path, data, content_type='application/json')
533
        self.assertEqual(response.status_code, 204)
534

    
535
    def delete_network(self, network_id):
536
        path = '/api/v1.1/networks/%s' % network_id
537
        response = self.client.delete(path)
538
        self.assertEqual(response.status_code, 204)
539

    
540
    def add_to_network(self, network_id, server_id):
541
        path = '/api/v1.1/networks/%s/action' % network_id
542
        data = json.dumps({'add': {'serverRef': server_id}})
543
        response = self.client.post(path, data, content_type='application/json')
544
        self.assertEqual(response.status_code, 202)
545

    
546
    def remove_from_network(self, network_id, server_id):
547
        path = '/api/v1.1/networks/%s/action' % network_id
548
        data = json.dumps({'remove': {'serverRef': server_id}})
549
        response = self.client.post(path, data, content_type='application/json')
550
        self.assertEqual(response.status_code, 202)
551

    
552

    
553
def popdict(l, **kwargs):
554
    """Pops a dict from list `l` based on the predicates given as `kwargs`."""
555

    
556
    for i in range(len(l)):
557
        item = l[i]
558
        match = True
559
        for key, val in kwargs.items():
560
            if item[key] != val:
561
                match = False
562
                break
563
        if match:
564
            del l[i]
565
            return item
566
    return None
567

    
568

    
569
class ListImages(BaseTestCase):
570
    IMAGES = 10
571

    
572
    def test_list_images(self):
573
        images = self.list_images()
574
        keys = set(['id', 'name'])
575
        for img in Image.objects.all():
576
            image = popdict(images, id=img.id)
577
            self.assertTrue(image is not None)
578
            self.assertEqual(set(image.keys()), keys)
579
            self.assertEqual(image['id'], img.id)
580
            self.assertEqual(image['name'], img.name)
581
        self.assertEqual(images, [])
582

    
583
    def test_list_images_detail(self):
584
        images = self.list_images(detail=True)
585
        keys = set(['id', 'name', 'updated', 'created', 'status', 'progress'])
586
        for img in Image.objects.all():
587
            image = popdict(images, id=img.id)
588
            self.assertTrue(image is not None)
589
            self.assertEqual(set(image.keys()), keys)
590
            self.assertEqual(image['id'], img.id)
591
            self.assertEqual(image['name'], img.name)
592
            self.assertEqual(image['status'], img.state)
593
            self.assertEqual(image['progress'], 100 if img.state == 'ACTIVE' else 0)
594
        self.assertEqual(images, [])
595

    
596

    
597
class ListServerMetadata(BaseTestCase):
598
    SERVERS = 5
599
    SERVER_METADATA = 100
600

    
601
    def test_list_metadata(self):
602
        with AssertInvariant(self.get_all_server_metadata) as metadata:
603
            for vm in VirtualMachine.objects.all():
604
                response_metadata = self.list_server_metadata(vm.id)
605
                self.assertEqual(response_metadata, metadata[vm.id])
606

    
607
    def test_invalid_server(self):
608
        with AssertInvariant(self.get_all_server_metadata):
609
            response = self.client.get('/api/v1.1/servers/0/meta')
610
            self.assertItemNotFound(response)
611

    
612

    
613
class UpdateServerMetadata(BaseTestCase):
614
    SERVER_METADATA = 10
615

    
616
    def test_update_metadata(self):
617
        metadata = self.get_all_server_metadata()
618
        server_id = choice(metadata.keys())
619
        new_metadata = {}
620
        for key in sample(metadata[server_id].keys(), 3):
621
            new_metadata[key] = 'New %s value' % key
622
        response_metadata = self.update_server_metadata(server_id, new_metadata)
623
        self.assertEqual(response_metadata, new_metadata)
624
        metadata[server_id].update(new_metadata)
625
        self.assertEqual(metadata, self.get_all_server_metadata())
626

    
627
    def test_does_not_create(self):
628
        with AssertInvariant(self.get_all_server_metadata) as metadata:
629
            server_id = choice(metadata.keys())
630
            new_metadata = {'Foo': 'Bar'}
631
            response_metadata = self.update_server_metadata(server_id, new_metadata)
632
            self.assertEqual(response_metadata, {})
633

    
634
    def test_invalid_data(self):
635
        with AssertInvariant(self.get_all_server_metadata) as metadata:
636
            server_id = choice(metadata.keys())
637
            path = '/api/v1.1/servers/%d/meta' % server_id
638
            response = self.client.post(path, 'metadata', content_type='application/json')
639
            self.assertBadRequest(response)
640

    
641
    def test_invalid_server(self):
642
        with AssertInvariant(self.get_all_server_metadata):
643
            path = '/api/v1.1/servers/0/meta'
644
            data = json.dumps({'metadata': {'Key1': 'A Value'}})
645
            response = self.client.post(path, data, content_type='application/json')
646
            self.assertItemNotFound(response)
647

    
648

    
649
class GetServerMetadataItem(BaseTestCase):
650
    SERVERS = 5
651
    SERVER_METADATA = 100
652

    
653
    def test_get_metadata_item(self):
654
        with AssertInvariant(self.get_all_server_metadata) as metadata:
655
            server_id = choice(metadata.keys())
656
            key = choice(metadata[server_id].keys())
657
            path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
658
            response = self.client.get(path)
659
            self.assertTrue(response.status_code in (200, 203))
660
            reply = json.loads(response.content)
661
            self.assertEqual(reply['meta'], {key: metadata[server_id][key]})
662

    
663
    def test_invalid_key(self):
664
        with AssertInvariant(self.get_all_server_metadata) as metadata:
665
            server_id = choice(metadata.keys())
666
            response = self.client.get('/api/v1.1/servers/%d/meta/foo' % server_id)
667
            self.assertItemNotFound(response)
668

    
669
    def test_invalid_server(self):
670
        with AssertInvariant(self.get_all_server_metadata):
671
            response = self.client.get('/api/v1.1/servers/0/meta/foo')
672
            self.assertItemNotFound(response)
673

    
674

    
675
class CreateServerMetadataItem(BaseTestCase):
676
    SERVER_METADATA = 10
677

    
678
    def test_create_metadata(self):
679
        metadata = self.get_all_server_metadata()
680
        server_id = choice(metadata.keys())
681
        meta = {'Foo': 'Bar'}
682
        self.create_server_meta(server_id, meta)
683
        metadata[server_id].update(meta)
684
        self.assertEqual(metadata, self.get_all_server_metadata())
685

    
686
    def test_update_metadata(self):
687
        metadata = self.get_all_server_metadata()
688
        server_id = choice(metadata.keys())
689
        key = choice(metadata[server_id].keys())
690
        meta = {key: 'New Value'}
691
        self.create_server_meta(server_id, meta)
692
        metadata[server_id].update(meta)
693
        self.assertEqual(metadata, self.get_all_server_metadata())
694

    
695
    def test_invalid_server(self):
696
        with AssertInvariant(self.get_all_server_metadata):
697
            path = '/api/v1.1/servers/0/meta/foo'
698
            data = json.dumps({'meta': {'foo': 'bar'}})
699
            response = self.client.put(path, data, content_type='application/json')
700
            self.assertItemNotFound(response)
701

    
702
    def test_invalid_key(self):
703
        with AssertInvariant(self.get_all_server_metadata) as metadata:
704
            server_id = choice(metadata.keys())
705
            path = '/api/v1.1/servers/%d/meta/baz' % server_id
706
            data = json.dumps({'meta': {'foo': 'bar'}})
707
            response = self.client.put(path, data, content_type='application/json')
708
            self.assertBadRequest(response)
709

    
710
    def test_invalid_data(self):
711
        with AssertInvariant(self.get_all_server_metadata) as metadata:
712
            server_id = choice(metadata.keys())
713
            path = '/api/v1.1/servers/%d/meta/foo' % server_id
714
            response = self.client.put(path, 'meta', content_type='application/json')
715
            self.assertBadRequest(response)
716

    
717

    
718
class DeleteServerMetadataItem(BaseTestCase):
719
    SERVER_METADATA = 10
720

    
721
    def test_delete_metadata(self):
722
        metadata = self.get_all_server_metadata()
723
        server_id = choice(metadata.keys())
724
        key = choice(metadata[server_id].keys())
725
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
726
        response = self.client.delete(path)
727
        self.assertEqual(response.status_code, 204)
728
        metadata[server_id].pop(key)
729
        self.assertEqual(metadata, self.get_all_server_metadata())
730

    
731
    def test_invalid_server(self):
732
        with AssertInvariant(self.get_all_server_metadata):
733
            response = self.client.delete('/api/v1.1/servers/9/meta/Key1')
734
            self.assertItemNotFound(response)
735

    
736
    def test_invalid_key(self):
737
        with AssertInvariant(self.get_all_server_metadata) as metadata:
738
            server_id = choice(metadata.keys())
739
            path = '/api/v1.1/servers/%d/meta/foo' % server_id
740
            response = self.client.delete(path)
741
            self.assertItemNotFound(response)
742

    
743

    
744
class ListImageMetadata(BaseTestCase):
745
    IMAGES = 5
746
    IMAGE_METADATA = 100
747

    
748
    def test_list_metadata(self):
749
        with AssertInvariant(self.get_all_image_metadata) as metadata:
750
            for image in Image.objects.all():
751
                response_metadata = self.list_image_metadata(image.id)
752
                self.assertEqual(response_metadata, metadata[image.id])
753

    
754
    def test_invalid_image(self):
755
        with AssertInvariant(self.get_all_image_metadata):
756
            response = self.client.get('/api/v1.1/images/0/meta')
757
            self.assertItemNotFound(response)
758

    
759
class UpdateImageMetadata(BaseTestCase):
760
    IMAGE_METADATA = 10
761

    
762
    def test_update_metadata(self):
763
        metadata = self.get_all_image_metadata()
764
        image_id = choice(metadata.keys())
765
        new_metadata = {}
766
        for key in sample(metadata[image_id].keys(), 3):
767
            new_metadata[key] = 'New %s value' % key
768
        response_metadata = self.update_image_metadata(image_id, new_metadata)
769
        self.assertEqual(response_metadata, new_metadata)
770
        metadata[image_id].update(new_metadata)
771
        self.assertEqual(metadata, self.get_all_image_metadata())
772

    
773
    def test_does_not_create(self):
774
        with AssertInvariant(self.get_all_image_metadata) as metadata:
775
            image_id = choice(metadata.keys())
776
            new_metadata = {'Foo': 'Bar'}
777
            response_metadata = self.update_image_metadata(image_id, new_metadata)
778
            self.assertEqual(response_metadata, {})
779

    
780
    def test_invalid_data(self):
781
        with AssertInvariant(self.get_all_image_metadata) as metadata:
782
            image_id = choice(metadata.keys())
783
            path = '/api/v1.1/images/%d/meta' % image_id
784
            response = self.client.post(path, 'metadata', content_type='application/json')
785
            self.assertBadRequest(response)
786

    
787
    def test_invalid_server(self):
788
        with AssertInvariant(self.get_all_image_metadata):
789
            path = '/api/v1.1/images/0/meta'
790
            data = json.dumps({'metadata': {'Key1': 'A Value'}})
791
            response = self.client.post(path, data, content_type='application/json')
792
            self.assertItemNotFound(response)
793

    
794

    
795
class ServerVNCConsole(BaseTestCase):
796
    SERVERS = 1
797

    
798
    def test_not_active_server(self):
799
        """Test console req for server not in ACTIVE state returns badRequest"""
800
        server_id = choice(VirtualMachine.objects.all()).id
801
        path = '/api/v1.1/servers/%d/action' % server_id
802
        data = json.dumps({'console': {'type': 'vnc'}})
803
        response = self.client.post(path, data, content_type='application/json')
804
        self.assertBadRequest(response)
805

    
806
    def test_active_server(self):
807
        """Test console req for ACTIVE server"""
808
        server_id = choice(VirtualMachine.objects.all()).id
809
        # FIXME: Start the server properly, instead of tampering with the DB
810
        vm = choice(VirtualMachine.objects.all())
811
        vm.operstate = 'STARTED'
812
        vm.save()
813
        server_id = vm.id
814
        
815
        path = '/api/v1.1/servers/%d/action' % server_id
816
        data = json.dumps({'console': {'type': 'vnc'}})
817
        response = self.client.post(path, data, content_type='application/json')
818
        self.assertEqual(response.status_code, 200)
819
        reply = json.loads(response.content)
820
        self.assertEqual(reply.keys(), ['console'])
821
        console = reply['console']
822
        self.assertEqual(console['type'], 'vnc')
823
        self.assertEqual(set(console.keys()), set(['type', 'host', 'port', 'password']))
824

    
825

    
826
class AaiTestCase(TestCase):
827
    fixtures = ['api_test_data', 'auth_test_data']
828
    apibase = '/api/v1.1'
829

    
830
    def setUp(self):
831
        self.client = Client()
832

    
833
    def test_auth_cookie(self):
834
        user = SynnefoUser.objects.get(uniq = "test@synnefo.gr")
835
        self.client.cookies['X-Auth-Token'] = user.auth_token
836
        response = self.client.get('/index.html', {},
837
                                   **{'X-Auth-Token': user.auth_token,
838
                                      'TEST-AAI' : 'true'})
839
        self.assertTrue(response.status_code, 200)
840
        self.assertTrue('Vary' in response)
841
        self.assertTrue('X-Auth-Token' in response['Vary'])
842

    
843
    def test_fail_oapi_auth(self):
844
        """ test authentication from not registered user using OpenAPI
845
        """
846
        response = self.client.get(self.apibase + '/servers', {},
847
                                   **{'X-Auth-User': 'notme',
848
                                      'X-Auth-Key': '0xdeadbabe',
849
                                      'TEST-AAI' : 'true'})
850
        self.assertEquals(response.status_code, 401)
851

    
852
    def test_oapi_auth(self):
853
        """authentication with user registration
854
        """
855
        response = self.client.get(self.apibase + '/index.html', {},
856
                                   **{'X-Auth-User': 'testdbuser',
857
                                      'X-Auth-Key': 'test@synnefo.gr',
858
                                      'TEST-AAI' : 'true'})
859
        self.assertEquals(response.status_code, 204)
860
        self.assertNotEqual(response['X-Auth-Token'], None)
861
        self.assertEquals(response['X-Server-Management-Url'], '')
862
        self.assertEquals(response['X-Storage-Url'], '')
863
        self.assertEquals(response['X-CDN-Management-Url'], '')
864

    
865
    def test_unauthorized_call(self):
866
        request = {'reboot': {'type': 'HARD'}}
867
        path = '/api/v1.1/servers/%d/action' % 1
868
        response = self.client.post(path, json.dumps(request), content_type='application/json')
869
        self.assertEquals(response.status_code, 401)
870

    
871

    
872
class ListNetworks(BaseTestCase):
873
    SERVERS = 5
874

    
875
    def setUp(self):
876
        BaseTestCase.setUp(self)
877
        
878
        for i in range(5):
879
            self.create_network('net%d' % i)
880
        
881
        machines = VirtualMachine.objects.all()
882
        for network in Network.objects.all():
883
            n = randint(0, self.SERVERS)
884
            for machine in sample(machines, n):
885
                machine.nics.create(network=network)
886
    
887
    def test_list_networks(self):
888
        networks = self.list_networks()
889
        for net in Network.objects.all():
890
            net_id = str(net.id) if not net.public else 'public'
891
            network = popdict(networks, id=net_id)
892
            self.assertEqual(network['name'], net.name)
893
        self.assertEqual(networks, [])
894

    
895
    def test_list_networks_detail(self):
896
        networks = self.list_networks(detail=True)
897
        for net in Network.objects.all():
898
            net_id = str(net.id) if not net.public else 'public'
899
            network = popdict(networks, id=net_id)
900
            self.assertEqual(network['name'], net.name)
901
            machines = set(vm.id for vm in net.machines.all())
902
            self.assertEqual(set(network['servers']['values']), machines)
903
        self.assertEqual(networks, [])
904

    
905

    
906
class CreateNetwork(BaseTestCase):
907
    def test_create_network(self):
908
        before = self.list_networks()
909
        self.create_network('net')
910
        after = self.list_networks()
911
        self.assertEqual(len(after) - len(before), 1)
912
        found = False
913
        for network in after:
914
            if network['name'] == 'net':
915
                found = True
916
                break
917
        self.assertTrue(found)
918

    
919

    
920
class GetNetworkDetails(BaseTestCase):
921
    SERVERS = 5
922
    
923
    def test_get_network_details(self):
924
        name = 'net'
925
        self.create_network(name)
926
        
927
        servers = VirtualMachine.objects.all()
928
        network = Network.objects.all()[1]
929

    
930
        net = self.get_network_details(network.id)
931
        self.assertEqual(net['name'], name)
932
        self.assertEqual(net['servers']['values'], [])
933

    
934
        server_id = choice(servers).id
935
        self.add_to_network(network.id, server_id)
936
        net = self.get_network_details(network.id)
937
        self.assertEqual(net['name'], network.name)
938

    
939

    
940
class UpdateNetworkName(BaseTestCase):
941
    def test_update_network_name(self):
942
        name = 'net'
943
        self.create_network(name)
944
        networks = self.list_networks(detail=True)
945
        priv = [net for net in networks if net['id'] != 'public']
946
        network = choice(priv)
947
        network_id = network['id']
948
        new_name = network['name'] + '_2'
949
        self.update_network_name(network_id, new_name)
950

    
951
        network['name'] = new_name
952
        del network['updated']
953
        net = self.get_network_details(network_id)
954
        del net['updated']
955
        self.assertEqual(net, network)
956

    
957

    
958
class DeleteNetwork(BaseTestCase):
959
    def test_delete_network(self):
960
        for i in range(5):
961
            self.create_network('net%d' % i)
962
        
963
        networks = self.list_networks()
964
        priv = [net for net in networks if net['id'] != 'public']
965
        network = choice(priv)
966
        network_id = network['id']
967
        self.delete_network(network_id)
968
        
969
        net = self.get_network_details(network_id)
970
        self.assertEqual(net['status'], 'DELETED')
971

    
972
        priv.remove(network)
973
        networks = self.list_networks()
974
        new_priv = [net for net in networks if net['id'] != 'public']
975
        self.assertEqual(priv, new_priv)
976

    
977

    
978
class NetworkActions(BaseTestCase):
979
    SERVERS = 20
980

    
981
    def test_add_remove_server(self):
982
        self.create_network('net')
983
        
984
        server_ids = [vm.id for vm in VirtualMachine.objects.all()]
985
        network = self.list_networks(detail=True)[1]
986
        network_id = network['id']
987

    
988
        to_add = set(sample(server_ids, 10))
989
        for server_id in to_add:
990
            self.add_to_network(network_id, server_id)
991
        
992
        to_remove = set(sample(to_add, 5))
993
        for server_id in to_remove:
994
            self.remove_from_network(network_id, server_id)