Statistics
| Branch: | Tag: | Revision:

root / api / tests.py @ 53481544

History | View | Annotate | Download (38.7 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

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

    
55

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

    
68
    def setUp(self):
69
        self.client = AaiClient()
70
        settings.MAX_VMS_PER_USER = 5
71

    
72
    def test_api_version(self):
73
        """Check API version."""
74

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

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

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

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

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

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

    
111
        response = self.client.get('/api/v1.1/servers/detail')
112

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
336
        key = 'name'
337
        request = {'meta': {key: 'a fancy name'}}
338

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

    
343

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

    
350

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

    
358

    
359
def create_images(n=1):
360
    owner = SynnefoUser.objects.all()[0]
361
    for i in range(n):
362
        Image.objects.create(
363
            name='Image %d' % (i + 1),
364
            state='ACTIVE',
365
            owner=owner)
366

    
367

    
368
def create_image_metadata(n=1):
369
    images = Image.objects.all()
370
    for i in range(n):
371
        ImageMetadata.objects.create(
372
            meta_key='Key%d' % (i + 1),
373
            meta_value='Value %d' % (i + 1),
374
            image=choice(images))
375

    
376

    
377
def create_servers(n=1):
378
    owner = SynnefoUser.objects.all()[0]
379
    flavors = Flavor.objects.all()
380
    images = Image.objects.all()
381
    for i in range(n):
382
        VirtualMachine.objects.create(
383
            name='Server %d' % (i + 1),
384
            owner=owner,
385
            sourceimage=choice(images),
386
            hostid=str(i),
387
            flavor=choice(flavors))
388

    
389

    
390
def create_server_metadata(n=1):
391
    servers = VirtualMachine.objects.all()
392
    for i in range(n):
393
        VirtualMachineMetadata.objects.create(
394
            meta_key='Key%d' % (i + 1),
395
            meta_value='Value %d' % (i + 1),
396
            vm=choice(servers))
397

    
398

    
399
class AssertInvariant(object):
400
    def __init__(self, callable, *args, **kwargs):
401
        self.callable = callable
402
        self.args = args
403
        self.kwargs = kwargs
404

    
405
    def __enter__(self):
406
        self.value = self.callable(*self.args, **self.kwargs)
407
        return self.value
408

    
409
    def __exit__(self, type, value, tb):
410
        assert self.value == self.callable(*self.args, **self.kwargs)
411

    
412

    
413
class BaseTestCase(TestCase):
414
    USERS = 0
415
    FLAVORS = 1
416
    IMAGES = 1
417
    SERVERS = 1
418
    SERVER_METADATA = 0
419
    IMAGE_METADATA = 0
420
    fixtures = ['users']
421

    
422
    def setUp(self):
423
        self.client = AaiClient()
424
        create_users(self.USERS)
425
        create_flavors(self.FLAVORS)
426
        create_images(self.IMAGES)
427
        create_image_metadata(self.IMAGE_METADATA)
428
        create_servers(self.SERVERS)
429
        create_server_metadata(self.SERVER_METADATA)
430

    
431
    def assertFault(self, response, status_code, name):
432
        self.assertEqual(response.status_code, status_code)
433
        fault = json.loads(response.content)
434
        self.assertEqual(fault.keys(), [name])
435

    
436
    def assertBadRequest(self, response):
437
        self.assertFault(response, 400, 'badRequest')
438

    
439
    def assertItemNotFound(self, response):
440
        self.assertFault(response, 404, 'itemNotFound')
441

    
442
    def list_images(self, detail=False):
443
        path = '/api/v1.1/images'
444
        if detail:
445
            path += '/detail'
446
        response = self.client.get(path)
447
        self.assertTrue(response.status_code in (200, 203))
448
        reply = json.loads(response.content)
449
        self.assertEqual(reply.keys(), ['images'])
450
        self.assertEqual(reply['images'].keys(), ['values'])
451
        return reply['images']['values']
452

    
453
    def list_metadata(self, path):
454
        response = self.client.get(path)
455
        self.assertTrue(response.status_code in (200, 203))
456
        reply = json.loads(response.content)
457
        self.assertEqual(reply.keys(), ['metadata'])
458
        self.assertEqual(reply['metadata'].keys(), ['values'])
459
        return reply['metadata']['values']
460

    
461
    def list_server_metadata(self, server_id):
462
        path = '/api/v1.1/servers/%d/meta' % server_id
463
        return self.list_metadata(path)
464

    
465
    def list_image_metadata(self, image_id):
466
        path = '/api/v1.1/images/%d/meta' % image_id
467
        return self.list_metadata(path)
468

    
469
    def update_metadata(self, path, metadata):
470
        data = json.dumps({'metadata': metadata})
471
        response = self.client.post(path, data, content_type='application/json')
472
        self.assertEqual(response.status_code, 201)
473
        reply = json.loads(response.content)
474
        self.assertEqual(reply.keys(), ['metadata'])
475
        return reply['metadata']
476

    
477
    def update_server_metadata(self, server_id, metadata):
478
        path = '/api/v1.1/servers/%d/meta' % server_id
479
        return self.update_metadata(path, metadata)
480

    
481
    def update_image_metadata(self, image_id, metadata):
482
        path = '/api/v1.1/images/%d/meta' % image_id
483
        return self.update_metadata(path, metadata)
484

    
485
    def create_server_meta(self, server_id, meta):
486
        key = meta.keys()[0]
487
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
488
        data = json.dumps({'meta': meta})
489
        response = self.client.put(path, data, content_type='application/json')
490
        self.assertEqual(response.status_code, 201)
491
        reply = json.loads(response.content)
492
        self.assertEqual(reply.keys(), ['meta'])
493
        response_meta = reply['meta']
494
        self.assertEqual(response_meta, meta)
495

    
496
    def get_all_server_metadata(self):
497
        metadata = defaultdict(dict)
498
        for m in VirtualMachineMetadata.objects.all():
499
            metadata[m.vm.id][m.meta_key] = m.meta_value
500
        return metadata
501

    
502
    def get_all_image_metadata(self):
503
        metadata = defaultdict(dict)
504
        for m in ImageMetadata.objects.all():
505
            metadata[m.image.id][m.meta_key] = m.meta_value
506
        return metadata
507

    
508
    def list_networks(self, detail=False):
509
        path = '/api/v1.1/networks'
510
        if detail:
511
            path += '/detail'
512
        response = self.client.get(path)
513
        self.assertTrue(response.status_code in (200, 203))
514
        reply = json.loads(response.content)
515
        self.assertEqual(reply.keys(), ['networks'])
516
        self.assertEqual(reply['networks'].keys(), ['values'])
517
        return reply['networks']['values']
518

    
519
    def create_network(self, name):
520
        path = '/api/v1.1/networks'
521
        data = json.dumps({'network': {'name': name}})
522
        response = self.client.post(path, data, content_type='application/json')
523
        self.assertEqual(response.status_code, 202)
524
        reply = json.loads(response.content)
525
        self.assertEqual(reply.keys(), ['network'])
526
        return reply
527

    
528
    def get_network_details(self, network_id):
529
        path = '/api/v1.1/networks/%s' % network_id
530
        response = self.client.get(path)
531
        self.assertEqual(response.status_code, 200)
532
        reply = json.loads(response.content)
533
        self.assertEqual(reply.keys(), ['network'])
534
        return reply['network']
535

    
536
    def update_network_name(self, network_id, new_name):
537
        path = '/api/v1.1/networks/%s' % network_id
538
        data = json.dumps({'network': {'name': new_name}})
539
        response = self.client.put(path, data, content_type='application/json')
540
        self.assertEqual(response.status_code, 204)
541

    
542
    def delete_network(self, network_id):
543
        path = '/api/v1.1/networks/%s' % network_id
544
        response = self.client.delete(path)
545
        self.assertEqual(response.status_code, 204)
546

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

    
553
    def remove_from_network(self, network_id, server_id):
554
        path = '/api/v1.1/networks/%s/action' % network_id
555
        data = json.dumps({'remove': {'serverRef': server_id}})
556
        response = self.client.post(path, data, content_type='application/json')
557
        self.assertEqual(response.status_code, 202)
558

    
559

    
560
def popdict(l, **kwargs):
561
    """Pops a dict from list `l` based on the predicates given as `kwargs`."""
562

    
563
    for i in range(len(l)):
564
        item = l[i]
565
        match = True
566
        for key, val in kwargs.items():
567
            if item[key] != val:
568
                match = False
569
                break
570
        if match:
571
            del l[i]
572
            return item
573
    return None
574

    
575

    
576
class ListImages(BaseTestCase):
577
    IMAGES = 10
578

    
579
    def test_list_images(self):
580
        images = self.list_images()
581
        keys = set(['id', 'name'])
582
        for img in Image.objects.all():
583
            image = popdict(images, id=img.id)
584
            self.assertTrue(image is not None)
585
            self.assertEqual(set(image.keys()), keys)
586
            self.assertEqual(image['id'], img.id)
587
            self.assertEqual(image['name'], img.name)
588
        self.assertEqual(images, [])
589

    
590
    def test_list_images_detail(self):
591
        images = self.list_images(detail=True)
592
        keys = set(['id', 'name', 'updated', 'created', 'status', 'progress'])
593
        for img in Image.objects.all():
594
            image = popdict(images, id=img.id)
595
            self.assertTrue(image is not None)
596
            self.assertEqual(set(image.keys()), keys)
597
            self.assertEqual(image['id'], img.id)
598
            self.assertEqual(image['name'], img.name)
599
            self.assertEqual(image['status'], img.state)
600
            self.assertEqual(image['progress'], 100 if img.state == 'ACTIVE' else 0)
601
        self.assertEqual(images, [])
602

    
603

    
604
class ListServerMetadata(BaseTestCase):
605
    SERVERS = 5
606
    SERVER_METADATA = 100
607

    
608
    def test_list_metadata(self):
609
        with AssertInvariant(self.get_all_server_metadata) as metadata:
610
            for vm in VirtualMachine.objects.all():
611
                response_metadata = self.list_server_metadata(vm.id)
612
                self.assertEqual(response_metadata, metadata[vm.id])
613

    
614
    def test_invalid_server(self):
615
        with AssertInvariant(self.get_all_server_metadata):
616
            response = self.client.get('/api/v1.1/servers/0/meta')
617
            self.assertItemNotFound(response)
618

    
619

    
620
class UpdateServerMetadata(BaseTestCase):
621
    SERVER_METADATA = 10
622

    
623
    def test_update_metadata(self):
624
        metadata = self.get_all_server_metadata()
625
        server_id = choice(metadata.keys())
626
        new_metadata = {}
627
        for key in sample(metadata[server_id].keys(), 3):
628
            new_metadata[key] = 'New %s value' % key
629
        response_metadata = self.update_server_metadata(server_id,
630
                                                        new_metadata)
631
        metadata[server_id].update(new_metadata)
632
        self.assertEqual(response_metadata, metadata[server_id])
633
        self.assertEqual(metadata, self.get_all_server_metadata())
634
    
635
    def test_invalid_data(self):
636
        with AssertInvariant(self.get_all_server_metadata) as metadata:
637
            server_id = choice(metadata.keys())
638
            path = '/api/v1.1/servers/%d/meta' % server_id
639
            response = self.client.post(path, 'metadata', content_type='application/json')
640
            self.assertBadRequest(response)
641

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

    
649

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

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

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

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

    
675

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

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

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

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

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

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

    
718

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

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

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

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

    
744

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

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

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

    
760

    
761
class UpdateImageMetadata(BaseTestCase):
762
    IMAGE_METADATA = 10
763

    
764
    def test_update_metadata(self):
765
        metadata = self.get_all_image_metadata()
766
        image_id = choice(metadata.keys())
767
        new_metadata = {}
768
        for key in sample(metadata[image_id].keys(), 3):
769
            new_metadata[key] = 'New %s value' % key
770
        response_metadata = self.update_image_metadata(image_id, new_metadata)
771
        metadata[image_id].update(new_metadata)
772
        self.assertEqual(response_metadata, metadata[image_id])
773
        self.assertEqual(metadata, self.get_all_image_metadata())
774
    
775
    def test_invalid_data(self):
776
        with AssertInvariant(self.get_all_image_metadata) as metadata:
777
            image_id = choice(metadata.keys())
778
            path = '/api/v1.1/images/%d/meta' % image_id
779
            response = self.client.post(path, 'metadata', content_type='application/json')
780
            self.assertBadRequest(response)
781

    
782
    def test_invalid_server(self):
783
        with AssertInvariant(self.get_all_image_metadata):
784
            path = '/api/v1.1/images/0/meta'
785
            data = json.dumps({'metadata': {'Key1': 'A Value'}})
786
            response = self.client.post(path, data, content_type='application/json')
787
            self.assertItemNotFound(response)
788

    
789

    
790
class ServerVNCConsole(BaseTestCase):
791
    SERVERS = 1
792

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

    
801
    def test_active_server(self):
802
        """Test console req for ACTIVE server"""
803
        server_id = choice(VirtualMachine.objects.all()).id
804
        # FIXME: Start the server properly, instead of tampering with the DB
805
        vm = choice(VirtualMachine.objects.all())
806
        vm.operstate = 'STARTED'
807
        vm.save()
808
        server_id = vm.id
809

    
810
        path = '/api/v1.1/servers/%d/action' % server_id
811
        data = json.dumps({'console': {'type': 'vnc'}})
812
        response = self.client.post(path, data, content_type='application/json')
813
        self.assertEqual(response.status_code, 200)
814
        reply = json.loads(response.content)
815
        self.assertEqual(reply.keys(), ['console'])
816
        console = reply['console']
817
        self.assertEqual(console['type'], 'vnc')
818
        self.assertEqual(set(console.keys()), set(['type', 'host', 'port', 'password']))
819

    
820

    
821
class AaiTestCase(TestCase):
822
    fixtures = ['users', 'api_test_data', 'auth_test_data']
823
    apibase = '/api/v1.1'
824

    
825
    def setUp(self):
826
        self.client = Client()
827

    
828
    def test_fail_oapi_auth(self):
829
        """ test authentication from not registered user using OpenAPI
830
        """
831
        response = self.client.get(self.apibase + '/servers', {},
832
                                   **{'X-Auth-User': 'notme',
833
                                      'X-Auth-Key': '0xdeadbabe',
834
                                      'TEST-AAI': 'true'})
835
        self.assertEquals(response.status_code, 401)
836

    
837
    def test_oapi_auth(self):
838
        """authentication with user registration
839
        """
840
        response = self.client.get(self.apibase + '/index.html', {},
841
                                   **{'X-Auth-User': 'testdbuser',
842
                                      'X-Auth-Key': 'test@synnefo.gr',
843
                                      'TEST-AAI': 'true'})
844
        self.assertEquals(response.status_code, 204)
845
        self.assertNotEqual(response['X-Auth-Token'], None)
846
        self.assertEquals(response['X-Server-Management-Url'], '')
847
        self.assertEquals(response['X-Storage-Url'], '')
848
        self.assertEquals(response['X-CDN-Management-Url'], '')
849

    
850
    def test_unauthorized_call(self):
851
        request = {'reboot': {'type': 'HARD'}}
852
        path = '/api/v1.1/servers/%d/action' % 1
853
        response = self.client.post(path, json.dumps(request),
854
                                    content_type='application/json')
855
        self.assertEquals(response.status_code, 401)
856

    
857

    
858
class ListNetworks(BaseTestCase):
859
    SERVERS = 5
860

    
861
    def setUp(self):
862
        BaseTestCase.setUp(self)
863
        
864
        for i in range(5):
865
            self.create_network('net%d' % i)
866
        
867
        machines = VirtualMachine.objects.all()
868
        for network in Network.objects.all():
869
            n = randint(0, self.SERVERS - 1)
870
            for machine in sample(machines, n):
871
                machine.nics.create(network=network)
872
    
873
    def test_list_networks(self):
874
        networks = self.list_networks()
875
        for net in Network.objects.all():
876
            net_id = str(net.id) if not net.public else 'public'
877
            network = popdict(networks, id=net_id)
878
            self.assertEqual(network['name'], net.name)
879
        self.assertEqual(networks, [])
880

    
881
    def test_list_networks_detail(self):
882
        networks = self.list_networks(detail=True)
883
        for net in Network.objects.all():
884
            net_id = str(net.id) if not net.public else 'public'
885
            network = popdict(networks, id=net_id)
886
            self.assertEqual(network['name'], net.name)
887
            machines = set(vm.id for vm in net.machines.all())
888
            self.assertEqual(set(network['servers']['values']), machines)
889
        self.assertEqual(networks, [])
890

    
891

    
892
class CreateNetwork(BaseTestCase):
893
    def test_create_network(self):
894
        before = self.list_networks()
895
        self.create_network('net')
896
        after = self.list_networks()
897
        self.assertEqual(len(after) - len(before), 1)
898
        found = False
899
        for network in after:
900
            if network['name'] == 'net':
901
                found = True
902
                break
903
        self.assertTrue(found)
904

    
905

    
906
class GetNetworkDetails(BaseTestCase):
907
    SERVERS = 5
908

    
909
    def test_get_network_details(self):
910
        name = 'net'
911
        self.create_network(name)
912
        
913
        servers = VirtualMachine.objects.all()
914
        network = Network.objects.all()[1]
915

    
916
        net = self.get_network_details(network.id)
917
        self.assertEqual(net['name'], name)
918
        self.assertEqual(net['servers']['values'], [])
919

    
920
        server_id = choice(servers).id
921
        self.add_to_network(network.id, server_id)
922
        net = self.get_network_details(network.id)
923
        self.assertEqual(net['name'], network.name)
924

    
925

    
926
class UpdateNetworkName(BaseTestCase):
927
    def test_update_network_name(self):
928
        name = 'net'
929
        self.create_network(name)
930
        networks = self.list_networks(detail=True)
931
        priv = [net for net in networks if net['id'] != 'public']
932
        network = choice(priv)
933
        network_id = network['id']
934
        new_name = network['name'] + '_2'
935
        self.update_network_name(network_id, new_name)
936

    
937
        network['name'] = new_name
938
        del network['updated']
939
        net = self.get_network_details(network_id)
940
        del net['updated']
941
        self.assertEqual(net, network)
942

    
943

    
944
class DeleteNetwork(BaseTestCase):
945
    def test_delete_network(self):
946
        for i in range(5):
947
            self.create_network('net%d' % i)
948
        
949
        networks = self.list_networks()
950
        priv = [net for net in networks if net['id'] != 'public']
951
        network = choice(priv)
952
        network_id = network['id']
953
        self.delete_network(network_id)
954
        
955
        net = self.get_network_details(network_id)
956
        self.assertEqual(net['status'], 'DELETED')
957

    
958
        priv.remove(network)
959
        networks = self.list_networks()
960
        new_priv = [net for net in networks if net['id'] != 'public']
961
        self.assertEqual(priv, new_priv)
962

    
963

    
964
class NetworkActions(BaseTestCase):
965
    SERVERS = 20
966

    
967
    def test_add_remove_server(self):
968
        self.create_network('net')
969
        
970
        server_ids = [vm.id for vm in VirtualMachine.objects.all()]
971
        network = self.list_networks(detail=True)[1]
972
        network_id = network['id']
973

    
974
        to_add = set(sample(server_ids, 10))
975
        for server_id in to_add:
976
            self.add_to_network(network_id, server_id)
977
        
978
        to_remove = set(sample(to_add, 5))
979
        for server_id in to_remove:
980
            self.remove_from_network(network_id, server_id)