Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / test / servers.py @ 1b696c26

History | View | Annotate | Download (18.6 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
import json
35

    
36
from snf_django.utils.testing import BaseAPITest, mocked_quotaholder
37
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
38
from synnefo.db import models_factory as mfactory
39
from synnefo.logic.utils import get_rsapi_state
40
from synnefo.cyclades_settings import cyclades_services
41
from synnefo.lib.services import get_service_path
42
from synnefo.lib import join_urls
43

    
44
from mock import patch
45

    
46

    
47
class ComputeAPITest(BaseAPITest):
48
    def setUp(self, *args, **kwargs):
49
        super(ComputeAPITest, self).setUp(*args, **kwargs)
50
        self.compute_path = get_service_path(cyclades_services, 'compute',
51
                                             version='v2.0')
52
    def myget(self, path, *args, **kwargs):
53
        path = join_urls(self.compute_path, path)
54
        return self.get(path, *args, **kwargs)
55

    
56
    def myput(self, path, *args, **kwargs):
57
        path = join_urls(self.compute_path, path)
58
        return self.put(path, *args, **kwargs)
59

    
60
    def mypost(self, path, *args, **kwargs):
61
        path = join_urls(self.compute_path, path)
62
        return self.post(path, *args, **kwargs)
63

    
64
    def mydelete(self, path, *args, **kwargs):
65
        path = join_urls(self.compute_path, path)
66
        return self.delete(path, *args, **kwargs)
67

    
68

    
69
class ServerAPITest(ComputeAPITest):
70
    def setUp(self):
71
        self.user1 = 'user1'
72
        self.user2 = 'user2'
73
        self.vm1 = mfactory.VirtualMachineFactory(userid=self.user1)
74
        self.vm2 = mfactory.VirtualMachineFactory(userid=self.user2)
75
        self.vm3 = mfactory.VirtualMachineFactory(deleted=True,
76
                                                  userid=self.user1)
77
        self.vm4 = mfactory.VirtualMachineFactory(userid=self.user2)
78
        super(ServerAPITest, self).setUp()
79

    
80
    def test_server_list_1(self):
81
        """Test if the expected list of servers is returned."""
82
        response = self.myget('servers')
83
        self.assertSuccess(response)
84
        servers = json.loads(response.content)['servers']
85
        self.assertEqual(servers, [])
86

    
87
    def test_server_list_2(self):
88
        """Test if the expected list of servers is returned."""
89
        response = self.myget('servers', self.user1)
90
        self.assertSuccess(response)
91
        servers = json.loads(response.content)['servers']
92
        db_server = self.vm1
93
        server = servers[0]
94
        self.assertEqual(server["name"], db_server.name)
95
        self.assertEqual(server["id"], db_server.id)
96

    
97
    def test_server_list_detail(self):
98
        """Test if the servers list details are returned."""
99
        user = self.user2
100
        user_vms = {self.vm2.id: self.vm2,
101
                    self.vm4.id: self.vm4}
102

    
103
        response = self.myget('servers/detail', user)
104
        servers = json.loads(response.content)['servers']
105
        self.assertEqual(len(servers), len(user_vms))
106
        for api_vm in servers:
107
            db_vm = user_vms[api_vm['id']]
108
            self.assertEqual(api_vm['flavor']["id"], db_vm.flavor.id)
109
            self.assertEqual(api_vm['hostId'], db_vm.hostid)
110
            self.assertEqual(api_vm['id'], db_vm.id)
111
            self.assertEqual(api_vm['image']["id"], db_vm.imageid)
112
            self.assertEqual(api_vm['name'], db_vm.name)
113
            self.assertEqual(api_vm['status'], get_rsapi_state(db_vm))
114
            self.assertSuccess(response)
115

    
116
    def test_server_detail(self):
117
        """Test if a server details are returned."""
118
        db_vm = self.vm2
119
        user = self.vm2.userid
120
        net = mfactory.NetworkFactory()
121
        nic = mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net)
122

    
123
        db_vm_meta = mfactory.VirtualMachineMetadataFactory(vm=db_vm)
124

    
125
        response = self.myget('servers/%d' % db_vm.id, user)
126
        server = json.loads(response.content)['server']
127

    
128
        self.assertEqual(server['flavor']["id"], db_vm.flavor.id)
129
        self.assertEqual(server['hostId'], db_vm.hostid)
130
        self.assertEqual(server['id'], db_vm.id)
131
        self.assertEqual(server['image']["id"], db_vm.imageid)
132
        self.assertEqual(server['name'], db_vm.name)
133
        self.assertEqual(server['status'], get_rsapi_state(db_vm))
134
        api_nic = server['attachments'][0]
135
        self.assertEqual(api_nic['network_id'], str(net.id))
136
        self.assertEqual(api_nic['mac_address'], nic.mac)
137
        self.assertEqual(api_nic['firewallProfile'], nic.firewall_profile)
138
        self.assertEqual(api_nic['ipv4'], nic.ipv4)
139
        self.assertEqual(api_nic['ipv6'], nic.ipv6)
140
        self.assertEqual(api_nic['id'], 'nic-%s-%s' % (db_vm.id, nic.index))
141

    
142
        metadata = server['metadata']
143
        self.assertEqual(len(metadata), 1)
144
        self.assertEqual(metadata[db_vm_meta.meta_key], db_vm_meta.meta_value)
145
        self.assertSuccess(response)
146

    
147
    def test_server_building_nics(self):
148
        db_vm = self.vm2
149
        user = self.vm2.userid
150
        net1 = mfactory.NetworkFactory()
151
        net2 = mfactory.NetworkFactory()
152
        net3 = mfactory.NetworkFactory()
153
        mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net1,
154
                                         state="BUILDING")
155
        nic2 = mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net2,
156
                                                state="ACTIVE")
157
        mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net3,
158
                                         state="BUILDING")
159

    
160
        response = self.myget('servers/%d' % db_vm.id, user)
161
        server = json.loads(response.content)['server']
162
        nics = server["attachments"]
163
        self.assertEqual(len(nics), 1)
164
        self.assertEqual(nics[0]["network_id"], str(nic2.network_id))
165

    
166
    def test_noauthorized(self):
167
        """Test 404 for detail of other user vm"""
168
        db_vm = self.vm2
169

    
170
        response = self.myget('servers/%d' % db_vm.id, 'wrong_user')
171
        self.assertItemNotFound(response)
172

    
173
    def test_wrong_server(self):
174
        """Test 404 response if server does not exist."""
175
        response = self.myget('servers/%d' % 5000)
176
        self.assertItemNotFound(response)
177

    
178
    def test_create_server_empty(self):
179
        """Test if the create server call returns a 400 badRequest if
180
           no attributes are specified."""
181

    
182
        response = self.mypost('servers', params={})
183
        self.assertBadRequest(response)
184

    
185
    def test_rename_server(self):
186
        vm = self.vm2
187
        request = {'server': {'name': 'new_name'}}
188
        response = self.myput('servers/%d' % vm.id, vm.userid,
189
                              json.dumps(request), 'json')
190
        self.assertSuccess(response)
191
        self.assertEqual(VirtualMachine.objects.get(id=vm.id).name, "new_name")
192

    
193
    def test_catch_wrong_api_paths(self):
194
        response = self.myget('nonexistent')
195
        self.assertEqual(response.status_code, 400)
196
        try:
197
            error = json.loads(response.content)
198
        except ValueError:
199
            self.assertTrue(False)
200

    
201

    
202
@patch('synnefo.api.util.get_image')
203
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
204
class ServerCreateAPITest(ComputeAPITest):
205
    def test_create_server(self, mrapi, mimage):
206
        """Test if the create server call returns the expected response
207
           if a valid request has been speficied."""
208
        mimage.return_value = {'location': 'pithos://foo',
209
                               'checksum': '1234',
210
                               "id": 1,
211
                               "name": "test_image",
212
                               'disk_format': 'diskdump'}
213
        mrapi().CreateInstance.return_value = 12
214
        flavor = mfactory.FlavorFactory()
215
        # Create public network and backend
216
        network = mfactory.NetworkFactory(public=True)
217
        backend = mfactory.BackendFactory()
218
        mfactory.BackendNetworkFactory(network=network, backend=backend)
219

    
220
        request = {
221
                    "server": {
222
                        "name": "new-server-test",
223
                        "userid": "test_user",
224
                        "imageRef": 1,
225
                        "flavorRef": flavor.id,
226
                        "metadata": {
227
                            "My Server Name": "Apache1"
228
                        },
229
                        "personality": []
230
                    }
231
        }
232
        with mocked_quotaholder():
233
            response = self.mypost('servers', 'test_user',
234
                                   json.dumps(request), 'json')
235
        self.assertEqual(response.status_code, 202)
236
        mrapi().CreateInstance.assert_called_once()
237

    
238
        api_server = json.loads(response.content)['server']
239
        self.assertEqual(api_server['status'], "BUILD")
240
        self.assertEqual(api_server['progress'], 0)
241
        self.assertEqual(api_server['metadata'],
242
                        {"My Server Name":  "Apache1"})
243
        self.assertTrue('adminPass' in api_server)
244

    
245
        db_vm = VirtualMachine.objects.get(userid='test_user')
246
        self.assertEqual(api_server['name'], db_vm.name)
247
        self.assertEqual(api_server['status'], db_vm.operstate)
248

    
249
        # Test drained flag in Network:
250
        network.drained = True
251
        network.save()
252
        with mocked_quotaholder():
253
            response = self.mypost('servers', 'test_user',
254
                                    json.dumps(request), 'json')
255
        self.assertEqual(response.status_code, 503, "serviceUnavailable")
256

    
257
    def test_create_server_no_flavor(self, mrapi, mimage):
258
        request = {
259
                    "server": {
260
                        "name": "new-server-test",
261
                        "userid": "test_user",
262
                        "imageRef": 1,
263
                        "flavorRef": 42,
264
                        "metadata": {
265
                            "My Server Name": "Apache1"
266
                        },
267
                        "personality": []
268
                    }
269
        }
270
        response = self.mypost('servers', 'test_user',
271
                               json.dumps(request), 'json')
272
        self.assertItemNotFound(response)
273

    
274

    
275
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
276
class ServerDestroyAPITest(ComputeAPITest):
277
    def test_delete_server(self, mrapi):
278
        vm = mfactory.VirtualMachineFactory()
279
        response = self.mydelete('servers/%d' % vm.id, vm.userid)
280
        self.assertEqual(response.status_code, 204)
281
        mrapi().DeleteInstance.assert_called_once()
282

    
283
    def test_non_existing_delete_server(self, mrapi):
284
        vm = mfactory.VirtualMachineFactory()
285
        response = self.mydelete('servers/%d' % 42, vm.userid)
286
        self.assertItemNotFound(response)
287
        self.assertFalse(mrapi.mock_calls)
288

    
289

    
290
class ServerMetadataAPITest(ComputeAPITest):
291
    def setUp(self):
292
        self.vm = mfactory.VirtualMachineFactory()
293
        self.metadata = mfactory.VirtualMachineMetadataFactory(vm=self.vm)
294
        super(ServerMetadataAPITest, self).setUp()
295

    
296
    def test_get_metadata(self):
297
        vm = self.vm
298
        create_meta = lambda: mfactory.VirtualMachineMetadataFactory(vm=vm)
299
        metadata = [create_meta(), create_meta(), create_meta()]
300
        response = self.myget('servers/%d/metadata' % vm.id, vm.userid)
301
        self.assertTrue(response.status_code in [200, 203])
302
        api_metadata = json.loads(response.content)['metadata']
303
        self.assertEqual(len(api_metadata), len(metadata) + 1)
304
        for db_m in metadata:
305
            self.assertEqual(api_metadata[db_m.meta_key], db_m.meta_value)
306

    
307
        request = {'metadata':
308
                        {'foo': 'bar'},
309
                        metadata[0].meta_key: 'bar2'
310
                  }
311
        response = self.mypost('servers/%d/metadata' % vm.id,
312
                             vm.userid, json.dumps(request), 'json')
313
        metadata2 = VirtualMachineMetadata.objects.filter(vm=vm)
314
        response = self.myget('servers/%d/metadata' % vm.id, vm.userid)
315
        self.assertTrue(response.status_code in [200, 203])
316
        api_metadata2 = json.loads(response.content)['metadata']
317
        self.assertTrue('foo' in api_metadata2.keys())
318
        self.assertTrue(api_metadata2[metadata[0].meta_key], 'bar2')
319
        self.assertEqual(len(api_metadata2), len(metadata2))
320
        for db_m in metadata2:
321
            self.assertEqual(api_metadata2[db_m.meta_key], db_m.meta_value)
322

    
323
        # Create new meta
324
        request = {'meta': {'foo2': 'bar2'}}
325
        response = self.myput('servers/%d/metadata/foo2' % vm.id,
326
                              vm.userid, json.dumps(request), 'json')
327

    
328
        # Get the new meta
329
        response = self.myget('servers/%d/metadata/foo2' % vm.id, vm.userid)
330
        meta = json.loads(response.content)['meta']
331
        self.assertEqual(meta['foo2'], 'bar2')
332

    
333
        # Delete the new meta
334
        response = self.mydelete('servers/%d/metadata/foo2' % vm.id, vm.userid)
335
        self.assertEqual(response.status_code, 204)
336

    
337
        # Try to get the deleted meta: should raise 404
338
        response = self.myget('servers/%d/metadata/foo2' % vm.id, vm.userid)
339
        self.assertEqual(response.status_code, 404)
340

    
341
    def test_invalid_metadata(self):
342
        vm = self.vm
343
        response = self.mypost('servers/%d/metadata' % vm.id, vm.userid)
344
        self.assertBadRequest(response)
345
        self.assertEqual(len(vm.metadata.all()), 1)
346

    
347
    def test_invalid_metadata_server(self):
348
        response = self.mypost('servers/42/metadata', 'user')
349
        self.assertItemNotFound(response)
350

    
351
    def test_get_meta_invalid_key(self):
352
        vm = self.vm
353
        response = self.myget('servers/%d/metadata/foo2' % vm.id, vm.userid)
354
        self.assertItemNotFound(response)
355

    
356

    
357
@patch('synnefo.api.util.get_image')
358
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
359
class ServerActionAPITest(ComputeAPITest):
360
    def test_actions(self, mrapi, mimage):
361
        actions = ['start', 'shutdown', 'reboot']
362
        vm = mfactory.VirtualMachineFactory()
363
        vm.operstate = "STOPPED"
364
        vm.save()
365
        for action in actions:
366
            val = {'type': 'HARD'} if action == 'reboot' else {}
367
            request = {action: val}
368
            response = self.mypost('servers/%d/action' % vm.id,
369
                                   vm.userid, json.dumps(request), 'json')
370
            self.assertEqual(response.status_code, 202)
371
            if action == 'shutdown':
372
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).action,
373
                                 "STOP")
374
            else:
375
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).action,
376
                                 action.upper())
377

    
378
    def test_action_in_building_vm(self, mrapi, mimage):
379
        """Test building in progress"""
380
        vm = mfactory.VirtualMachineFactory()
381
        request = {'start': '{}'}
382
        response = self.mypost('servers/%d/action' % vm.id,
383
                               vm.userid, json.dumps(request), 'json')
384
        self.assertEqual(response.status_code, 409)
385
        self.assertFalse(mrapi.mock_calls)
386

    
387
    def test_destroy_build_vm(self, mrapi, mimage):
388
        """Test building in progress"""
389
        vm = mfactory.VirtualMachineFactory()
390
        response = self.mydelete('servers/%d' % vm.id, vm.userid)
391
        self.assertSuccess(response)
392
        mrapi().RemoveInstance.assert_called_once()
393

    
394
    def test_firewall(self, mrapi, mimage):
395
        vm = mfactory.VirtualMachineFactory()
396
        vm.operstate = "STOPPED"
397
        vm.save()
398
        request = {'firewallProfile': {'profile': 'PROTECTED'}}
399
        response = self.mypost('servers/%d/action' % vm.id,
400
                               vm.userid, json.dumps(request), 'json')
401
        self.assertEqual(response.status_code, 202)
402
        mrapi().ModifyInstance.assert_called_once()
403

    
404
    def test_unsupported_firewall(self, mrapi, mimage):
405
        vm = mfactory.VirtualMachineFactory()
406
        vm.operstate = "STOPPED"
407
        vm.save()
408
        request = {'firewallProfile': {'profile': 'FOO'}}
409
        response = self.mypost('servers/%d/action' % vm.id,
410
                               vm.userid, json.dumps(request), 'json')
411
        self.assertBadRequest(response)
412
        self.assertFalse(mrapi.mock_calls)
413

    
414

    
415
class ServerVNCConsole(ComputeAPITest):
416
    def test_not_active_server(self):
417
        """Test console req for not ACTIVE server returns badRequest"""
418
        vm = mfactory.VirtualMachineFactory()
419
        data = json.dumps({'console': {'type': 'vnc'}})
420
        response = self.mypost('servers/%d/action' % vm.id,
421
                               vm.userid, data, 'json')
422
        self.assertBadRequest(response)
423

    
424
    def test_active_server(self):
425
        """Test console req for ACTIVE server"""
426
        vm = mfactory.VirtualMachineFactory()
427
        vm.operstate = 'STARTED'
428
        vm.save()
429

    
430
        data = json.dumps({'console': {'type': 'vnc'}})
431
        response = self.mypost('servers/%d/action' % vm.id,
432
                               vm.userid, data, 'json')
433
        self.assertEqual(response.status_code, 200)
434
        reply = json.loads(response.content)
435
        self.assertEqual(reply.keys(), ['console'])
436
        console = reply['console']
437
        self.assertEqual(console['type'], 'vnc')
438
        self.assertEqual(set(console.keys()),
439
                         set(['type', 'host', 'port', 'password']))
440

    
441
    def test_wrong_console_type(self):
442
        """Test console req for ACTIVE server"""
443
        vm = mfactory.VirtualMachineFactory()
444
        vm.operstate = 'STARTED'
445
        vm.save()
446

    
447
        data = json.dumps({'console': {'type': 'foo'}})
448
        response = self.mypost('servers/%d/action' % vm.id,
449
                               vm.userid, data, 'json')
450
        self.assertBadRequest(response)