Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (37 kB)

1
# Copyright 2012 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 django.utils import simplejson as json
37
from django.test import TestCase
38
from django.test.client import Client
39

    
40
from synnefo.db.models import *
41
from synnefo.logic.utils import get_rsapi_state
42

    
43
from mock import patch, Mock
44

    
45
from contextlib import contextmanager
46
from synnefo.db import models_factory as mfactory
47

    
48
from synnefo.api import util
49
from synnefo.api import faults
50

    
51

    
52
def get_image_mock(request, *Args, **kwargs):
53
    return {'backend_id': '1234',
54
            'location': 'pithos://dummyimage',
55
            'disk_format': 'drbd'}
56
util.get_image = get_image_mock
57

    
58

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

64
    usage:
65
    with astakos_user("user@user.com"):
66
        .... make api calls ....
67

68
    """
69
    from synnefo.lib import astakos
70
    from synnefo.api import util
71
    orig_method = astakos.get_user
72

    
73
    def dummy_get_user(request, *args, **kwargs):
74
        request.user = {'username': user, 'groups': []}
75
        request.user_uniq = user
76
    astakos.get_user = dummy_get_user
77
    util.get_user = dummy_get_user
78
    yield
79
    astakos.get_user = orig_method
80

    
81

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

    
87

    
88
def create_flavors(num):
89
    return [mfactory.FlavorFactory() for x in range(num)]
90

    
91

    
92
class APITest(TestCase):
93

    
94
    def test_api_version(self):
95
        """Check API version."""
96
        with astakos_user('user'):
97
            response = self.client.get('/api/v1.1/')
98
        self.assertEqual(response.status_code, 200)
99
        api_version = json.loads(response.content)['version']
100
        self.assertEqual(api_version['id'], 'v1.1')
101
        self.assertEqual(api_version['status'], 'CURRENT')
102

    
103

    
104
class BaseTestCase(TestCase):
105
    def get(self, url, user='user'):
106
        with astakos_user(user):
107
            response = self.client.get(url)
108
        return response
109

    
110
    def delete(self, url, user='user'):
111
        with astakos_user(user):
112
            response = self.client.delete(url)
113
        return response
114

    
115
    def post(self, url, user='user', params={}, ctype='json'):
116
        if ctype == 'json':
117
            content_type = 'application/json'
118
        with astakos_user(user):
119
            response = self.client.post(url, params, content_type=content_type)
120
        return response
121

    
122
    def put(self, url, user='user', params={}, ctype='json'):
123
        if ctype == 'json':
124
            content_type = 'application/json'
125
        with astakos_user(user):
126
            response = self.client.put(url, params, content_type=content_type)
127
        return response
128

    
129
    def assertSuccess(self, response):
130
        self.assertTrue(response.status_code in [200, 203])
131

    
132
    def assertFault(self, response, status_code, name):
133
        self.assertEqual(response.status_code, status_code)
134
        fault = json.loads(response.content)
135
        self.assertEqual(fault.keys(), [name])
136

    
137
    def assertBadRequest(self, response):
138
        self.assertFault(response, 400, 'badRequest')
139

    
140
    def assertItemNotFound(self, response):
141
        self.assertFault(response, 404, 'itemNotFound')
142

    
143

    
144
class FlavorAPITest(BaseTestCase):
145

    
146
    def setUp(self):
147
        self.flavor1 = mfactory.FlavorFactory()
148
        self.flavor2 = mfactory.FlavorFactory(deleted=True)
149
        self.flavor3 = mfactory.FlavorFactory()
150

    
151
    def test_flavor_list(self):
152
        """Test if the expected list of flavors is returned by."""
153

    
154
        # with astakos_user('user'):
155
        #     response = self.client.get('/api/v1.1/flavors')
156
        response = self.get('/api/v1.1/flavors')
157

    
158
        flavors_from_api = json.loads(response.content)['flavors']['values']
159
        flavors_from_db = Flavor.objects.filter(deleted=False)
160
        self.assertEqual(len(flavors_from_api), len(flavors_from_db))
161
        self.assertTrue(response.status_code in [200, 203])
162
        for flavor_from_api in flavors_from_api:
163
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
164
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
165
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
166

    
167
    def test_flavors_details(self):
168
        """Test if the flavors details are returned."""
169
        response = self.get('/api/v1.1/flavors/detail')
170

    
171
        flavors_from_db = Flavor.objects.filter(deleted=False)
172
        flavors_from_api = json.loads(response.content)['flavors']['values']
173

    
174
        # Assert that all flavors in the db appear inthe API call result
175
        for i in range(0, len(flavors_from_db)):
176
            flavor_from_api = flavors_from_api[i]
177
            flavor_from_db = Flavor.objects.get(id=flavors_from_db[i].id)
178
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
179
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
180
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
181
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
182
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
183

    
184
        # Assert that all flavors returned by the API also exist in the db
185
        for flavor_from_api in flavors_from_api:
186
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
187
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
188
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
189
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
190
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
191
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
192

    
193
        # Check if we have the right status_code
194
        self.assertTrue(response.status_code in [200, 203])
195

    
196
    def test_flavor_details(self):
197
        """Test if the expected flavor is returned."""
198
        flavor = self.flavor3
199

    
200
        response = self.get('/api/v1.1/flavors/%d' % flavor.id)
201

    
202
        flavor_from_api = json.loads(response.content)['flavor']
203
        flavor_from_db = Flavor.objects.get(id=flavor.id)
204
        self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
205
        self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
206
        self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
207
        self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
208
        self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
209
        self.assertTrue(response.status_code in [200, 203])
210

    
211
    def test_deleted_flavor_details(self):
212
        """Test that API returns details for deleted flavors"""
213
        flavor = self.flavor2
214
        response = self.get('/api/v1.1/flavors/%d' % flavor.id)
215
        flavor_from_api = json.loads(response.content)['flavor']
216
        self.assertEqual(response.status_code, 200)
217
        self.assertEquals(flavor_from_api['name'], flavor.name)
218

    
219
    def test_deleted_flavors_list(self):
220
        """Test that deleted flavors do not appear to flavors list"""
221
        response = self.get('/api/v1.1/flavors')
222
        flavors_from_api = json.loads(response.content)['flavors']['values']
223
        self.assertEqual(len(flavors_from_api), 2)
224

    
225
    def test_deleted_flavors_details(self):
226
        """Test that deleted flavors do not appear to flavors detail list"""
227
        mfactory.FlavorFactory(deleted=True)
228
        response = self.get('/api/v1.1/flavors/detail')
229
        flavors_from_api = json.loads(response.content)['flavors']['values']
230
        self.assertEqual(len(flavors_from_api), 2)
231

    
232
    def test_wrong_flavor(self):
233
        """Test 404 result when requesting a flavor that does not exist."""
234

    
235
        response = self.get('/api/v1.1/flavors/%d' % 22)
236
        self.assertTrue(response.status_code in [404])
237

    
238

    
239
class ServerAPITest(BaseTestCase):
240
    def setUp(self):
241
        self.user1 = 'user1'
242
        self.user2 = 'user2'
243
        self.vm1 = mfactory.VirtualMachineFactory(userid=self.user1)
244
        self.vm2 = mfactory.VirtualMachineFactory(userid=self.user2)
245
        self.vm3 = mfactory.VirtualMachineFactory(deleted=True,
246
                                                  userid=self.user1)
247
        self.vm4 = mfactory.VirtualMachineFactory(userid=self.user2)
248

    
249
    def test_server_list_1(self):
250
        """Test if the expected list of servers is returned."""
251
        response = self.get('/api/v1.1/servers')
252
        self.assertTrue(response.status_code in [200, 203])
253
        servers = json.loads(response.content)['servers']['values']
254
        self.assertEqual(servers, [])
255

    
256
    def test_server_list_2(self):
257
        """Test if the expected list of servers is returned."""
258
        response = self.get('/api/v1.1/servers', self.user1)
259
        self.assertTrue(response.status_code in [200, 203])
260
        servers = json.loads(response.content)['servers']['values']
261
        db_server = self.vm1
262
        self.assertEqual(servers, [{'name': db_server.name,
263
                                    'id': db_server.id}])
264

    
265
    def test_server_list_detail(self):
266
        """Test if the servers list details are returned."""
267
        user = self.user2
268
        user_vms = {self.vm2.id: self.vm2,
269
                    self.vm4.id: self.vm4}
270

    
271
        response = self.get('/api/v1.1/servers/detail', user)
272
        servers = json.loads(response.content)['servers']['values']
273
        self.assertEqual(len(servers), len(user_vms))
274
        for api_vm in servers:
275
            db_vm = user_vms[api_vm['id']]
276
            self.assertEqual(api_vm['flavorRef'], db_vm.flavor.id)
277
            self.assertEqual(api_vm['hostId'], db_vm.hostid)
278
            self.assertEqual(api_vm['id'], db_vm.id)
279
            self.assertEqual(api_vm['imageRef'], db_vm.imageid)
280
            self.assertEqual(api_vm['name'], db_vm.name)
281
            self.assertEqual(api_vm['status'], get_rsapi_state(db_vm))
282
            self.assertTrue(response.status_code in [200, 203])
283

    
284
    def test_server_detail(self):
285
        """Test if a server details are returned."""
286
        db_vm = self.vm2
287
        user = self.vm2.userid
288
        db_vm_meta = mfactory.VirtualMachineMetadataFactory(vm=db_vm)
289

    
290
        response = self.get('/api/v1.1/servers/%d' % db_vm.id, user)
291
        server = json.loads(response.content)['server']
292

    
293
        self.assertEqual(server['flavorRef'], db_vm.flavor.id)
294
        self.assertEqual(server['hostId'], db_vm.hostid)
295
        self.assertEqual(server['id'], db_vm.id)
296
        self.assertEqual(server['imageRef'], db_vm.imageid)
297
        self.assertEqual(server['name'], db_vm.name)
298
        self.assertEqual(server['status'], get_rsapi_state(db_vm))
299

    
300
        metadata = server['metadata']['values']
301
        self.assertEqual(len(metadata), 1)
302
        self.assertEqual(metadata[db_vm_meta.meta_key], db_vm_meta.meta_value)
303
        self.assertTrue(response.status_code in [200, 203])
304

    
305
    def test_noauthorized(self):
306
        """Test 404 for detail of other user vm"""
307
        db_vm = self.vm2
308

    
309
        response = self.get('/api/v1.1/servers/%d' % db_vm.id, 'wrong_user')
310
        self.assertEqual(response.status_code, 404)
311

    
312
    def test_wrong_server(self):
313
        """Test 404 response if server does not exist."""
314
        response = self.get('/api/v1.1/servers/%d' % 5000)
315
        self.assertEqual(response.status_code, 404)
316

    
317
    def test_create_server_empty(self):
318
        """Test if the create server call returns a 400 badRequest if
319
           no attributes are specified."""
320

    
321
        response = self.post('/api/v1.1/servers', params={})
322
        self.assertEqual(response.status_code, 400)
323

    
324

    
325
@patch('synnefo.api.util.get_image')
326
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
327
class ServerCreateAPITest(BaseTestCase):
328
    def test_create_server(self, mrapi, mimage):
329
        """Test if the create server call returns the expected response
330
           if a valid request has been speficied."""
331
        mimage.return_value = {'location': 'pithos://foo',
332
                               'disk_format': 'diskdump'}
333
        mrapi().CreateInstance.return_value = 12
334
        flavor = mfactory.FlavorFactory()
335
        # Create public network and backend
336
        network = mfactory.NetworkFactory(public=True)
337
        backend = mfactory.BackendFactory()
338
        mfactory.BackendNetworkFactory(network=network, backend=backend)
339

    
340
        request = {
341
                    "server": {
342
                        "name": "new-server-test",
343
                        "userid": "test_user",
344
                        "imageRef": 1,
345
                        "flavorRef": flavor.id,
346
                        "metadata": {
347
                            "My Server Name": "Apache1"
348
                        },
349
                        "personality": []
350
                    }
351
        }
352
        response = self.post('/api/v1.1/servers', 'test_user',
353
                                 json.dumps(request), 'json')
354
        self.assertEqual(response.status_code, 202)
355
        mrapi().CreateInstance.assert_called_once()
356

    
357
        api_server = json.loads(response.content)['server']
358
        self.assertEqual(api_server['status'], "BUILD")
359
        self.assertEqual(api_server['progress'], 0)
360
        self.assertEqual(api_server['metadata']['values'],
361
                        {"My Server Name":  "Apache1"})
362
        self.assertTrue('adminPass' in api_server)
363

    
364
        db_vm = VirtualMachine.objects.get(userid='test_user')
365
        self.assertEqual(api_server['name'], db_vm.name)
366
        self.assertEqual(api_server['status'], db_vm.operstate)
367

    
368
    def test_create_server_no_flavor(self, mrapi, mimage):
369
        request = {
370
                    "server": {
371
                        "name": "new-server-test",
372
                        "userid": "test_user",
373
                        "imageRef": 1,
374
                        "flavorRef": 42,
375
                        "metadata": {
376
                            "My Server Name": "Apache1"
377
                        },
378
                        "personality": []
379
                    }
380
        }
381
        response = self.post('/api/v1.1/servers', 'test_user',
382
                                 json.dumps(request), 'json')
383
        self.assertItemNotFound(response)
384

    
385

    
386
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
387
class ServerDestroyAPITest(BaseTestCase):
388
    def test_delete_server(self, mrapi):
389
        vm = mfactory.VirtualMachineFactory()
390
        response = self.delete('/api/v1.1/servers/%d' % vm.id, vm.userid)
391
        self.assertEqual(response.status_code, 204)
392
        mrapi().DeleteInstance.assert_called_once()
393

    
394
    def test_non_existing_delete_server(self, mrapi):
395
        vm = mfactory.VirtualMachineFactory()
396
        response = self.delete('/api/v1.1/servers/%d' % 42, vm.userid)
397
        self.assertItemNotFound(response)
398
        mrapi().DeleteInstance.assert_not_called()
399

    
400

    
401
@patch('synnefo.api.util.get_image')
402
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
403
class ServerActionAPITest(BaseTestCase):
404
    def test_actions(self, mrapi, mimage):
405
        actions = ['start', 'shutdown', 'reboot']
406
        vm = mfactory.VirtualMachineFactory()
407
        vm.operstate = "STOPPED"
408
        vm.save()
409
        for action in actions:
410
            val = {'type': 'HARD'} if action == 'reboot' else {}
411
            request = {action: val}
412
            response = self.post('/api/v1.1/servers/%d/action' % vm.id,
413
                                vm.userid, json.dumps(request), 'json')
414
            self.assertEqual(response.status_code, 202)
415
            if action == 'shutdown':
416
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).action,
417
                                 "STOP")
418
            else:
419
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).action,
420
                                 action.upper())
421

    
422
    def test_firewall(self, mrapi, mimage):
423
        vm = mfactory.VirtualMachineFactory()
424
        vm.operstate = "STOPPED"
425
        vm.save()
426
        request = {'firewallProfile': {'profile': 'PROTECTED'}}
427
        response = self.post('/api/v1.1/servers/%d/action' % vm.id,
428
                             vm.userid, json.dumps(request), 'json')
429
        self.assertEqual(response.status_code, 202)
430
        mrapi().ModifyInstance.assert_called_once()
431

    
432

    
433
class ServerMetadataAPITest(BaseTestCase):
434
    def setUp(self):
435
        self.vm = mfactory.VirtualMachineFactory()
436
        self.metadata = mfactory.VirtualMachineMetadataFactory(vm=self.vm)
437

    
438
    def test_get_metadata(self):
439
        vm = self.vm
440
        create_meta = lambda: mfactory.VirtualMachineMetadataFactory(vm=vm)
441
        metadata = [create_meta(), create_meta(), create_meta()]
442
        response = self.get('/api/v1.1/servers/%d/meta' % vm.id, vm.userid)
443
        self.assertTrue(response.status_code in [200, 203])
444
        api_metadata = json.loads(response.content)['metadata']['values']
445
        self.assertEqual(len(api_metadata), len(metadata) + 1)
446
        for db_m in metadata:
447
            self.assertEqual(api_metadata[db_m.meta_key], db_m.meta_value)
448

    
449
        request = {'metadata':
450
                        {'foo': 'bar'},
451
                        metadata[0].meta_key: 'bar2'
452
                  }
453
        response = self.post('/api/v1.1/servers/%d/meta' % vm.id, vm.userid,
454
                             json.dumps(request), 'json')
455
        metadata2 = VirtualMachineMetadata.objects.filter(vm=vm)
456
        response = self.get('/api/v1.1/servers/%d/meta' % vm.id, vm.userid)
457
        self.assertTrue(response.status_code in [200, 203])
458
        api_metadata2 = json.loads(response.content)['metadata']['values']
459
        self.assertTrue('foo' in api_metadata2.keys())
460
        self.assertTrue(api_metadata2[metadata[0].meta_key], 'bar2')
461
        self.assertEqual(len(api_metadata2), len(metadata2))
462
        for db_m in metadata2:
463
            self.assertEqual(api_metadata2[db_m.meta_key], db_m.meta_value)
464

    
465
        # Create new meta
466
        request = {'meta': {'foo2': 'bar2'}}
467
        response = self.put('/api/v1.1/servers/%d/meta/foo2' % vm.id,
468
                            vm.userid, json.dumps(request), 'json')
469

    
470
        # Get the new meta
471
        response = self.get('/api/v1.1/servers/%d/meta/foo2' % vm.id,
472
                            vm.userid)
473
        meta = json.loads(response.content)['meta']
474
        self.assertEqual(meta['foo2'], 'bar2')
475

    
476
        # Delete the new meta
477
        response = self.delete('/api/v1.1/servers/%d/meta/foo2' % vm.id,
478
                               vm.userid)
479
        self.assertEqual(response.status_code, 204)
480

    
481
        # Try to get the deleted meta: should raise 404
482
        response = self.get('/api/v1.1/servers/%d/meta/foo2' % vm.id,
483
                            vm.userid)
484
        self.assertEqual(response.status_code, 404)
485

    
486
    def test_invalid_metadata(self):
487
        vm = self.vm
488
        response = self.post('/api/v1.1/servers/%d/meta' % vm.id, vm.userid)
489
        self.assertBadRequest(response)
490
        self.assertEqual(len(vm.metadata.all()), 1)
491

    
492
    def test_invalid_metadata_server(self):
493
        response = self.post('/api/v1.1/servers/42/meta', 'user')
494
        self.assertItemNotFound(response)
495

    
496
    def test_get_meta_invalid_key(self):
497
        vm = self.vm
498
        response = self.get('/api/v1.1/servers/%d/meta/foo2' % vm.id,
499
                            vm.userid)
500
        self.assertItemNotFound(response)
501

    
502

    
503
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
504
class NetworkAPITest(BaseTestCase):
505
    def setUp(self):
506
        self.mac_prefixes = mfactory.MacPrefixPoolTableFactory()
507
        self.bridges = mfactory.BridgePoolTableFactory()
508
        self.user = 'dummy-user'
509
        self.net1 = mfactory.NetworkFactory(userid=self.user)
510
        self.net2 = mfactory.NetworkFactory(userid=self.user)
511

    
512
    def assertNetworksEqual(self, db_net, api_net, detail=False):
513
        self.assertEqual(str(db_net.id), api_net["id"])
514
        self.assertEqual(db_net.name, api_net['name'])
515
        if detail:
516
            self.assertEqual(db_net.state, api_net['status'])
517
            self.assertEqual(db_net.flavor, api_net['type'])
518
            self.assertEqual(db_net.subnet, api_net['cidr'])
519
            self.assertEqual(db_net.subnet6, api_net['cidr6'])
520
            self.assertEqual(db_net.gateway, api_net['gateway'])
521
            self.assertEqual(db_net.gateway6, api_net['gateway6'])
522
            self.assertEqual(db_net.dhcp, api_net['dhcp'])
523

    
524
    def test_create_network_1(self, mrapi):
525
        request = {
526
            'network': {'name': 'foo'}
527
            }
528
        response = self.post('/api/v1.1/networks/', 'user1',
529
                             json.dumps(request), 'json')
530
        self.assertEqual(response.status_code, 202)
531
        db_networks = Network.objects.filter(userid='user1')
532
        self.assertEqual(len(db_networks), 1)
533
        db_net = db_networks[0]
534
        api_net = json.loads(response.content)['network']
535
        self.assertNetworksEqual(db_net, api_net)
536
        mrapi.CreateNetwork.assert_called()
537
        mrapi.ConnectNetwork.assert_called()
538

    
539
    def test_invalid_data_1(self, mrapi):
540
        """Test invalid flavor"""
541
        request = {
542
            'network': {'name': 'foo', 'type': 'LoLo'}
543
            }
544
        response = self.post('/api/v1.1/networks/', 'user1',
545
                             json.dumps(request), 'json')
546
        self.assertBadRequest(response)
547
        self.assertEqual(len(Network.objects.filter(userid='user1')), 0)
548

    
549
    def test_invalid_data_2(self, mrapi):
550
        """Test invalid subnet"""
551
        request = {
552
            'network': {'name': 'foo', 'cidr': '10.0.0.0/8'}
553
            }
554
        response = self.post('/api/v1.1/networks/', 'user1',
555
                             json.dumps(request), 'json')
556
        self.assertFault(response, 413, "overLimit")
557

    
558
    def test_invalid_data_3(self, mrapi):
559
        """Test unauthorized to create public network"""
560
        request = {
561
                'network': {'name': 'foo', 'public': True}
562
            }
563
        response = self.post('/api/v1.1/networks/', 'user1',
564
                             json.dumps(request), 'json')
565
        self.assertFault(response, 403, "forbidden")
566

    
567
    def test_list_networks(self, mrapi):
568
        mfactory.NetworkFactory(userid=self.user, deleted=True)
569
        response = self.get('/api/v1.1/networks/', self.user)
570
        self.assertSuccess(response)
571
        db_nets = Network.objects.filter(userid=self.user, deleted=False)
572
        api_nets = json.loads(response.content)["networks"]["values"]
573
        self.assertEqual(len(db_nets), len(api_nets))
574
        for api_net in api_nets:
575
            net_id = api_net['id']
576
            self.assertNetworksEqual(Network.objects.get(id=net_id), api_net)
577

    
578
    def test_list_networks_detail(self, mrapi):
579
        mfactory.NetworkFactory(userid=self.user, deleted=True)
580
        response = self.get('/api/v1.1/networks/detail', self.user)
581
        self.assertSuccess(response)
582
        db_nets = Network.objects.filter(userid=self.user, deleted=False)
583
        api_nets = json.loads(response.content)["networks"]["values"]
584
        self.assertEqual(len(db_nets), len(api_nets))
585
        for api_net in api_nets:
586
            net_id = api_net['id']
587
            self.assertNetworksEqual(Network.objects.get(id=net_id), api_net,
588
                                     detail=True)
589

    
590
    def test_network_details_1(self, mrapi):
591
        response = self.get('/api/v1.1/networks/%d' % self.net1.id,
592
                            self.net1.userid)
593
        self.assertSuccess(response)
594
        api_net = json.loads(response.content)["network"]
595
        self.assertNetworksEqual(self.net1, api_net, detail=True)
596

    
597
    def test_invalid_network(self, mrapi):
598
        response = self.get('/api/v1.1/networks/%d' % 42,
599
                            self.net1.userid)
600
        self.assertItemNotFound(response)
601

    
602
    def test_rename_network(self, mrapi):
603
        request = {'network': {'name': "new_name"}}
604
        response = self.put('/api/v1.1/networks/%d' % self.net2.id,
605
                            self.net2.userid, json.dumps(request), 'json')
606
        self.assertEqual(response.status_code, 204)
607
        self.assertEqual(Network.objects.get(id=self.net2.id).name, "new_name")
608

    
609
    def test_rename_public_network(self, mrapi):
610
        net = mfactory.NetworkFactory(public=True)
611
        request = {'network': {'name': "new_name"}}
612
        response = self.put('/api/v1.1/networks/%d' % net.id,
613
                            self.net2.userid, json.dumps(request), 'json')
614
        self.assertFault(response, 403, 'forbidden')
615

    
616
    def test_delete_network(self, mrapi):
617
        response = self.delete('/api/v1.1/networks/%d' % self.net2.id,
618
                                self.net2.userid)
619
        self.assertEqual(response.status_code, 204)
620
        net = Network.objects.get(id=self.net2.id, userid=self.net2.userid)
621
        self.assertEqual(net.action, 'DESTROY')
622
        mrapi.DeleteNetwork.assert_called()
623

    
624
    def test_delete_public_network(self, mrapi):
625
        net = mfactory.NetworkFactory(public=True)
626
        response = self.delete('/api/v1.1/networks/%d' % net.id,
627
                                self.net2.userid)
628
        self.assertFault(response, 403, 'forbidden')
629
        mrapi.DeleteNetwork.assert_not_called()
630

    
631
    def test_add_nic(self, mrapi):
632
        user = 'userr'
633
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
634
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
635
        request = {'add': {'serverRef': vm.id}}
636
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
637
                             net.userid, json.dumps(request), 'json')
638
        self.assertEqual(response.status_code, 202)
639

    
640
    def test_add_nic_malformed(self, mrapi):
641
        user = 'userr'
642
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
643
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
644
        request = {'add': {'serveRef': vm.id}}
645
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
646
                             net.userid, json.dumps(request), 'json')
647
        self.assertBadRequest(response)
648

    
649
    def test_remove_nic(self, mrapi):
650
        user = 'userr'
651
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
652
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
653
        nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net)
654
        request = {'remove': {'attachment': 'nic-%s-%s' % (vm.id, nic.index)}}
655
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
656
                             net.userid, json.dumps(request), 'json')
657
        self.assertEqual(response.status_code, 202)
658
        self.assertTrue(NetworkInterface.objects.get(id=nic.id).dirty)
659
        # Remove dirty nic
660
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
661
                             net.userid, json.dumps(request), 'json')
662
        self.assertFault(response, 409, 'buildInProgress')
663

    
664
    def test_remove_nic_malformed(self, mrapi):
665
        user = 'userr'
666
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
667
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
668
        nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net)
669
        request = {'remove':
670
                    {'att234achment': 'nic-%s-%s' % (vm.id, nic.index)}
671
                  }
672
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
673
                             net.userid, json.dumps(request), 'json')
674
        self.assertBadRequest(response)
675

    
676

    
677
class ServerVNCConsole(BaseTestCase):
678

    
679
    def test_not_active_server(self):
680
        """Test console req for not ACTIVE server returns badRequest"""
681
        vm = mfactory.VirtualMachineFactory()
682
        data = json.dumps({'console': {'type': 'vnc'}})
683
        response = self.post('/api/v1.1/servers/%d/action' % vm.id,
684
                             vm.userid, data, 'json')
685
        self.assertBadRequest(response)
686

    
687
    def test_active_server(self):
688
        """Test console req for ACTIVE server"""
689
        vm = mfactory.VirtualMachineFactory()
690
        vm.operstate = 'STARTED'
691
        vm.save()
692

    
693
        data = json.dumps({'console': {'type': 'vnc'}})
694
        response = self.post('/api/v1.1/servers/%d/action' % vm.id,
695
                             vm.userid, data, 'json')
696
        self.assertEqual(response.status_code, 200)
697
        reply = json.loads(response.content)
698
        self.assertEqual(reply.keys(), ['console'])
699
        console = reply['console']
700
        self.assertEqual(console['type'], 'vnc')
701
        self.assertEqual(set(console.keys()),
702
                         set(['type', 'host', 'port', 'password']))
703

    
704

    
705
@patch('synnefo.api.images.ImageBackend')
706
class ImageAPITest(BaseTestCase):
707
    def test_create_image(self, mimage):
708
        """Test that create image is not implemented"""
709
        response = self.post('/api/v1.1/images/', 'user', json.dumps(''),
710
                             'json')
711
        self.assertEqual(response.status_code, 503)
712

    
713
    def test_list_images(self, mimage):
714
        """Test that expected list of images is returned"""
715
        images = [{'id': 1, 'name': 'image-1'},
716
                  {'id': 2, 'name': 'image-2'},
717
                  {'id': 3, 'name': 'image-3'}]
718
        mimage().list.return_value = images
719
        response = self.get('/api/v1.1/images/', 'user')
720
        self.assertSuccess(response)
721
        api_images = json.loads(response.content)['images']['values']
722
        self.assertEqual(images, api_images)
723

    
724
    def test_list_images_detail(self, mimage):
725
        images = [{'id': 1,
726
                   'name': 'image-1',
727
                   'status':'available',
728
                   'created_at': '2012-11-26 11:52:54',
729
                   'updated_at': '2012-12-26 11:52:54',
730
                   'deleted_at': '',
731
                   'properties': {'foo':'bar'}},
732
                  {'id': 2,
733
                   'name': 'image-2',
734
                   'status': 'deleted',
735
                   'created_at': '2012-11-26 11:52:54',
736
                   'updated_at': '2012-12-26 11:52:54',
737
                   'deleted_at': '2012-12-27 11:52:54',
738
                   'properties': ''},
739
                  {'id': 3,
740
                   'name': 'image-3',
741
                   'status': 'available',
742
                   'created_at': '2012-11-26 11:52:54',
743
                   'deleted_at': '',
744
                   'updated_at': '2012-12-26 11:52:54',
745
                   'properties': ''}]
746
        result_images = [
747
                  {'id': 1,
748
                   'name': 'image-1',
749
                   'status':'ACTIVE',
750
                   'progress': 100,
751
                   'created': '2012-11-26T11:52:54+00:00',
752
                   'updated': '2012-12-26T11:52:54+00:00',
753
                   'metadata': {'values': {'foo':'bar'}}},
754
                  {'id': 2,
755
                   'name': 'image-2',
756
                   'status': 'DELETED',
757
                   'progress': 0,
758
                   'created': '2012-11-26T11:52:54+00:00',
759
                   'updated': '2012-12-26T11:52:54+00:00'},
760
                  {'id': 3,
761
                   'name': 'image-3',
762
                   'status': 'ACTIVE',
763
                   'progress': 100,
764
                   'created': '2012-11-26T11:52:54+00:00',
765
                   'updated': '2012-12-26T11:52:54+00:00'}]
766
        mimage().list.return_value = images
767
        response = self.get('/api/v1.1/images/detail', 'user')
768
        self.assertSuccess(response)
769
        api_images = json.loads(response.content)['images']['values']
770
        self.assertEqual(len(result_images), len(api_images))
771
        self.assertEqual(result_images, api_images)
772

    
773
    def test_get_image_details(self, mimage):
774
        image = {'id': 42,
775
                 'name': 'image-1',
776
                 'status': 'available',
777
                 'created_at': '2012-11-26 11:52:54',
778
                 'updated_at': '2012-12-26 11:52:54',
779
                 'deleted_at': '',
780
                 'properties': {'foo': 'bar'}}
781
        result_image = \
782
                  {'id': 42,
783
                   'name': 'image-1',
784
                   'status': 'ACTIVE',
785
                   'progress': 100,
786
                   'created': '2012-11-26T11:52:54+00:00',
787
                   'updated': '2012-12-26T11:52:54+00:00',
788
                   'metadata': {'values': {'foo': 'bar'}}}
789
        with patch('synnefo.api.util.get_image') as m:
790
            m.return_value = image
791
            response = self.get('/api/v1.1/images/42', 'user')
792
        self.assertSuccess(response)
793
        api_image = json.loads(response.content)['image']
794
        self.assertEqual(api_image, result_image)
795

    
796
    def test_invalid_image(self, mimage):
797
        with patch('synnefo.api.util.get_image') as m:
798
            m.side_effect = faults.ItemNotFound('Image not found')
799
            response = self.get('/api/v1.1/images/42', 'user')
800
        self.assertItemNotFound(response)
801

    
802
    def test_delete_image(self, mimage):
803
        # TODO
804
        pass
805

    
806

    
807
class ImageMetadataAPITest(BaseTestCase):
808
    def setUp(self):
809
        self.image = {'id': 42,
810
                 'name': 'image-1',
811
                 'status': 'available',
812
                 'created_at': '2012-11-26 11:52:54',
813
                 'updated_at': '2012-12-26 11:52:54',
814
                 'deleted_at': '',
815
                 'properties': {'foo': 'bar', 'foo2': 'bar2'}}
816
        self.result_image = \
817
                  {'id': 42,
818
                   'name': 'image-1',
819
                   'status': 'ACTIVE',
820
                   'progress': 100,
821
                   'created': '2012-11-26T11:52:54+00:00',
822
                   'updated': '2012-12-26T11:52:54+00:00',
823
                   'metadata': {'values': {'foo': 'bar'}}}
824
        self.mock_get_image = Mock('synnefo.api.util.get_image')
825
        self.mock_get_image.result = self.image
826
        self.mock_image_backend = Mock('synnefo.api.images.ImageBackend')
827

    
828
    def test_list_metadata(self):
829
        with patch('synnefo.api.util.get_image') as m:
830
            m.return_value = self.image
831
            response = self.get('/api/v1.1/images/42/meta', 'user')
832
        self.assertSuccess(response)
833
        meta = json.loads(response.content)['metadata']['values']
834
        self.assertEqual(meta, self.image['properties'])
835

    
836
    def test_get_metadata(self):
837
        with patch('synnefo.api.util.get_image') as m:
838
            m.return_value = self.image
839
            response = self.get('/api/v1.1/images/42/meta/foo', 'user')
840
        self.assertSuccess(response)
841
        meta = json.loads(response.content)['meta']
842
        self.assertEqual(meta['foo'], 'bar')
843

    
844
    def test_get_invalid_metadata(self):
845
        with patch('synnefo.api.util.get_image') as m:
846
            m.return_value = self.image
847
            response = self.get('/api/v1.1/images/42/meta/not_found', 'user')
848
        self.assertItemNotFound(response)
849

    
850
    def test_delete_metadata_item(self):
851
        with patch('synnefo.api.util.get_image') as m:
852
            with patch('synnefo.api.images.ImageBackend') as mimage:
853
                m.return_value = self.image
854
                response = self.delete('/api/v1.1/images/42/meta/foo', 'user')
855
                self.assertEqual(response.status_code, 204)
856
                mimage().update.assert_called_once_with('42',
857
                                             {'properties': {'foo2': 'bar2'}})
858

    
859
    def test_create_metadata_item(self):
860
        with patch('synnefo.api.util.get_image') as m:
861
            with patch('synnefo.api.images.ImageBackend') as mimage:
862
                m.return_value = self.image
863
                request = {'meta': {'foo3': 'bar3'}}
864
                response = self.put('/api/v1.1/images/42/meta/foo3', 'user',
865
                                    json.dumps(request), 'json')
866
                self.assertEqual(response.status_code, 201)
867
                mimage().update.assert_called_once_with('42',
868
                        {'properties':
869
                            {'foo': 'bar', 'foo2': 'bar2', 'foo3': 'bar3'}})
870

    
871
    def test_update_metadata_item(self):
872
        with patch('synnefo.api.util.get_image') as m:
873
            with patch('synnefo.api.images.ImageBackend') as mimage:
874
                m.return_value = self.image
875
                request = {'metadata': {'foo': 'bar_new', 'foo4': 'bar4'}}
876
                response = self.post('/api/v1.1/images/42/meta', 'user',
877
                                    json.dumps(request), 'json')
878
                self.assertEqual(response.status_code, 201)
879
                mimage().update.assert_called_once_with('42', {'properties':
880
                        {'foo': 'bar_new', 'foo2': 'bar2', 'foo4': 'bar4'}})