Statistics
| Branch: | Tag: | Revision:

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

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'] = \
53
            settings.BYPASS_AUTHENTICATION_SECRET_TOKEN
54
        return super(AaiClient, self).request(**request)
55

    
56

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
344

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

    
351

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

    
359

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

    
368

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

    
377

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

    
390

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

    
399

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

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

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

    
413

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

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

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

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

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

    
443
    def list_images(self, detail=False):
444
        path = '/api/v1.1/images'
445
        if detail:
446
            path += '/detail'
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(), ['images'])
451
        self.assertEqual(reply['images'].keys(), ['values'])
452
        return reply['images']['values']
453

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
560

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

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

    
576

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

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

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

    
604

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

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

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

    
620

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

    
624
    def test_update_metadata(self):
625
        metadata = self.get_all_server_metadata()
626
        server_id = choice(metadata.keys())
627
        new_metadata = {}
628
        for key in sample(metadata[server_id].keys(), 3):
629
            new_metadata[key] = 'New %s value' % key
630
        response_metadata = self.update_server_metadata(server_id,
631
                                                        new_metadata)
632
        metadata[server_id].update(new_metadata)
633
        self.assertEqual(response_metadata, metadata[server_id])
634
        self.assertEqual(metadata, self.get_all_server_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

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

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

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

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

    
790

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

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

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

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

    
821

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

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

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

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

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

    
858

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

    
862
    def setUp(self):
863
        BaseTestCase.setUp(self)
864

    
865
        for i in range(5):
866
            self.create_network('net%d' % i)
867

    
868
        machines = VirtualMachine.objects.all()
869
        for network in Network.objects.all():
870
            n = randint(0, self.SERVERS - 1)
871
            for machine in sample(machines, n):
872
                machine.nics.create(network=network)
873

    
874
    def test_list_networks(self):
875
        networks = self.list_networks()
876
        for net in Network.objects.all():
877
            net_id = str(net.id) if not net.public else 'public'
878
            network = popdict(networks, id=net_id)
879
            self.assertEqual(network['name'], net.name)
880
        self.assertEqual(networks, [])
881

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

    
892

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

    
906

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

    
910
    def test_get_network_details(self):
911
        name = 'net'
912
        self.create_network(name)
913

    
914
        servers = VirtualMachine.objects.all()
915
        network = Network.objects.all()[1]
916

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

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

    
926

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

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

    
944

    
945
class DeleteNetwork(BaseTestCase):
946
    def test_delete_network(self):
947
        for i in range(5):
948
            self.create_network('net%d' % i)
949

    
950
        networks = self.list_networks()
951
        priv = [net for net in networks if net['id'] != 'public']
952
        network = choice(priv)
953
        network_id = network['id']
954
        self.delete_network(network_id)
955

    
956
        net = self.get_network_details(network_id)
957
        self.assertEqual(net['status'], 'DELETED')
958

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

    
964

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

    
968
    def test_add_remove_server(self):
969
        self.create_network('net')
970

    
971
        server_ids = [vm.id for vm in VirtualMachine.objects.all()]
972
        network = self.list_networks(detail=True)[1]
973
        network_id = network['id']
974

    
975
        to_add = set(sample(server_ids, 10))
976
        for server_id in to_add:
977
            self.add_to_network(network_id, server_id)
978

    
979
        to_remove = set(sample(to_add, 5))
980
        for server_id in to_remove:
981
            self.remove_from_network(network_id, server_id)