Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (45.3 kB)

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

    
34
from __future__ import with_statement
35

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

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

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

    
49
import mock
50
from synnefo.lib import astakos
51

    
52
from contextlib import contextmanager
53

    
54
# This is a hack to override astakos
55
# The global variable ASTAKOS_PATCHED_USER keeps the name of
56
# a dummy user that we provide through replacement of the
57
# corresponding astakos function with a dummy function.
58
ASTAKOS_PATCHED_USER = "user"
59

    
60
@contextmanager
61
def astakos_user(user):
62
    """
63
    Context manager to mock astakos response.
64

65
    usage:
66
    with astakos_user("user@user.com"):
67
        .... make api calls .... get_user will act as user@user.com requested the api.
68

69
    """
70
    from synnefo.lib import astakos
71
    orig_method = astakos.get_user
72
    
73
    global ASTAKOS_PATCHED_USER
74
    ASTAKOS_PATCHED_USER = user
75
    def dummy_get_user(request, *args, **kwargs):
76
        global ASTAKOS_PATCHED_USER
77
        request.user = {'username': ASTAKOS_PATCHED_USER, 'groups': []}
78
        request.user_uniq = ASTAKOS_PATCHED_USER
79
    astakos.get_user = dummy_get_user
80
    yield
81
    astakos.get_user = orig_method
82

    
83
class AaiClient(Client):
84
    def request(self, **request):
85
        request['HTTP_X_AUTH_TOKEN'] = '0000'
86
        return super(AaiClient, self).request(**request)
87

    
88
class NetworksTest(TestCase):
89
    
90
    fixtures = ['network_test_data']
91

    
92
    def test_attachments_list(self):
93
        with astakos_user("admin@adminland.com"):
94
            r = self.client.get("/api/v1.1/networks")
95
            data = json.loads(r.content)
96
            self.assertEqual(data["networks"]["values"][1]["name"], "network4admin")
97
            #import pdb; pdb.set_trace()
98
            self.assertEqual(len(data["networks"]["values"]), 2)
99
        with astakos_user("user1@userland.com"):
100
            r = self.client.get("/api/v1.1/networks")
101
            data = json.loads(r.content)
102
            self.assertEqual(data["networks"]["values"][1]["name"], "network4user1")
103
            #import pdb; pdb.set_trace()
104
            self.assertEqual(len(data["networks"]["values"]), 2)
105

    
106
    def test_create_network(self):
107
        with astakos_user("admin@adminland.com"):
108
            r = self.client.post("/api/v1.1/networks")
109

    
110
class ServersTest(TestCase):
111

    
112
    fixtures = ['network_test_data']
113

    
114
    def test_attachments_list(self):
115
        with astakos_user("admin@adminland.com"):
116
            r = self.client.get("/api/v1.1/servers")
117
            data = json.loads(r.content)
118
            self.assertEqual(data["servers"]["values"][0]["id"], 1001)
119
            self.assertEqual(len(data["servers"]["values"]), 1)
120
            r = self.client.get("/api/v1.1/servers/1001")
121

    
122

    
123
class TestQuota(TestCase):
124

    
125
    fixtures = ['users', 'flavors']
126
    astakos_response_user = 'test'
127

    
128
    def setUp(self):
129

    
130
        self.astakos_response_user = 'test'
131
        def get_user_mock(request, *Args, **kwargs):
132
            if request.META.get('HTTP_X_AUTH_TOKEN', None) == '0000':
133
                request.user_uniq = self.astakos_response_user
134
                request.user = {'uniq': self.astakos_response_user}
135

    
136
        def get_image_mock(request, *Args, **kwargs):
137
            return {'backend_id':'1234', 'location':'pithos://dummyimage',
138
                    'disk_format': 'plain'}
139

    
140
        self.rapi_patch = mock.patch('synnefo.logic.backend.rapi')
141
        self.rapi_mock = self.rapi_patch.start()
142

    
143
        self.pithos_patch = mock.patch('synnefo.api.util.get_image')
144
        self.pithos_mock = self.rapi_patch.start()
145

    
146
        # mock the astakos authentication function
147
        from synnefo.api import util
148
        util.get_user = get_user_mock
149
        util.get_image = get_image_mock
150

    
151
        settings.SKIP_SSH_VALIDATION = True
152
        self.client = AaiClient()
153
        self.user = 'test'
154

    
155
    def test_vms_quota(self):
156
        request = {
157
                    "server": {
158
                        "name": "new-server-test",
159
                        "userid": "test",
160
                        "imageRef": 1,
161
                        "flavorRef": 1,
162
                        "metadata": {
163
                            "My Server Name": "Apache1"
164
                        },
165
                        "personality": []
166
                    }
167
        }
168

    
169
        def create_server(for_user='test'):
170
            self.astakos_response_user = for_user
171
            return self.client.post('/api/v1.1/servers', json.dumps(request),
172
                content_type="application/json")
173

    
174
        def user_vms_count(user='test'):
175
            return VirtualMachine.objects.filter(userid=user).count()
176

    
177
        # admin sets max vms per user to 2
178
        settings.MAX_VMS_PER_USER = 2
179
        create_server()
180
        create_server()
181
        self.assertEqual(user_vms_count(), 2)
182

    
183
        # third creation fails
184
        resp = create_server()
185
        self.assertEqual(resp.status_code, 413)
186
        self.assertEqual(user_vms_count(), 2)
187

    
188
        # setting changed, no additional servers can get created
189
        settings.MAX_VMS_PER_USER = 1
190
        resp = create_server()
191
        self.assertEqual(resp.status_code, 413)
192
        self.assertEqual(user_vms_count(), 2)
193

    
194
        # admin wants test user to create 4 vms, now test user can create
195
        # one additional vm, but no more
196
        settings.VMS_USER_QUOTA = {'test':3}
197
        create_server()
198
        self.assertEqual(user_vms_count(), 3)
199
        resp = create_server()
200
        self.assertEqual(resp.status_code, 413)
201
        self.assertEqual(user_vms_count(), 3)
202
        # other users still apply to the global quota
203
        create_server("testuser2")
204
        self.assertEqual(user_vms_count("testuser2"), 1)
205
        resp = create_server("testuser2")
206
        self.assertEqual(resp.status_code, 413)
207
        self.assertEqual(user_vms_count("testuser2"), 1)
208

    
209

    
210
    def test_networks_quota(self):
211

    
212
        def create_network(for_user='test'):
213
            request = json.dumps({'network': {'name': 'user %s network' %
214
                for_user}})
215
            self.astakos_response_user = for_user
216
            return self.client.post('/api/v1.1/networks', request,
217
                content_type="application/json")
218

    
219
        def user_networks_count(user='test'):
220
            return Network.objects.filter(userid=user).count()
221

    
222
        settings.MAX_NETWORKS_PER_USER = 1
223
        create_network()
224
        self.assertEqual(user_networks_count(),1)
225
        resp = create_network()
226
        self.assertEqual(resp.status_code, 413)
227
        self.assertEqual(user_networks_count(), 1)
228

    
229
        settings.NETWORKS_USER_QUOTA = {'test':2}
230
        create_network()
231
        self.assertEqual(user_networks_count(),2)
232
        resp = create_network()
233
        self.assertEqual(resp.status_code, 413)
234
        self.assertEqual(user_networks_count(), 2)
235

    
236
        create_network("testuser2")
237
        self.assertEqual(user_networks_count("testuser2"),1)
238
        resp = create_network("testuser2")
239
        self.assertEqual(resp.status_code, 413)
240
        self.assertEqual(user_networks_count("testuser2"), 1)
241

    
242
        settings.GANETI_MAX_LINK_NUMBER = 3
243
        settings.NETWORKS_USER_QUOTA = {'test':10}
244
        resp = create_network()
245
        self.assertEqual(Network.objects.count(), 4)
246
        self.assertEqual('No networks available.' in resp.content, True)
247
        self.assertEqual(user_networks_count(), 2)
248

    
249

    
250

    
251

    
252
class APITestCase(TestCase):
253
    fixtures = ['users', 'api_test_data']
254
    test_server_id = 1001
255
    test_image_id = 1
256
    test_flavor_id = 1
257
    test_group_id = 1
258
    test_wrong_server_id = 99999999
259
    test_wrong_image_id = 99999999
260
    test_wrong_flavor_id = 99999999
261
    test_wrong_group_id = 99999999
262
    #make the testing with these id's
263

    
264
    def setUp(self):
265
        self.client = AaiClient()
266
        settings.MAX_VMS_PER_USER = 5
267

    
268
    def test_api_version(self):
269
        """Check API version."""
270

    
271
        response = self.client.get('/api/v1.1/')
272
        self.assertEqual(response.status_code, 200)
273
        api_version = json.loads(response.content)['version']
274
        self.assertEqual(api_version['id'], 'v1.1')
275
        self.assertEqual(api_version['status'], 'CURRENT')
276

    
277
    def test_server_list(self):
278
        """Test if the expected list of servers is returned."""
279

    
280
        response = self.client.get('/api/v1.1/servers')
281
        vms_from_api = json.loads(response.content)['servers']['values']
282
        vms_from_db = VirtualMachine.objects.filter(deleted=False)
283
        self.assertEqual(len(vms_from_api), len(vms_from_db))
284
        self.assertTrue(response.status_code in [200, 203])
285
        for vm_from_api in vms_from_api:
286
            vm_from_db = VirtualMachine.objects.get(id=vm_from_api['id'])
287
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
288
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
289

    
290
    def test_server_details(self):
291
        """Test if the expected server is returned."""
292

    
293
        response = self.client.get('/api/v1.1/servers/%d' % self.test_server_id)
294
        vm_from_api = json.loads(response.content)['server']
295
        vm_from_db = VirtualMachine.objects.get(id=self.test_server_id)
296
        self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
297
        self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
298
        self.assertEqual(vm_from_api['id'], vm_from_db.id)
299
        self.assertEqual(vm_from_api['imageRef'], vm_from_db.imageid)
300
        self.assertEqual(vm_from_api['name'], vm_from_db.name)
301
        self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
302
        self.assertTrue(response.status_code in [200, 203])
303

    
304
    def test_servers_details(self):
305
        """Test if the servers details are returned."""
306

    
307
        response = self.client.get('/api/v1.1/servers/detail')
308

    
309
        # Make sure both DB and API responses are sorted by id,
310
        # to allow for 1-1 comparisons
311
        vms_from_db = VirtualMachine.objects.filter(deleted=False).order_by('id')
312
        vms_from_api = json.loads(response.content)['servers']['values']
313
        vms_from_api = sorted(vms_from_api, key=lambda vm: vm['id'])
314
        self.assertEqual(len(vms_from_db), len(vms_from_api))
315

    
316
        id_list = [vm.id for vm in vms_from_db]
317
        number = 0
318
        for vm_id in id_list:
319
            vm_from_api = vms_from_api[number]
320
            vm_from_db = VirtualMachine.objects.get(id=vm_id)
321
            self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
322
            self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
323
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
324
            self.assertEqual(vm_from_api['imageRef'], vm_from_db.imageid)
325
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
326
            self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
327
            number += 1
328
        for vm_from_api in vms_from_api:
329
            vm_from_db = VirtualMachine.objects.get(id=vm_from_api['id'])
330
            self.assertEqual(vm_from_api['flavorRef'], vm_from_db.flavor.id)
331
            self.assertEqual(vm_from_api['hostId'], vm_from_db.hostid)
332
            self.assertEqual(vm_from_api['id'], vm_from_db.id)
333
            self.assertEqual(vm_from_api['imageRef'], vm_from_db.imageid)
334
            self.assertEqual(vm_from_api['name'], vm_from_db.name)
335
            self.assertEqual(vm_from_api['status'], get_rsapi_state(vm_from_db))
336
        self.assertTrue(response.status_code in [200, 203])
337

    
338
    def test_wrong_server(self):
339
        """Test 404 response if server does not exist."""
340

    
341
        response = self.client.get('/api/v1.1/servers/%d' % self.test_wrong_server_id)
342
        self.assertEqual(response.status_code, 404)
343

    
344
    def test_create_server_empty(self):
345
        """Test if the create server call returns a 400 badRequest if
346
           no attributes are specified."""
347

    
348
        response = self.client.post('/api/v1.1/servers', {})
349
        self.assertEqual(response.status_code, 400)
350

    
351
    def test_create_server(self):
352
        """Test if the create server call returns the expected response
353
           if a valid request has been speficied."""
354

    
355
        request = {
356
                    "server": {
357
                        "name": "new-server-test",
358
                        "userid": "test",
359
                        "imageRef": 1,
360
                        "flavorRef": 1,
361
                        "metadata": {
362
                            "My Server Name": "Apache1"
363
                        },
364
                        "personality": []
365
                    }
366
        }
367
        response = self.client.post('/api/v1.1/servers', json.dumps(request),
368
                                    content_type='application/json')
369
        self.assertEqual(response.status_code, 202)
370
        #TODO: check response.content
371
        #TODO: check create server with wrong options (eg non existing flavor)
372

    
373
    def test_server_polling(self):
374
        """Test if the server polling works as expected."""
375

    
376
        response = self.client.get('/api/v1.1/servers/detail')
377
        vms_from_api_initial = json.loads(response.content)['servers']['values']
378
        ts = mktime(parsedate(response['Date']))
379
        since = datetime.datetime.fromtimestamp(ts).isoformat() + 'Z'
380
        response = self.client.get('/api/v1.1/servers/detail?changes-since=%s' % since)
381
        self.assertEqual(len(response.content), 0)
382

    
383
        #now create a machine. Then check if it is on the list
384
        request = {
385
                    "server": {
386
                        "name": "new-server-test",
387
                        "imageRef": 1,
388
                        "flavorRef": 1,
389
                        "metadata": {
390
                            "My Server Name": "Apache1"
391
                        },
392
                        "personality": []
393
                    }
394
        }
395

    
396
        path = '/api/v1.1/servers'
397
        response = self.client.post(path, json.dumps(request), content_type='application/json')
398
        self.assertEqual(response.status_code, 202)
399

    
400
        response = self.client.get('/api/v1.1/servers/detail?changes-since=%s' % since)
401
        self.assertEqual(response.status_code, 200)
402
        vms_from_api_after = json.loads(response.content)['servers']['values']
403
        #make sure the newly created server is included on the updated list
404
        self.assertEqual(len(vms_from_api_after), 1)
405

    
406
    def test_reboot_server(self):
407
        """Test if the specified server is rebooted."""
408
        request = {'reboot': {'type': 'HARD'}}
409
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
410
        response = self.client.post(path, json.dumps(request), content_type='application/json')
411
        self.assertEqual(response.status_code, 202)
412
        # server id that does not exist
413
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
414
        response = self.client.post(path, json.dumps(request), content_type='application/json')
415
        self.assertEqual(response.status_code, 404)
416

    
417
    def test_shutdown_server(self):
418
        """Test if the specified server is shutdown."""
419

    
420
        request = {'shutdown': {}}
421
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
422
        response = self.client.post(path, json.dumps(request), content_type='application/json')
423
        self.assertEqual(response.status_code, 202)
424
        # server id that does not exist
425
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
426
        response = self.client.post(path, json.dumps(request), content_type='application/json')
427
        self.assertEqual(response.status_code, 404)
428

    
429
    def test_start_server(self):
430
        """Test if the specified server is started."""
431

    
432
        request = {'start': {}}
433
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
434
        response = self.client.post(path, json.dumps(request), content_type='application/json')
435
        self.assertEqual(response.status_code, 202)
436
        # server id that does not exist
437
        path = '/api/v1.1/servers/%d/action' % self.test_wrong_server_id
438
        response = self.client.post(path, json.dumps(request), content_type='application/json')
439
        self.assertEqual(response.status_code, 404)
440

    
441
    def test_delete_server(self):
442
        """Test if the specified server is deleted."""
443
        response = self.client.delete('/api/v1.1/servers/%d' % self.test_server_id)
444
        self.assertEqual(response.status_code, 204)
445
        # server id that does not exist
446
        response = self.client.delete('/api/v1.1/servers/%d' % self.test_wrong_server_id)
447
        self.assertEqual(response.status_code, 404)
448

    
449
    def test_flavor_list(self):
450
        """Test if the expected list of flavors is returned by."""
451

    
452
        response = self.client.get('/api/v1.1/flavors')
453
        flavors_from_api = json.loads(response.content)['flavors']['values']
454
        flavors_from_db = Flavor.objects.all()
455
        self.assertEqual(len(flavors_from_api), len(flavors_from_db))
456
        self.assertTrue(response.status_code in [200, 203])
457
        for flavor_from_api in flavors_from_api:
458
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
459
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
460
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
461

    
462
    def test_flavors_details(self):
463
        """Test if the flavors details are returned."""
464

    
465
        response = self.client.get('/api/v1.1/flavors/detail')
466
        flavors_from_db = Flavor.objects.all()
467
        flavors_from_api = json.loads(response.content)['flavors']['values']
468

    
469
        # Assert that all flavors in the db appear inthe API call result
470
        for i in range(0, len(flavors_from_db)):
471
            flavor_from_api = flavors_from_api[i]
472
            flavor_from_db = Flavor.objects.get(id=flavors_from_db[i].id)
473
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
474
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
475
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
476
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
477
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
478

    
479
        # Assert that all flavors returned by the API also exist in the db
480
        for flavor_from_api in flavors_from_api:
481
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
482
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
483
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
484
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
485
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
486
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
487

    
488
        # Check if we have the right status_code
489
        self.assertTrue(response.status_code in [200, 203])
490

    
491
    def test_flavor_details(self):
492
        """Test if the expected flavor is returned."""
493

    
494
        response = self.client.get('/api/v1.1/flavors/%d' % self.test_flavor_id)
495
        flavor_from_api = json.loads(response.content)['flavor']
496
        flavor_from_db = Flavor.objects.get(id=self.test_flavor_id)
497
        self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
498
        self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
499
        self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
500
        self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
501
        self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
502
        self.assertTrue(response.status_code in [200, 203])
503

    
504
    def test_wrong_flavor(self):
505
        """Test 404 result when requesting a flavor that does not exist."""
506

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

    
510
    def test_image_list(self):
511
        """Test if the expected list of images is returned by the API."""
512

    
513
        response = self.client.get('/api/v1.1/images')
514
        images_from_api = json.loads(response.content)['images']['values']
515
        images_from_db = Image.objects.all()
516
        self.assertEqual(len(images_from_api), len(images_from_db))
517
        self.assertTrue(response.status_code in [200, 203])
518
        for image_from_api in images_from_api:
519
            image_from_db = Image.objects.get(id=image_from_api['id'])
520
            self.assertEqual(image_from_api['id'], image_from_db.id)
521
            self.assertEqual(image_from_api['name'], image_from_db.name)
522

    
523
    def test_wrong_image(self):
524
        """Test 404 result if a non existent image is requested."""
525

    
526
        response = self.client.get('/api/v1.1/images/%d' % self.test_wrong_image_id)
527
        self.assertEqual(response.status_code, 404)
528

    
529
    def test_server_metadata(self):
530
        """Test server's metadata (add, edit)."""
531

    
532
        key = 'name'
533
        request = {'meta': {key: 'a fancy name'}}
534

    
535
        path = '/api/v1.1/servers/%d/meta/%s' % (self.test_server_id, key)
536
        response = self.client.put(path, json.dumps(request), content_type='application/json')
537
        self.assertEqual(response.status_code, 201)
538

    
539

    
540
def create_flavors(n=1):
541
    for i in range(n):
542
        Flavor.objects.create(
543
            cpu=randint(1, 4),
544
            ram=randint(1, 8) * 512,
545
            disk=randint(1, 40))
546

    
547

    
548
def create_images(n=1):
549
    for i in range(n):
550
        Image.objects.create(
551
            name='Image %d' % (i + 1),
552
            state='ACTIVE',
553
            owner='test')
554

    
555

    
556
def create_image_metadata(n=1):
557
    images = Image.objects.all()
558
    for i in range(n):
559
        ImageMetadata.objects.create(
560
            meta_key='Key%d' % (i + 1),
561
            meta_value='Value %d' % (i + 1),
562
            image=choice(images))
563

    
564

    
565
def create_servers(n=1):
566
    flavors = Flavor.objects.all()
567
    images = Image.objects.all()
568
    for i in range(n):
569
        VirtualMachine.objects.create(
570
            name='Server %d' % (i + 1),
571
            owner='test',
572
            imageid=choice(images).id,
573
            hostid=str(i),
574
            flavor=choice(flavors))
575

    
576

    
577
def create_server_metadata(n=1):
578
    servers = VirtualMachine.objects.all()
579
    for i in range(n):
580
        VirtualMachineMetadata.objects.create(
581
            meta_key='Key%d' % (i + 1),
582
            meta_value='Value %d' % (i + 1),
583
            vm=choice(servers))
584

    
585

    
586
class AssertInvariant(object):
587
    def __init__(self, callable, *args, **kwargs):
588
        self.callable = callable
589
        self.args = args
590
        self.kwargs = kwargs
591

    
592
    def __enter__(self):
593
        self.value = self.callable(*self.args, **self.kwargs)
594
        return self.value
595

    
596
    def __exit__(self, type, value, tb):
597
        assert self.value == self.callable(*self.args, **self.kwargs)
598

    
599

    
600
class BaseTestCase(TestCase):
601
    FLAVORS = 1
602
    IMAGES = 1
603
    SERVERS = 1
604
    SERVER_METADATA = 0
605
    IMAGE_METADATA = 0
606
    fixtures = ['users']
607

    
608
    def setUp(self):
609
        self.client = AaiClient()
610
        create_flavors(self.FLAVORS)
611
        create_images(self.IMAGES)
612
        create_image_metadata(self.IMAGE_METADATA)
613
        create_servers(self.SERVERS)
614
        create_server_metadata(self.SERVER_METADATA)
615

    
616
    def assertFault(self, response, status_code, name):
617
        self.assertEqual(response.status_code, status_code)
618
        fault = json.loads(response.content)
619
        self.assertEqual(fault.keys(), [name])
620

    
621
    def assertBadRequest(self, response):
622
        self.assertFault(response, 400, 'badRequest')
623

    
624
    def assertItemNotFound(self, response):
625
        self.assertFault(response, 404, 'itemNotFound')
626

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

    
638
    def list_metadata(self, path):
639
        response = self.client.get(path)
640
        self.assertTrue(response.status_code in (200, 203))
641
        reply = json.loads(response.content)
642
        self.assertEqual(reply.keys(), ['metadata'])
643
        self.assertEqual(reply['metadata'].keys(), ['values'])
644
        return reply['metadata']['values']
645

    
646
    def list_server_metadata(self, server_id):
647
        path = '/api/v1.1/servers/%d/meta' % server_id
648
        return self.list_metadata(path)
649

    
650
    def list_image_metadata(self, image_id):
651
        path = '/api/v1.1/images/%d/meta' % image_id
652
        return self.list_metadata(path)
653

    
654
    def update_metadata(self, path, metadata):
655
        data = json.dumps({'metadata': metadata})
656
        response = self.client.post(path, data, content_type='application/json')
657
        self.assertEqual(response.status_code, 201)
658
        reply = json.loads(response.content)
659
        self.assertEqual(reply.keys(), ['metadata'])
660
        return reply['metadata']
661

    
662
    def update_server_metadata(self, server_id, metadata):
663
        path = '/api/v1.1/servers/%d/meta' % server_id
664
        return self.update_metadata(path, metadata)
665

    
666
    def update_image_metadata(self, image_id, metadata):
667
        path = '/api/v1.1/images/%d/meta' % image_id
668
        return self.update_metadata(path, metadata)
669

    
670
    def create_server_meta(self, server_id, meta):
671
        key = meta.keys()[0]
672
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
673
        data = json.dumps({'meta': meta})
674
        response = self.client.put(path, data, content_type='application/json')
675
        self.assertEqual(response.status_code, 201)
676
        reply = json.loads(response.content)
677
        self.assertEqual(reply.keys(), ['meta'])
678
        response_meta = reply['meta']
679
        self.assertEqual(response_meta, meta)
680

    
681
    def get_all_server_metadata(self):
682
        metadata = defaultdict(dict)
683
        for m in VirtualMachineMetadata.objects.all():
684
            metadata[m.vm.id][m.meta_key] = m.meta_value
685
        return metadata
686

    
687
    def get_all_image_metadata(self):
688
        metadata = defaultdict(dict)
689
        for m in ImageMetadata.objects.all():
690
            metadata[m.image.id][m.meta_key] = m.meta_value
691
        return metadata
692

    
693
    def list_networks(self, detail=False):
694
        path = '/api/v1.1/networks'
695
        if detail:
696
            path += '/detail'
697
        response = self.client.get(path)
698
        self.assertTrue(response.status_code in (200, 203))
699
        reply = json.loads(response.content)
700
        self.assertEqual(reply.keys(), ['networks'])
701
        self.assertEqual(reply['networks'].keys(), ['values'])
702
        return reply['networks']['values']
703

    
704
    def create_network(self, name):
705
        with astakos_user("admin@adminland.com"):
706
            path = '/api/v1.1/networks'
707
            data = json.dumps({'network': {'name': name}})
708
            response = self.client.post(path, data, content_type='application/json')
709
            self.assertEqual(response.status_code, 202)
710
            reply = json.loads(response.content)
711
            self.assertEqual(reply.keys(), ['network'])
712
            return reply
713

    
714
    def get_network_details(self, network_id):
715
        path = '/api/v1.1/networks/%s' % network_id
716
        response = self.client.get(path)
717
        self.assertEqual(response.status_code, 200)
718
        reply = json.loads(response.content)
719
        self.assertEqual(reply.keys(), ['network'])
720
        return reply['network']
721

    
722
    def update_network_name(self, network_id, new_name):
723
        path = '/api/v1.1/networks/%s' % network_id
724
        data = json.dumps({'network': {'name': new_name}})
725
        response = self.client.put(path, data, content_type='application/json')
726
        self.assertEqual(response.status_code, 204)
727

    
728
    def delete_network(self, network_id):
729
        path = '/api/v1.1/networks/%s' % network_id
730
        response = self.client.delete(path)
731
        self.assertEqual(response.status_code, 204)
732

    
733
    def add_to_network(self, network_id, server_id):
734
        path = '/api/v1.1/networks/%s/action' % network_id
735
        data = json.dumps({'add': {'serverRef': server_id}})
736
        response = self.client.post(path, data, content_type='application/json')
737
        self.assertEqual(response.status_code, 202)
738

    
739
    def remove_from_network(self, network_id, server_id):
740
        path = '/api/v1.1/networks/%s/action' % network_id
741
        data = json.dumps({'remove': {'serverRef': server_id}})
742
        response = self.client.post(path, data, content_type='application/json')
743
        self.assertEqual(response.status_code, 202)
744

    
745

    
746
def popdict(l, **kwargs):
747
    """Pops a dict from list `l` based on the predicates given as `kwargs`."""
748

    
749
    for i in range(len(l)):
750
        item = l[i]
751
        match = True
752
        for key, val in kwargs.items():
753
            if item[key] != val:
754
                match = False
755
                break
756
        if match:
757
            del l[i]
758
            return item
759
    return None
760

    
761

    
762
class ListImages(BaseTestCase):
763
    IMAGES = 10
764

    
765
    def test_list_images(self):
766
        images = self.list_images()
767
        keys = set(['id', 'name'])
768
        for img in Image.objects.all():
769
            image = popdict(images, id=img.id)
770
            self.assertTrue(image is not None)
771
            self.assertEqual(set(image.keys()), keys)
772
            self.assertEqual(image['id'], img.id)
773
            self.assertEqual(image['name'], img.name)
774
        self.assertEqual(images, [])
775

    
776
    def test_list_images_detail(self):
777
        images = self.list_images(detail=True)
778
        keys = set(['id', 'name', 'updated', 'created', 'status', 'progress'])
779
        for img in Image.objects.all():
780
            image = popdict(images, id=img.id)
781
            self.assertTrue(image is not None)
782
            self.assertEqual(set(image.keys()), keys)
783
            self.assertEqual(image['id'], img.id)
784
            self.assertEqual(image['name'], img.name)
785
            self.assertEqual(image['status'], img.state)
786
            self.assertEqual(image['progress'], 100 if img.state == 'ACTIVE' else 0)
787
        self.assertEqual(images, [])
788

    
789

    
790
class ListServerMetadata(BaseTestCase):
791
    SERVERS = 5
792
    SERVER_METADATA = 100
793

    
794
    def test_list_metadata(self):
795
        with AssertInvariant(self.get_all_server_metadata) as metadata:
796
            for vm in VirtualMachine.objects.all():
797
                response_metadata = self.list_server_metadata(vm.id)
798
                self.assertEqual(response_metadata, metadata[vm.id])
799

    
800
    def test_invalid_server(self):
801
        with AssertInvariant(self.get_all_server_metadata):
802
            response = self.client.get('/api/v1.1/servers/0/meta')
803
            self.assertItemNotFound(response)
804

    
805

    
806
class UpdateServerMetadata(BaseTestCase):
807
    SERVER_METADATA = 10
808

    
809
    def test_update_metadata(self):
810
        metadata = self.get_all_server_metadata()
811
        server_id = choice(metadata.keys())
812
        new_metadata = {}
813
        for key in sample(metadata[server_id].keys(), 3):
814
            new_metadata[key] = 'New %s value' % key
815
        response_metadata = self.update_server_metadata(server_id,
816
                                                        new_metadata)
817
        metadata[server_id].update(new_metadata)
818
        self.assertEqual(response_metadata, metadata[server_id])
819
        self.assertEqual(metadata, self.get_all_server_metadata())
820

    
821
    def test_invalid_data(self):
822
        with AssertInvariant(self.get_all_server_metadata) as metadata:
823
            server_id = choice(metadata.keys())
824
            path = '/api/v1.1/servers/%d/meta' % server_id
825
            response = self.client.post(path, 'metadata', content_type='application/json')
826
            self.assertBadRequest(response)
827

    
828
    def test_invalid_server(self):
829
        with AssertInvariant(self.get_all_server_metadata):
830
            path = '/api/v1.1/servers/0/meta'
831
            data = json.dumps({'metadata': {'Key1': 'A Value'}})
832
            response = self.client.post(path, data, content_type='application/json')
833
            self.assertItemNotFound(response)
834

    
835

    
836
class GetServerMetadataItem(BaseTestCase):
837
    SERVERS = 5
838
    SERVER_METADATA = 100
839

    
840
    def test_get_metadata_item(self):
841
        with AssertInvariant(self.get_all_server_metadata) as metadata:
842
            server_id = choice(metadata.keys())
843
            key = choice(metadata[server_id].keys())
844
            path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
845
            response = self.client.get(path)
846
            self.assertTrue(response.status_code in (200, 203))
847
            reply = json.loads(response.content)
848
            self.assertEqual(reply['meta'], {key: metadata[server_id][key]})
849

    
850
    def test_invalid_key(self):
851
        with AssertInvariant(self.get_all_server_metadata) as metadata:
852
            server_id = choice(metadata.keys())
853
            response = self.client.get('/api/v1.1/servers/%d/meta/foo' % server_id)
854
            self.assertItemNotFound(response)
855

    
856
    def test_invalid_server(self):
857
        with AssertInvariant(self.get_all_server_metadata):
858
            response = self.client.get('/api/v1.1/servers/0/meta/foo')
859
            self.assertItemNotFound(response)
860

    
861

    
862
class CreateServerMetadataItem(BaseTestCase):
863
    SERVER_METADATA = 10
864

    
865
    def test_create_metadata(self):
866
        metadata = self.get_all_server_metadata()
867
        server_id = choice(metadata.keys())
868
        meta = {'Foo': 'Bar'}
869
        self.create_server_meta(server_id, meta)
870
        metadata[server_id].update(meta)
871
        self.assertEqual(metadata, self.get_all_server_metadata())
872

    
873
    def test_update_metadata(self):
874
        metadata = self.get_all_server_metadata()
875
        server_id = choice(metadata.keys())
876
        key = choice(metadata[server_id].keys())
877
        meta = {key: 'New Value'}
878
        self.create_server_meta(server_id, meta)
879
        metadata[server_id].update(meta)
880
        self.assertEqual(metadata, self.get_all_server_metadata())
881

    
882
    def test_invalid_server(self):
883
        with AssertInvariant(self.get_all_server_metadata):
884
            path = '/api/v1.1/servers/0/meta/foo'
885
            data = json.dumps({'meta': {'foo': 'bar'}})
886
            response = self.client.put(path, data, content_type='application/json')
887
            self.assertItemNotFound(response)
888

    
889
    def test_invalid_key(self):
890
        with AssertInvariant(self.get_all_server_metadata) as metadata:
891
            server_id = choice(metadata.keys())
892
            path = '/api/v1.1/servers/%d/meta/baz' % server_id
893
            data = json.dumps({'meta': {'foo': 'bar'}})
894
            response = self.client.put(path, data, content_type='application/json')
895
            self.assertBadRequest(response)
896

    
897
    def test_invalid_data(self):
898
        with AssertInvariant(self.get_all_server_metadata) as metadata:
899
            server_id = choice(metadata.keys())
900
            path = '/api/v1.1/servers/%d/meta/foo' % server_id
901
            response = self.client.put(path, 'meta', content_type='application/json')
902
            self.assertBadRequest(response)
903

    
904

    
905
class DeleteServerMetadataItem(BaseTestCase):
906
    SERVER_METADATA = 10
907

    
908
    def test_delete_metadata(self):
909
        metadata = self.get_all_server_metadata()
910
        server_id = choice(metadata.keys())
911
        key = choice(metadata[server_id].keys())
912
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
913
        response = self.client.delete(path)
914
        self.assertEqual(response.status_code, 204)
915
        metadata[server_id].pop(key)
916
        self.assertEqual(metadata, self.get_all_server_metadata())
917

    
918
    def test_invalid_server(self):
919
        with AssertInvariant(self.get_all_server_metadata):
920
            response = self.client.delete('/api/v1.1/servers/9/meta/Key1')
921
            self.assertItemNotFound(response)
922

    
923
    def test_invalid_key(self):
924
        with AssertInvariant(self.get_all_server_metadata) as metadata:
925
            server_id = choice(metadata.keys())
926
            path = '/api/v1.1/servers/%d/meta/foo' % server_id
927
            response = self.client.delete(path)
928
            self.assertItemNotFound(response)
929

    
930

    
931
class ListImageMetadata(BaseTestCase):
932
    IMAGES = 5
933
    IMAGE_METADATA = 100
934

    
935
    def test_list_metadata(self):
936
        with AssertInvariant(self.get_all_image_metadata) as metadata:
937
            for image in Image.objects.all():
938
                response_metadata = self.list_image_metadata(image.id)
939
                self.assertEqual(response_metadata, metadata[image.id])
940

    
941
    def test_invalid_image(self):
942
        with AssertInvariant(self.get_all_image_metadata):
943
            response = self.client.get('/api/v1.1/images/0/meta')
944
            self.assertItemNotFound(response)
945

    
946

    
947
class UpdateImageMetadata(BaseTestCase):
948
    IMAGE_METADATA = 10
949

    
950
    def test_update_metadata(self):
951
        metadata = self.get_all_image_metadata()
952
        image_id = choice(metadata.keys())
953
        new_metadata = {}
954
        for key in sample(metadata[image_id].keys(), 3):
955
            new_metadata[key] = 'New %s value' % key
956
        response_metadata = self.update_image_metadata(image_id, new_metadata)
957
        metadata[image_id].update(new_metadata)
958
        self.assertEqual(response_metadata, metadata[image_id])
959
        self.assertEqual(metadata, self.get_all_image_metadata())
960

    
961
    def test_invalid_data(self):
962
        with AssertInvariant(self.get_all_image_metadata) as metadata:
963
            image_id = choice(metadata.keys())
964
            path = '/api/v1.1/images/%d/meta' % image_id
965
            response = self.client.post(path, 'metadata', content_type='application/json')
966
            self.assertBadRequest(response)
967

    
968
    def test_invalid_server(self):
969
        with AssertInvariant(self.get_all_image_metadata):
970
            path = '/api/v1.1/images/0/meta'
971
            data = json.dumps({'metadata': {'Key1': 'A Value'}})
972
            response = self.client.post(path, data, content_type='application/json')
973
            self.assertItemNotFound(response)
974

    
975

    
976
class ServerVNCConsole(BaseTestCase):
977
    SERVERS = 1
978

    
979
    def test_not_active_server(self):
980
        """Test console req for server not in ACTIVE state returns badRequest"""
981
        server_id = choice(VirtualMachine.objects.all()).id
982
        path = '/api/v1.1/servers/%d/action' % server_id
983
        data = json.dumps({'console': {'type': 'vnc'}})
984
        response = self.client.post(path, data, content_type='application/json')
985
        self.assertBadRequest(response)
986

    
987
    def test_active_server(self):
988
        """Test console req for ACTIVE server"""
989
        server_id = choice(VirtualMachine.objects.all()).id
990
        # FIXME: Start the server properly, instead of tampering with the DB
991
        vm = choice(VirtualMachine.objects.all())
992
        vm.operstate = 'STARTED'
993
        vm.save()
994
        server_id = vm.id
995

    
996
        path = '/api/v1.1/servers/%d/action' % server_id
997
        data = json.dumps({'console': {'type': 'vnc'}})
998
        response = self.client.post(path, data, content_type='application/json')
999
        self.assertEqual(response.status_code, 200)
1000
        reply = json.loads(response.content)
1001
        self.assertEqual(reply.keys(), ['console'])
1002
        console = reply['console']
1003
        self.assertEqual(console['type'], 'vnc')
1004
        self.assertEqual(set(console.keys()), set(['type', 'host', 'port', 'password']))
1005

    
1006

    
1007
class AaiTestCase(TestCase):
1008
    fixtures = ['users', 'api_test_data', 'auth_test_data']
1009
    apibase = '/api/v1.1'
1010

    
1011
    def setUp(self):
1012
        self.client = Client()
1013

    
1014
    def test_fail_oapi_auth(self):
1015
        """ test authentication from not registered user using OpenAPI
1016
        """
1017
        response = self.client.get(self.apibase + '/servers', {},
1018
                                   **{'X-Auth-User': 'notme',
1019
                                      'X-Auth-Key': '0xdeadbabe',
1020
                                      'TEST-AAI': 'true'})
1021
        self.assertEquals(response.status_code, 401)
1022

    
1023
    def test_oapi_auth(self):
1024
        """authentication with user registration
1025
        """
1026
        response = self.client.get(self.apibase + '/index.html', {},
1027
                                   **{'X-Auth-User': 'testdbuser',
1028
                                      'X-Auth-Key': 'test@synnefo.gr',
1029
                                      'TEST-AAI': 'true'})
1030
        self.assertEquals(response.status_code, 204)
1031
        self.assertNotEqual(response['X-Auth-Token'], None)
1032
        self.assertEquals(response['X-Server-Management-Url'], '')
1033
        self.assertEquals(response['X-Storage-Url'], '')
1034
        self.assertEquals(response['X-CDN-Management-Url'], '')
1035

    
1036
    def test_unauthorized_call(self):
1037
        request = {'reboot': {'type': 'HARD'}}
1038
        path = '/api/v1.1/servers/%d/action' % 1
1039
        response = self.client.post(path, json.dumps(request),
1040
                                    content_type='application/json')
1041
        self.assertEquals(response.status_code, 401)
1042

    
1043

    
1044
class ListNetworks(BaseTestCase):
1045
    SERVERS = 5
1046

    
1047
    def setUp(self):
1048
        BaseTestCase.setUp(self)
1049

    
1050
        for i in range(5):
1051
            self.create_network('net%d' % i)
1052

    
1053
        machines = VirtualMachine.objects.all()
1054
        for network in Network.objects.all():
1055
            n = randint(0, self.SERVERS - 1)
1056
            for machine in sample(machines, n):
1057
                machine.nics.create(network=network)
1058

    
1059
    def test_list_networks(self):
1060
        networks = self.list_networks()
1061
        for net in Network.objects.all():
1062
            net_id = str(net.id) if not net.public else 'public'
1063
            network = popdict(networks, id=net_id)
1064
            self.assertEqual(network['name'], net.name)
1065
        self.assertEqual(networks, [])
1066

    
1067
    def test_list_networks_detail(self):
1068
        networks = self.list_networks(detail=True)
1069
        for net in Network.objects.all():
1070
            net_id = str(net.id) if not net.public else 'public'
1071
            network = popdict(networks, id=net_id)
1072
            self.assertEqual(network['name'], net.name)
1073
            machines = set(vm.id for vm in net.machines.all())
1074
            self.assertEqual(set(network['servers']['values']), machines)
1075
        self.assertEqual(networks, [])
1076

    
1077

    
1078
class CreateNetwork(BaseTestCase):
1079
    def test_create_network(self):
1080
        before = self.list_networks()
1081
        self.create_network('net')
1082
        after = self.list_networks()
1083
        self.assertEqual(len(after) - len(before), 1)
1084
        found = False
1085
        for network in after:
1086
            if network['name'] == 'net':
1087
                found = True
1088
                break
1089
        self.assertTrue(found)
1090

    
1091

    
1092
class GetNetworkDetails(BaseTestCase):
1093
    SERVERS = 5
1094

    
1095
    def test_get_network_details(self):
1096
        name = 'net'
1097
        self.create_network(name)
1098

    
1099
        servers = VirtualMachine.objects.all()
1100
        network = Network.objects.all()[1]
1101

    
1102
        net = self.get_network_details(network.id)
1103
        self.assertEqual(net['name'], name)
1104
        self.assertEqual(net['servers']['values'], [])
1105

    
1106
        server_id = choice(servers).id
1107
        self.add_to_network(network.id, server_id)
1108
        net = self.get_network_details(network.id)
1109
        self.assertEqual(net['name'], network.name)
1110

    
1111

    
1112
class UpdateNetworkName(BaseTestCase):
1113
    def test_update_network_name(self):
1114
        name = 'net'
1115
        self.create_network(name)
1116
        networks = self.list_networks(detail=True)
1117
        priv = [net for net in networks if net['id'] != 'public']
1118
        network = choice(priv)
1119
        network_id = network['id']
1120
        new_name = network['name'] + '_2'
1121
        self.update_network_name(network_id, new_name)
1122

    
1123
        network['name'] = new_name
1124
        del network['updated']
1125
        net = self.get_network_details(network_id)
1126
        del net['updated']
1127
        self.assertEqual(net, network)
1128

    
1129

    
1130
class DeleteNetwork(BaseTestCase):
1131
    def test_delete_network(self):
1132
        for i in range(5):
1133
            self.create_network('net%d' % i)
1134

    
1135
        networks = self.list_networks()
1136
        priv = [net for net in networks if net['id'] != 'public']
1137
        network = choice(priv)
1138
        network_id = network['id']
1139
        self.delete_network(network_id)
1140

    
1141
        net = self.get_network_details(network_id)
1142
        self.assertEqual(net['status'], 'DELETED')
1143

    
1144
        priv.remove(network)
1145
        networks = self.list_networks()
1146
        new_priv = [net for net in networks if net['id'] != 'public']
1147
        self.assertEqual(priv, new_priv)
1148

    
1149

    
1150
class NetworkActions(BaseTestCase):
1151
    SERVERS = 20
1152

    
1153
    def test_add_remove_server(self):
1154
        self.create_network('net')
1155

    
1156
        server_ids = [vm.id for vm in VirtualMachine.objects.all()]
1157
        network = self.list_networks(detail=True)[1]
1158
        network_id = network['id']
1159

    
1160
        to_add = set(sample(server_ids, 10))
1161
        for server_id in to_add:
1162
            self.add_to_network(network_id, server_id)
1163

    
1164
        to_remove = set(sample(to_add, 5))
1165
        for server_id in to_remove:
1166
            self.remove_from_network(network_id, server_id)