Statistics
| Branch: | Tag: | Revision:

root / api / tests.py @ ccd0d474

History | View | Annotate | Download (39.2 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 = ['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
def create_flavors(n=1):
351
    for i in range(n):
352
        Flavor.objects.create(
353
            cpu=randint(1, 4),
354
            ram=randint(1, 8) * 512,
355
            disk=randint(1, 40))
356

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

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

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

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

    
393

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

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

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

    
407

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

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

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

    
430
    def assertBadRequest(self, response):
431
        self.assertFault(response, 400, 'badRequest')
432

    
433
    def assertItemNotFound(self, response):
434
        self.assertFault(response, 404, 'itemNotFound')
435

    
436

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
554

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

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

    
570

    
571
class ListImages(BaseTestCase):
572
    IMAGES = 10
573

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

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

    
598

    
599
class ListServerMetadata(BaseTestCase):
600
    SERVERS = 5
601
    SERVER_METADATA = 100
602

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

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

    
614

    
615
class UpdateServerMetadata(BaseTestCase):
616
    SERVER_METADATA = 10
617

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

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

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

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

    
650

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

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

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

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

    
676

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

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

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

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

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

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

    
719

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

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

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

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

    
745

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

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

    
756
    def test_invalid_image(self):
757
        with AssertInvariant(self.get_all_image_metadata):
758
            response = self.client.get('/api/v1.1/images/0/meta')
759
            self.assertItemNotFound(response)
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
        self.assertEqual(response_metadata, new_metadata)
772
        metadata[image_id].update(new_metadata)
773
        self.assertEqual(metadata, self.get_all_image_metadata())
774

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

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

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

    
796

    
797
class ServerVNCConsole(BaseTestCase):
798
    SERVERS = 1
799

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

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

    
827

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

    
832
    def setUp(self):
833
        self.client = Client()
834

    
835
    def test_fail_oapi_auth(self):
836
        """ test authentication from not registered user using OpenAPI
837
        """
838
        response = self.client.get(self.apibase + '/servers', {},
839
                                   **{'X-Auth-User': 'notme',
840
                                      'X-Auth-Key': '0xdeadbabe',
841
                                      'TEST-AAI' : 'true'})
842
        self.assertEquals(response.status_code, 401)
843

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

    
857
    def test_unauthorized_call(self):
858
        request = {'reboot': {'type': 'HARD'}}
859
        path = '/api/v1.1/servers/%d/action' % 1
860
        response = self.client.post(path, json.dumps(request), content_type='application/json')
861
        self.assertEquals(response.status_code, 401)
862

    
863

    
864
class ListNetworks(BaseTestCase):
865
    SERVERS = 5
866

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

    
887
    def test_list_networks_detail(self):
888
        networks = self.list_networks(detail=True)
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
            machines = set(vm.id for vm in net.machines.all())
894
            self.assertEqual(set(network['servers']['values']), machines)
895
        self.assertEqual(networks, [])
896

    
897

    
898
class CreateNetwork(BaseTestCase):
899
    def test_create_network(self):
900
        before = self.list_networks()
901
        self.create_network('net')
902
        after = self.list_networks()
903
        self.assertEqual(len(after) - len(before), 1)
904
        found = False
905
        for network in after:
906
            if network['name'] == 'net':
907
                found = True
908
                break
909
        self.assertTrue(found)
910

    
911

    
912
class GetNetworkDetails(BaseTestCase):
913
    SERVERS = 5
914
    
915
    def test_get_network_details(self):
916
        name = 'net'
917
        self.create_network(name)
918
        
919
        servers = VirtualMachine.objects.all()
920
        network = Network.objects.all()[1]
921

    
922
        net = self.get_network_details(network.id)
923
        self.assertEqual(net['name'], name)
924
        self.assertEqual(net['servers']['values'], [])
925

    
926
        server_id = choice(servers).id
927
        self.add_to_network(network.id, server_id)
928
        net = self.get_network_details(network.id)
929
        self.assertEqual(net['name'], network.name)
930

    
931

    
932
class UpdateNetworkName(BaseTestCase):
933
    def test_update_network_name(self):
934
        name = 'net'
935
        self.create_network(name)
936
        networks = self.list_networks(detail=True)
937
        priv = [net for net in networks if net['id'] != 'public']
938
        network = choice(priv)
939
        network_id = network['id']
940
        new_name = network['name'] + '_2'
941
        self.update_network_name(network_id, new_name)
942

    
943
        network['name'] = new_name
944
        del network['updated']
945
        net = self.get_network_details(network_id)
946
        del net['updated']
947
        self.assertEqual(net, network)
948

    
949

    
950
class DeleteNetwork(BaseTestCase):
951
    def test_delete_network(self):
952
        for i in range(5):
953
            self.create_network('net%d' % i)
954
        
955
        networks = self.list_networks()
956
        priv = [net for net in networks if net['id'] != 'public']
957
        network = choice(priv)
958
        network_id = network['id']
959
        self.delete_network(network_id)
960
        
961
        net = self.get_network_details(network_id)
962
        self.assertEqual(net['status'], 'DELETED')
963

    
964
        priv.remove(network)
965
        networks = self.list_networks()
966
        new_priv = [net for net in networks if net['id'] != 'public']
967
        self.assertEqual(priv, new_priv)
968

    
969

    
970
class NetworkActions(BaseTestCase):
971
    SERVERS = 20
972

    
973
    def test_add_remove_server(self):
974
        self.create_network('net')
975
        
976
        server_ids = [vm.id for vm in VirtualMachine.objects.all()]
977
        network = self.list_networks(detail=True)[1]
978
        network_id = network['id']
979

    
980
        to_add = set(sample(server_ids, 10))
981
        for server_id in to_add:
982
            self.add_to_network(network_id, server_id)
983
        
984
        to_remove = set(sample(to_add, 5))
985
        for server_id in to_remove:
986
            self.remove_from_network(network_id, server_id)