Statistics
| Branch: | Tag: | Revision:

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

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

    
39
from mock import patch, Mock
40
from contextlib import contextmanager
41
from functools import wraps
42

    
43
from synnefo.db.models import *
44
from synnefo.db import models_factory as mfactory
45
from synnefo.logic.utils import get_rsapi_state
46

    
47
from synnefo.api import faults
48

    
49

    
50
@contextmanager
51
def astakos_user(user):
52
    """
53
    Context manager to mock astakos response.
54

55
    usage:
56
    with astakos_user("user@user.com"):
57
        .... make api calls ....
58

59
    """
60
    def dummy_get_user(request, *args, **kwargs):
61
        request.user = {'username': user, 'groups': []}
62
        request.user_uniq = user
63

    
64
    with patch('synnefo.api.util.get_user') as m:
65
        m.side_effect = dummy_get_user
66
        yield
67

    
68

    
69
class BaseAPITest(TestCase):
70
    def get(self, url, user='user', *args, **kwargs):
71
        with astakos_user(user):
72
            response = self.client.get(url, *args, **kwargs)
73
        return response
74

    
75
    def delete(self, url, user='user'):
76
        with astakos_user(user):
77
            response = self.client.delete(url)
78
        return response
79

    
80
    def post(self, url, user='user', params={}, ctype='json', *args, **kwargs):
81
        if ctype == 'json':
82
            content_type = 'application/json'
83
        with astakos_user(user):
84
            response = self.client.post(url, params, content_type=content_type,
85
                                        *args, **kwargs)
86
        return response
87

    
88
    def put(self, url, user='user', params={}, ctype='json', *args, **kwargs):
89
        if ctype == 'json':
90
            content_type = 'application/json'
91
        with astakos_user(user):
92
            response = self.client.put(url, params, content_type=content_type,
93
                    *args, **kwargs)
94
        return response
95

    
96
    def assertSuccess(self, response):
97
        self.assertTrue(response.status_code in [200, 203, 204])
98

    
99
    def assertFault(self, response, status_code, name):
100
        self.assertEqual(response.status_code, status_code)
101
        fault = json.loads(response.content)
102
        self.assertEqual(fault.keys(), [name])
103

    
104
    def assertBadRequest(self, response):
105
        self.assertFault(response, 400, 'badRequest')
106

    
107
    def assertItemNotFound(self, response):
108
        self.assertFault(response, 404, 'itemNotFound')
109

    
110

    
111
class FlavorAPITest(BaseAPITest):
112

    
113
    def setUp(self):
114
        self.flavor1 = mfactory.FlavorFactory()
115
        self.flavor2 = mfactory.FlavorFactory(deleted=True)
116
        self.flavor3 = mfactory.FlavorFactory()
117

    
118
    def test_flavor_list(self):
119
        """Test if the expected list of flavors is returned by."""
120
        response = self.get('/api/v1.1/flavors')
121
        self.assertSuccess(response)
122

    
123
        flavors_from_api = json.loads(response.content)['flavors']['values']
124
        flavors_from_db = Flavor.objects.filter(deleted=False)
125
        self.assertEqual(len(flavors_from_api), len(flavors_from_db))
126
        for flavor_from_api in flavors_from_api:
127
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
128
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
129
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
130

    
131
    def test_flavors_details(self):
132
        """Test if the flavors details are returned."""
133
        response = self.get('/api/v1.1/flavors/detail')
134
        self.assertSuccess(response)
135

    
136
        flavors_from_db = Flavor.objects.filter(deleted=False)
137
        flavors_from_api = json.loads(response.content)['flavors']['values']
138

    
139
        # Assert that all flavors in the db appear inthe API call result
140
        for i in range(0, len(flavors_from_db)):
141
            flavor_from_api = flavors_from_api[i]
142
            flavor_from_db = Flavor.objects.get(id=flavors_from_db[i].id)
143
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
144
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
145
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
146
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
147
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
148

    
149
        # Assert that all flavors returned by the API also exist in the db
150
        for flavor_from_api in flavors_from_api:
151
            flavor_from_db = Flavor.objects.get(id=flavor_from_api['id'])
152
            self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
153
            self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
154
            self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
155
            self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
156
            self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
157

    
158
    def test_flavor_details(self):
159
        """Test if the expected flavor is returned."""
160
        flavor = self.flavor3
161

    
162
        response = self.get('/api/v1.1/flavors/%d' % flavor.id)
163
        self.assertSuccess(response)
164

    
165
        flavor_from_api = json.loads(response.content)['flavor']
166
        flavor_from_db = Flavor.objects.get(id=flavor.id)
167
        self.assertEqual(flavor_from_api['cpu'], flavor_from_db.cpu)
168
        self.assertEqual(flavor_from_api['id'], flavor_from_db.id)
169
        self.assertEqual(flavor_from_api['disk'], flavor_from_db.disk)
170
        self.assertEqual(flavor_from_api['name'], flavor_from_db.name)
171
        self.assertEqual(flavor_from_api['ram'], flavor_from_db.ram)
172

    
173
    def test_deleted_flavor_details(self):
174
        """Test that API returns details for deleted flavors"""
175
        flavor = self.flavor2
176
        response = self.get('/api/v1.1/flavors/%d' % flavor.id)
177
        self.assertSuccess(response)
178
        flavor_from_api = json.loads(response.content)['flavor']
179
        self.assertEquals(flavor_from_api['name'], flavor.name)
180

    
181
    def test_deleted_flavors_list(self):
182
        """Test that deleted flavors do not appear to flavors list"""
183
        response = self.get('/api/v1.1/flavors')
184
        self.assertSuccess(response)
185
        flavors_from_api = json.loads(response.content)['flavors']['values']
186
        self.assertEqual(len(flavors_from_api), 2)
187

    
188
    def test_deleted_flavors_details(self):
189
        """Test that deleted flavors do not appear to flavors detail list"""
190
        mfactory.FlavorFactory(deleted=True)
191
        response = self.get('/api/v1.1/flavors/detail')
192
        self.assertSuccess(response)
193
        flavors_from_api = json.loads(response.content)['flavors']['values']
194
        self.assertEqual(len(flavors_from_api), 2)
195

    
196
    def test_wrong_flavor(self):
197
        """Test 404 result when requesting a flavor that does not exist."""
198

    
199
        response = self.get('/api/v1.1/flavors/%d' % 22)
200
        self.assertItemNotFound(response)
201

    
202

    
203
class ServerAPITest(BaseAPITest):
204
    def setUp(self):
205
        self.user1 = 'user1'
206
        self.user2 = 'user2'
207
        self.vm1 = mfactory.VirtualMachineFactory(userid=self.user1)
208
        self.vm2 = mfactory.VirtualMachineFactory(userid=self.user2)
209
        self.vm3 = mfactory.VirtualMachineFactory(deleted=True,
210
                                                  userid=self.user1)
211
        self.vm4 = mfactory.VirtualMachineFactory(userid=self.user2)
212

    
213
    def test_server_list_1(self):
214
        """Test if the expected list of servers is returned."""
215
        response = self.get('/api/v1.1/servers')
216
        self.assertSuccess(response)
217
        servers = json.loads(response.content)['servers']['values']
218
        self.assertEqual(servers, [])
219

    
220
    def test_server_list_2(self):
221
        """Test if the expected list of servers is returned."""
222
        response = self.get('/api/v1.1/servers', self.user1)
223
        self.assertSuccess(response)
224
        servers = json.loads(response.content)['servers']['values']
225
        db_server = self.vm1
226
        self.assertEqual(servers, [{'name': db_server.name,
227
                                    'id': db_server.id}])
228

    
229
    def test_server_list_detail(self):
230
        """Test if the servers list details are returned."""
231
        user = self.user2
232
        user_vms = {self.vm2.id: self.vm2,
233
                    self.vm4.id: self.vm4}
234

    
235
        response = self.get('/api/v1.1/servers/detail', user)
236
        servers = json.loads(response.content)['servers']['values']
237
        self.assertEqual(len(servers), len(user_vms))
238
        for api_vm in servers:
239
            db_vm = user_vms[api_vm['id']]
240
            self.assertEqual(api_vm['flavorRef'], db_vm.flavor.id)
241
            self.assertEqual(api_vm['hostId'], db_vm.hostid)
242
            self.assertEqual(api_vm['id'], db_vm.id)
243
            self.assertEqual(api_vm['imageRef'], db_vm.imageid)
244
            self.assertEqual(api_vm['name'], db_vm.name)
245
            self.assertEqual(api_vm['status'], get_rsapi_state(db_vm))
246
            self.assertSuccess(response)
247

    
248
    def test_server_detail(self):
249
        """Test if a server details are returned."""
250
        db_vm = self.vm2
251
        user = self.vm2.userid
252
        db_vm_meta = mfactory.VirtualMachineMetadataFactory(vm=db_vm)
253

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

    
257
        self.assertEqual(server['flavorRef'], db_vm.flavor.id)
258
        self.assertEqual(server['hostId'], db_vm.hostid)
259
        self.assertEqual(server['id'], db_vm.id)
260
        self.assertEqual(server['imageRef'], db_vm.imageid)
261
        self.assertEqual(server['name'], db_vm.name)
262
        self.assertEqual(server['status'], get_rsapi_state(db_vm))
263

    
264
        metadata = server['metadata']['values']
265
        self.assertEqual(len(metadata), 1)
266
        self.assertEqual(metadata[db_vm_meta.meta_key], db_vm_meta.meta_value)
267
        self.assertSuccess(response)
268

    
269
    def test_noauthorized(self):
270
        """Test 404 for detail of other user vm"""
271
        db_vm = self.vm2
272

    
273
        response = self.get('/api/v1.1/servers/%d' % db_vm.id, 'wrong_user')
274
        self.assertItemNotFound(response)
275

    
276
    def test_wrong_server(self):
277
        """Test 404 response if server does not exist."""
278
        response = self.get('/api/v1.1/servers/%d' % 5000)
279
        self.assertItemNotFound(response)
280

    
281
    def test_create_server_empty(self):
282
        """Test if the create server call returns a 400 badRequest if
283
           no attributes are specified."""
284

    
285
        response = self.post('/api/v1.1/servers', params={})
286
        self.assertBadRequest(response)
287

    
288

    
289
@patch('synnefo.api.util.get_image')
290
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
291
class ServerCreateAPITest(BaseAPITest):
292
    def test_create_server(self, mrapi, mimage):
293
        """Test if the create server call returns the expected response
294
           if a valid request has been speficied."""
295
        mimage.return_value = {'location': 'pithos://foo',
296
                               'disk_format': 'diskdump'}
297
        mrapi().CreateInstance.return_value = 12
298
        flavor = mfactory.FlavorFactory()
299
        # Create public network and backend
300
        network = mfactory.NetworkFactory(public=True)
301
        backend = mfactory.BackendFactory()
302
        mfactory.BackendNetworkFactory(network=network, backend=backend)
303

    
304
        request = {
305
                    "server": {
306
                        "name": "new-server-test",
307
                        "userid": "test_user",
308
                        "imageRef": 1,
309
                        "flavorRef": flavor.id,
310
                        "metadata": {
311
                            "My Server Name": "Apache1"
312
                        },
313
                        "personality": []
314
                    }
315
        }
316
        response = self.post('/api/v1.1/servers', 'test_user',
317
                                 json.dumps(request), 'json')
318
        self.assertEqual(response.status_code, 202)
319
        mrapi().CreateInstance.assert_called_once()
320

    
321
        api_server = json.loads(response.content)['server']
322
        self.assertEqual(api_server['status'], "BUILD")
323
        self.assertEqual(api_server['progress'], 0)
324
        self.assertEqual(api_server['metadata']['values'],
325
                        {"My Server Name":  "Apache1"})
326
        self.assertTrue('adminPass' in api_server)
327

    
328
        db_vm = VirtualMachine.objects.get(userid='test_user')
329
        self.assertEqual(api_server['name'], db_vm.name)
330
        self.assertEqual(api_server['status'], db_vm.operstate)
331

    
332
    def test_create_server_no_flavor(self, mrapi, mimage):
333
        request = {
334
                    "server": {
335
                        "name": "new-server-test",
336
                        "userid": "test_user",
337
                        "imageRef": 1,
338
                        "flavorRef": 42,
339
                        "metadata": {
340
                            "My Server Name": "Apache1"
341
                        },
342
                        "personality": []
343
                    }
344
        }
345
        response = self.post('/api/v1.1/servers', 'test_user',
346
                                 json.dumps(request), 'json')
347
        self.assertItemNotFound(response)
348

    
349

    
350
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
351
class ServerDestroyAPITest(BaseAPITest):
352
    def test_delete_server(self, mrapi):
353
        vm = mfactory.VirtualMachineFactory()
354
        response = self.delete('/api/v1.1/servers/%d' % vm.id, vm.userid)
355
        self.assertEqual(response.status_code, 204)
356
        mrapi().DeleteInstance.assert_called_once()
357

    
358
    def test_non_existing_delete_server(self, mrapi):
359
        vm = mfactory.VirtualMachineFactory()
360
        response = self.delete('/api/v1.1/servers/%d' % 42, vm.userid)
361
        self.assertItemNotFound(response)
362
        mrapi().DeleteInstance.assert_not_called()
363

    
364

    
365
@patch('synnefo.api.util.get_image')
366
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
367
class ServerActionAPITest(BaseAPITest):
368
    def test_actions(self, mrapi, mimage):
369
        actions = ['start', 'shutdown', 'reboot']
370
        vm = mfactory.VirtualMachineFactory()
371
        vm.operstate = "STOPPED"
372
        vm.save()
373
        for action in actions:
374
            val = {'type': 'HARD'} if action == 'reboot' else {}
375
            request = {action: val}
376
            response = self.post('/api/v1.1/servers/%d/action' % vm.id,
377
                                vm.userid, json.dumps(request), 'json')
378
            self.assertEqual(response.status_code, 202)
379
            if action == 'shutdown':
380
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).action,
381
                                 "STOP")
382
            else:
383
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).action,
384
                                 action.upper())
385

    
386
    def test_firewall(self, mrapi, mimage):
387
        vm = mfactory.VirtualMachineFactory()
388
        vm.operstate = "STOPPED"
389
        vm.save()
390
        request = {'firewallProfile': {'profile': 'PROTECTED'}}
391
        response = self.post('/api/v1.1/servers/%d/action' % vm.id,
392
                             vm.userid, json.dumps(request), 'json')
393
        self.assertEqual(response.status_code, 202)
394
        mrapi().ModifyInstance.assert_called_once()
395

    
396

    
397
class ServerMetadataAPITest(BaseAPITest):
398
    def setUp(self):
399
        self.vm = mfactory.VirtualMachineFactory()
400
        self.metadata = mfactory.VirtualMachineMetadataFactory(vm=self.vm)
401

    
402
    def test_get_metadata(self):
403
        vm = self.vm
404
        create_meta = lambda: mfactory.VirtualMachineMetadataFactory(vm=vm)
405
        metadata = [create_meta(), create_meta(), create_meta()]
406
        response = self.get('/api/v1.1/servers/%d/meta' % vm.id, vm.userid)
407
        self.assertTrue(response.status_code in [200, 203])
408
        api_metadata = json.loads(response.content)['metadata']['values']
409
        self.assertEqual(len(api_metadata), len(metadata) + 1)
410
        for db_m in metadata:
411
            self.assertEqual(api_metadata[db_m.meta_key], db_m.meta_value)
412

    
413
        request = {'metadata':
414
                        {'foo': 'bar'},
415
                        metadata[0].meta_key: 'bar2'
416
                  }
417
        response = self.post('/api/v1.1/servers/%d/meta' % vm.id, vm.userid,
418
                             json.dumps(request), 'json')
419
        metadata2 = VirtualMachineMetadata.objects.filter(vm=vm)
420
        response = self.get('/api/v1.1/servers/%d/meta' % vm.id, vm.userid)
421
        self.assertTrue(response.status_code in [200, 203])
422
        api_metadata2 = json.loads(response.content)['metadata']['values']
423
        self.assertTrue('foo' in api_metadata2.keys())
424
        self.assertTrue(api_metadata2[metadata[0].meta_key], 'bar2')
425
        self.assertEqual(len(api_metadata2), len(metadata2))
426
        for db_m in metadata2:
427
            self.assertEqual(api_metadata2[db_m.meta_key], db_m.meta_value)
428

    
429
        # Create new meta
430
        request = {'meta': {'foo2': 'bar2'}}
431
        response = self.put('/api/v1.1/servers/%d/meta/foo2' % vm.id,
432
                            vm.userid, json.dumps(request), 'json')
433

    
434
        # Get the new meta
435
        response = self.get('/api/v1.1/servers/%d/meta/foo2' % vm.id,
436
                            vm.userid)
437
        meta = json.loads(response.content)['meta']
438
        self.assertEqual(meta['foo2'], 'bar2')
439

    
440
        # Delete the new meta
441
        response = self.delete('/api/v1.1/servers/%d/meta/foo2' % vm.id,
442
                               vm.userid)
443
        self.assertEqual(response.status_code, 204)
444

    
445
        # Try to get the deleted meta: should raise 404
446
        response = self.get('/api/v1.1/servers/%d/meta/foo2' % vm.id,
447
                            vm.userid)
448
        self.assertEqual(response.status_code, 404)
449

    
450
    def test_invalid_metadata(self):
451
        vm = self.vm
452
        response = self.post('/api/v1.1/servers/%d/meta' % vm.id, vm.userid)
453
        self.assertBadRequest(response)
454
        self.assertEqual(len(vm.metadata.all()), 1)
455

    
456
    def test_invalid_metadata_server(self):
457
        response = self.post('/api/v1.1/servers/42/meta', 'user')
458
        self.assertItemNotFound(response)
459

    
460
    def test_get_meta_invalid_key(self):
461
        vm = self.vm
462
        response = self.get('/api/v1.1/servers/%d/meta/foo2' % vm.id,
463
                            vm.userid)
464
        self.assertItemNotFound(response)
465

    
466

    
467
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
468
class NetworkAPITest(BaseAPITest):
469
    def setUp(self):
470
        self.mac_prefixes = mfactory.MacPrefixPoolTableFactory()
471
        self.bridges = mfactory.BridgePoolTableFactory()
472
        self.user = 'dummy-user'
473
        self.net1 = mfactory.NetworkFactory(userid=self.user)
474
        self.net2 = mfactory.NetworkFactory(userid=self.user)
475

    
476
    def assertNetworksEqual(self, db_net, api_net, detail=False):
477
        self.assertEqual(str(db_net.id), api_net["id"])
478
        self.assertEqual(db_net.name, api_net['name'])
479
        if detail:
480
            self.assertEqual(db_net.state, api_net['status'])
481
            self.assertEqual(db_net.flavor, api_net['type'])
482
            self.assertEqual(db_net.subnet, api_net['cidr'])
483
            self.assertEqual(db_net.subnet6, api_net['cidr6'])
484
            self.assertEqual(db_net.gateway, api_net['gateway'])
485
            self.assertEqual(db_net.gateway6, api_net['gateway6'])
486
            self.assertEqual(db_net.dhcp, api_net['dhcp'])
487

    
488
    def test_create_network_1(self, mrapi):
489
        request = {
490
            'network': {'name': 'foo'}
491
            }
492
        response = self.post('/api/v1.1/networks/', 'user1',
493
                             json.dumps(request), 'json')
494
        self.assertEqual(response.status_code, 202)
495
        db_networks = Network.objects.filter(userid='user1')
496
        self.assertEqual(len(db_networks), 1)
497
        db_net = db_networks[0]
498
        api_net = json.loads(response.content)['network']
499
        self.assertNetworksEqual(db_net, api_net)
500
        mrapi.CreateNetwork.assert_called()
501
        mrapi.ConnectNetwork.assert_called()
502

    
503
    def test_invalid_data_1(self, mrapi):
504
        """Test invalid flavor"""
505
        request = {
506
            'network': {'name': 'foo', 'type': 'LoLo'}
507
            }
508
        response = self.post('/api/v1.1/networks/', 'user1',
509
                             json.dumps(request), 'json')
510
        self.assertBadRequest(response)
511
        self.assertEqual(len(Network.objects.filter(userid='user1')), 0)
512

    
513
    def test_invalid_data_2(self, mrapi):
514
        """Test invalid subnet"""
515
        request = {
516
            'network': {'name': 'foo', 'cidr': '10.0.0.0/8'}
517
            }
518
        response = self.post('/api/v1.1/networks/', 'user1',
519
                             json.dumps(request), 'json')
520
        self.assertFault(response, 413, "overLimit")
521

    
522
    def test_invalid_data_3(self, mrapi):
523
        """Test unauthorized to create public network"""
524
        request = {
525
                'network': {'name': 'foo', 'public': True}
526
            }
527
        response = self.post('/api/v1.1/networks/', 'user1',
528
                             json.dumps(request), 'json')
529
        self.assertFault(response, 403, "forbidden")
530

    
531
    def test_list_networks(self, mrapi):
532
        mfactory.NetworkFactory(userid=self.user, deleted=True)
533
        response = self.get('/api/v1.1/networks/', self.user)
534
        self.assertSuccess(response)
535
        db_nets = Network.objects.filter(userid=self.user, deleted=False)
536
        api_nets = json.loads(response.content)["networks"]["values"]
537
        self.assertEqual(len(db_nets), len(api_nets))
538
        for api_net in api_nets:
539
            net_id = api_net['id']
540
            self.assertNetworksEqual(Network.objects.get(id=net_id), api_net)
541

    
542
    def test_list_networks_detail(self, mrapi):
543
        mfactory.NetworkFactory(userid=self.user, deleted=True)
544
        response = self.get('/api/v1.1/networks/detail', self.user)
545
        self.assertSuccess(response)
546
        db_nets = Network.objects.filter(userid=self.user, deleted=False)
547
        api_nets = json.loads(response.content)["networks"]["values"]
548
        self.assertEqual(len(db_nets), len(api_nets))
549
        for api_net in api_nets:
550
            net_id = api_net['id']
551
            self.assertNetworksEqual(Network.objects.get(id=net_id), api_net,
552
                                     detail=True)
553

    
554
    def test_network_details_1(self, mrapi):
555
        response = self.get('/api/v1.1/networks/%d' % self.net1.id,
556
                            self.net1.userid)
557
        self.assertSuccess(response)
558
        api_net = json.loads(response.content)["network"]
559
        self.assertNetworksEqual(self.net1, api_net, detail=True)
560

    
561
    def test_invalid_network(self, mrapi):
562
        response = self.get('/api/v1.1/networks/%d' % 42,
563
                            self.net1.userid)
564
        self.assertItemNotFound(response)
565

    
566
    def test_rename_network(self, mrapi):
567
        request = {'network': {'name': "new_name"}}
568
        response = self.put('/api/v1.1/networks/%d' % self.net2.id,
569
                            self.net2.userid, json.dumps(request), 'json')
570
        self.assertEqual(response.status_code, 204)
571
        self.assertEqual(Network.objects.get(id=self.net2.id).name, "new_name")
572

    
573
    def test_rename_public_network(self, mrapi):
574
        net = mfactory.NetworkFactory(public=True)
575
        request = {'network': {'name': "new_name"}}
576
        response = self.put('/api/v1.1/networks/%d' % net.id,
577
                            self.net2.userid, json.dumps(request), 'json')
578
        self.assertFault(response, 403, 'forbidden')
579

    
580
    def test_delete_network(self, mrapi):
581
        response = self.delete('/api/v1.1/networks/%d' % self.net2.id,
582
                                self.net2.userid)
583
        self.assertEqual(response.status_code, 204)
584
        net = Network.objects.get(id=self.net2.id, userid=self.net2.userid)
585
        self.assertEqual(net.action, 'DESTROY')
586
        mrapi.DeleteNetwork.assert_called()
587

    
588
    def test_delete_public_network(self, mrapi):
589
        net = mfactory.NetworkFactory(public=True)
590
        response = self.delete('/api/v1.1/networks/%d' % net.id,
591
                                self.net2.userid)
592
        self.assertFault(response, 403, 'forbidden')
593
        mrapi.DeleteNetwork.assert_not_called()
594

    
595
    def test_add_nic(self, mrapi):
596
        user = 'userr'
597
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
598
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
599
        request = {'add': {'serverRef': vm.id}}
600
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
601
                             net.userid, json.dumps(request), 'json')
602
        self.assertEqual(response.status_code, 202)
603

    
604
    def test_add_nic_malformed(self, mrapi):
605
        user = 'userr'
606
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
607
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
608
        request = {'add': {'serveRef': vm.id}}
609
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
610
                             net.userid, json.dumps(request), 'json')
611
        self.assertBadRequest(response)
612

    
613
    def test_remove_nic(self, mrapi):
614
        user = 'userr'
615
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
616
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
617
        nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net)
618
        request = {'remove': {'attachment': 'nic-%s-%s' % (vm.id, nic.index)}}
619
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
620
                             net.userid, json.dumps(request), 'json')
621
        self.assertEqual(response.status_code, 202)
622
        self.assertTrue(NetworkInterface.objects.get(id=nic.id).dirty)
623
        # Remove dirty nic
624
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
625
                             net.userid, json.dumps(request), 'json')
626
        self.assertFault(response, 409, 'buildInProgress')
627

    
628
    def test_remove_nic_malformed(self, mrapi):
629
        user = 'userr'
630
        vm = mfactory.VirtualMachineFactory(name='yo', userid=user)
631
        net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
632
        nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net)
633
        request = {'remove':
634
                    {'att234achment': 'nic-%s-%s' % (vm.id, nic.index)}
635
                  }
636
        response = self.post('/api/v1.1/networks/%d/action' % net.id,
637
                             net.userid, json.dumps(request), 'json')
638
        self.assertBadRequest(response)
639

    
640

    
641
class ServerVNCConsole(BaseAPITest):
642

    
643
    def test_not_active_server(self):
644
        """Test console req for not ACTIVE server returns badRequest"""
645
        vm = mfactory.VirtualMachineFactory()
646
        data = json.dumps({'console': {'type': 'vnc'}})
647
        response = self.post('/api/v1.1/servers/%d/action' % vm.id,
648
                             vm.userid, data, 'json')
649
        self.assertBadRequest(response)
650

    
651
    def test_active_server(self):
652
        """Test console req for ACTIVE server"""
653
        vm = mfactory.VirtualMachineFactory()
654
        vm.operstate = 'STARTED'
655
        vm.save()
656

    
657
        data = json.dumps({'console': {'type': 'vnc'}})
658
        response = self.post('/api/v1.1/servers/%d/action' % vm.id,
659
                             vm.userid, data, 'json')
660
        self.assertEqual(response.status_code, 200)
661
        reply = json.loads(response.content)
662
        self.assertEqual(reply.keys(), ['console'])
663
        console = reply['console']
664
        self.assertEqual(console['type'], 'vnc')
665
        self.assertEqual(set(console.keys()),
666
                         set(['type', 'host', 'port', 'password']))
667

    
668

    
669
def assert_backend_closed(func):
670
    @wraps(func)
671
    def wrapper(self, backend):
672
        result = func(self, backend)
673
        if backend.called is True:
674
            backend.return_value.close.assert_called_once_with()
675
        return result
676
    return wrapper
677

    
678

    
679
@patch('synnefo.api.images.ImageBackend')
680
class ImageAPITest(BaseAPITest):
681
    @assert_backend_closed
682
    def test_create_image(self, mimage):
683
        """Test that create image is not implemented"""
684
        response = self.post('/api/v1.1/images/', 'user', json.dumps(''),
685
                             'json')
686
        self.assertEqual(response.status_code, 503)
687

    
688
    @assert_backend_closed
689
    def test_list_images(self, mimage):
690
        """Test that expected list of images is returned"""
691
        images = [{'id': 1, 'name': 'image-1'},
692
                  {'id': 2, 'name': 'image-2'},
693
                  {'id': 3, 'name': 'image-3'}]
694
        mimage().list.return_value = images
695
        response = self.get('/api/v1.1/images/', 'user')
696
        self.assertSuccess(response)
697
        api_images = json.loads(response.content)['images']['values']
698
        self.assertEqual(images, api_images)
699

    
700
    @assert_backend_closed
701
    def test_list_images_detail(self, mimage):
702
        images = [{'id': 1,
703
                   'name': 'image-1',
704
                   'status':'available',
705
                   'created_at': '2012-11-26 11:52:54',
706
                   'updated_at': '2012-12-26 11:52:54',
707
                   'deleted_at': '',
708
                   'properties': {'foo':'bar'}},
709
                  {'id': 2,
710
                   'name': 'image-2',
711
                   'status': 'deleted',
712
                   'created_at': '2012-11-26 11:52:54',
713
                   'updated_at': '2012-12-26 11:52:54',
714
                   'deleted_at': '2012-12-27 11:52:54',
715
                   'properties': ''},
716
                  {'id': 3,
717
                   'name': 'image-3',
718
                   'status': 'available',
719
                   'created_at': '2012-11-26 11:52:54',
720
                   'deleted_at': '',
721
                   'updated_at': '2012-12-26 11:52:54',
722
                   'properties': ''}]
723
        result_images = [
724
                  {'id': 1,
725
                   'name': 'image-1',
726
                   'status':'ACTIVE',
727
                   'progress': 100,
728
                   'created': '2012-11-26T11:52:54+00:00',
729
                   'updated': '2012-12-26T11:52:54+00:00',
730
                   'metadata': {'values': {'foo':'bar'}}},
731
                  {'id': 2,
732
                   'name': 'image-2',
733
                   'status': 'DELETED',
734
                   'progress': 0,
735
                   'created': '2012-11-26T11:52:54+00:00',
736
                   'updated': '2012-12-26T11:52:54+00:00'},
737
                  {'id': 3,
738
                   'name': 'image-3',
739
                   'status': 'ACTIVE',
740
                   'progress': 100,
741
                   'created': '2012-11-26T11:52:54+00:00',
742
                   'updated': '2012-12-26T11:52:54+00:00'}]
743
        mimage().list.return_value = images
744
        response = self.get('/api/v1.1/images/detail', 'user')
745
        self.assertSuccess(response)
746
        api_images = json.loads(response.content)['images']['values']
747
        self.assertEqual(len(result_images), len(api_images))
748
        self.assertEqual(result_images, api_images)
749

    
750
    @assert_backend_closed
751
    def test_get_image_details(self, mimage):
752
        image = {'id': 42,
753
                 'name': 'image-1',
754
                 'status': 'available',
755
                 'created_at': '2012-11-26 11:52:54',
756
                 'updated_at': '2012-12-26 11:52:54',
757
                 'deleted_at': '',
758
                 'properties': {'foo': 'bar'}}
759
        result_image = \
760
                  {'id': 42,
761
                   'name': 'image-1',
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
                   'metadata': {'values': {'foo': 'bar'}}}
767
        with patch('synnefo.api.util.get_image') as m:
768
            m.return_value = image
769
            response = self.get('/api/v1.1/images/42', 'user')
770
        self.assertSuccess(response)
771
        api_image = json.loads(response.content)['image']
772
        self.assertEqual(api_image, result_image)
773

    
774
    @assert_backend_closed
775
    def test_invalid_image(self, mimage):
776
        with patch('synnefo.api.util.get_image') as m:
777
            m.side_effect = faults.ItemNotFound('Image not found')
778
            response = self.get('/api/v1.1/images/42', 'user')
779
        self.assertItemNotFound(response)
780

    
781
    def test_delete_image(self, mimage):
782
        # TODO
783
        pass
784

    
785

    
786
@patch('synnefo.api.util.ImageBackend')
787
class ImageMetadataAPITest(BaseAPITest):
788
    def setUp(self):
789
        self.image = {'id': 42,
790
                 'name': 'image-1',
791
                 'status': 'available',
792
                 'created_at': '2012-11-26 11:52:54',
793
                 'updated_at': '2012-12-26 11:52:54',
794
                 'deleted_at': '',
795
                 'properties': {'foo': 'bar', 'foo2': 'bar2'}}
796
        self.result_image = \
797
                  {'id': 42,
798
                   'name': 'image-1',
799
                   'status': 'ACTIVE',
800
                   'progress': 100,
801
                   'created': '2012-11-26T11:52:54+00:00',
802
                   'updated': '2012-12-26T11:52:54+00:00',
803
                   'metadata': {'values': {'foo': 'bar'}}}
804

    
805
    @assert_backend_closed
806
    def test_list_metadata(self, backend):
807
        backend.return_value.get_image.return_value = self.image
808
        response = self.get('/api/v1.1/images/42/meta', 'user')
809
        self.assertSuccess(response)
810
        meta = json.loads(response.content)['metadata']['values']
811
        self.assertEqual(meta, self.image['properties'])
812

    
813
    @assert_backend_closed
814
    def test_get_metadata(self, backend):
815
        backend.return_value.get_image.return_value = self.image
816
        response = self.get('/api/v1.1/images/42/meta/foo', 'user')
817
        self.assertSuccess(response)
818
        meta = json.loads(response.content)['meta']
819
        self.assertEqual(meta['foo'], 'bar')
820

    
821
    @assert_backend_closed
822
    def test_get_invalid_metadata(self, backend):
823
        backend.return_value.get_image.return_value = self.image
824
        response = self.get('/api/v1.1/images/42/meta/not_found', 'user')
825
        self.assertItemNotFound(response)
826

    
827
    @assert_backend_closed
828
    def test_delete_metadata_item(self, backend):
829
        backend.return_value.get_image.return_value = self.image
830
        with patch("synnefo.api.images.ImageBackend") as m:
831
            response = self.delete('/api/v1.1/images/42/meta/foo', 'user')
832
            self.assertEqual(response.status_code, 204)
833
            m.return_value.update.assert_called_once_with('42',
834
                                        {'properties': {'foo2': 'bar2'}})
835

    
836
    @assert_backend_closed
837
    def test_create_metadata_item(self, backend):
838
        backend.return_value.get_image.return_value = self.image
839
        with patch("synnefo.api.images.ImageBackend") as m:
840
                request = {'meta': {'foo3': 'bar3'}}
841
                response = self.put('/api/v1.1/images/42/meta/foo3', 'user',
842
                                    json.dumps(request), 'json')
843
                self.assertEqual(response.status_code, 201)
844
                m.return_value.update.assert_called_once_with('42',
845
                        {'properties':
846
                            {'foo': 'bar', 'foo2': 'bar2', 'foo3': 'bar3'}})
847

    
848
    @assert_backend_closed
849
    def test_update_metadata_item(self, backend):
850
        backend.return_value.get_image.return_value = self.image
851
        with patch("synnefo.api.images.ImageBackend") as m:
852
                request = {'metadata': {'foo': 'bar_new', 'foo4': 'bar4'}}
853
                response = self.post('/api/v1.1/images/42/meta', 'user',
854
                                    json.dumps(request), 'json')
855
                self.assertEqual(response.status_code, 201)
856
                m.return_value.update.assert_called_once_with('42',
857
                        {'properties':
858
                            {'foo': 'bar_new', 'foo2': 'bar2', 'foo4': 'bar4'}
859
                        })
860

    
861

    
862
class APITest(TestCase):
863
    def test_api_version(self):
864
        """Check API version."""
865
        with astakos_user('user'):
866
            response = self.client.get('/api/v1.1/')
867
        self.assertEqual(response.status_code, 200)
868
        api_version = json.loads(response.content)['version']
869
        self.assertEqual(api_version['id'], 'v1.1')
870
        self.assertEqual(api_version['status'], 'CURRENT')