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

import json
35
from copy import deepcopy
Christos Stavrakakis's avatar
Christos Stavrakakis committed
36

37
38
from snf_django.utils.testing import (BaseAPITest, mocked_quotaholder,
                                      override_settings)
39
40
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata,
                               FloatingIP)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
41
42
from synnefo.db import models_factory as mfactory
from synnefo.logic.utils import get_rsapi_state
43
44
45
from synnefo.cyclades_settings import cyclades_services
from synnefo.lib.services import get_service_path
from synnefo.lib import join_urls
46
from django.conf import settings
Christos Stavrakakis's avatar
Christos Stavrakakis committed
47

48
from mock import patch, Mock
Christos Stavrakakis's avatar
Christos Stavrakakis committed
49
50


51
class ComputeAPITest(BaseAPITest):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
52
53
    def __init__(self, *args, **kwargs):
        super(ComputeAPITest, self).__init__(*args, **kwargs)
54
55
        self.compute_path = get_service_path(cyclades_services, 'compute',
                                             version='v2.0')
56

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
    def myget(self, path, *args, **kwargs):
        path = join_urls(self.compute_path, path)
        return self.get(path, *args, **kwargs)

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

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

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


class ServerAPITest(ComputeAPITest):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
75
76
77
78
79
80
81
82
83
84
85
    def setUp(self):
        self.user1 = 'user1'
        self.user2 = 'user2'
        self.vm1 = mfactory.VirtualMachineFactory(userid=self.user1)
        self.vm2 = mfactory.VirtualMachineFactory(userid=self.user2)
        self.vm3 = mfactory.VirtualMachineFactory(deleted=True,
                                                  userid=self.user1)
        self.vm4 = mfactory.VirtualMachineFactory(userid=self.user2)

    def test_server_list_1(self):
        """Test if the expected list of servers is returned."""
86
        response = self.myget('servers')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
87
        self.assertSuccess(response)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
88
        servers = json.loads(response.content)['servers']
Christos Stavrakakis's avatar
Christos Stavrakakis committed
89
90
91
92
        self.assertEqual(servers, [])

    def test_server_list_2(self):
        """Test if the expected list of servers is returned."""
93
        response = self.myget('servers', self.user1)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
94
        self.assertSuccess(response)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
95
        servers = json.loads(response.content)['servers']
Christos Stavrakakis's avatar
Christos Stavrakakis committed
96
        db_server = self.vm1
97
98
99
        server = servers[0]
        self.assertEqual(server["name"], db_server.name)
        self.assertEqual(server["id"], db_server.id)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
100
101
102
103
104
105
106

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

107
        response = self.myget('servers/detail', user)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
108
        servers = json.loads(response.content)['servers']
Christos Stavrakakis's avatar
Christos Stavrakakis committed
109
110
111
        self.assertEqual(len(servers), len(user_vms))
        for api_vm in servers:
            db_vm = user_vms[api_vm['id']]
112
            self.assertEqual(api_vm['flavor']["id"], db_vm.flavor.id)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
113
114
            self.assertEqual(api_vm['hostId'], db_vm.hostid)
            self.assertEqual(api_vm['id'], db_vm.id)
115
            self.assertEqual(api_vm['image']["id"], db_vm.imageid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
116
117
118
119
120
121
122
123
124
            self.assertEqual(api_vm['name'], db_vm.name)
            self.assertEqual(api_vm['status'], get_rsapi_state(db_vm))
            self.assertSuccess(response)

    def test_server_detail(self):
        """Test if a server details are returned."""
        db_vm = self.vm2
        user = self.vm2.userid
        net = mfactory.NetworkFactory()
125
        nic = mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net,
126
                                               ipv6="::babe")
Christos Stavrakakis's avatar
Christos Stavrakakis committed
127
128
129

        db_vm_meta = mfactory.VirtualMachineMetadataFactory(vm=db_vm)

130
        response = self.myget('servers/%d' % db_vm.id, user)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
131
132
        server = json.loads(response.content)['server']

133
        self.assertEqual(server['flavor']["id"], db_vm.flavor.id)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
134
135
        self.assertEqual(server['hostId'], db_vm.hostid)
        self.assertEqual(server['id'], db_vm.id)
136
        self.assertEqual(server['image']["id"], db_vm.imageid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
137
138
        self.assertEqual(server['name'], db_vm.name)
        self.assertEqual(server['status'], get_rsapi_state(db_vm))
Christos Stavrakakis's avatar
Christos Stavrakakis committed
139
        api_nic = server['attachments'][0]
Christos Stavrakakis's avatar
Christos Stavrakakis committed
140
141
142
143
144
        self.assertEqual(api_nic['network_id'], str(net.id))
        self.assertEqual(api_nic['mac_address'], nic.mac)
        self.assertEqual(api_nic['firewallProfile'], nic.firewall_profile)
        self.assertEqual(api_nic['ipv4'], nic.ipv4)
        self.assertEqual(api_nic['ipv6'], nic.ipv6)
145
        self.assertEqual(api_nic['OS-EXT-IPS:type'], "fixed")
Christos Stavrakakis's avatar
Christos Stavrakakis committed
146
        self.assertEqual(api_nic['id'], 'nic-%s-%s' % (db_vm.id, nic.index))
147
        api_address = server["addresses"]
148
149
150
151
        self.assertEqual(api_address[str(net.id)], [
            {"version": 4, "addr": nic.ipv4, "OS-EXT-IPS:type": "fixed"},
            {"version": 6, "addr": nic.ipv6, "OS-EXT-IPS:type": "fixed"}
        ])
Christos Stavrakakis's avatar
Christos Stavrakakis committed
152

Christos Stavrakakis's avatar
Christos Stavrakakis committed
153
        metadata = server['metadata']
Christos Stavrakakis's avatar
Christos Stavrakakis committed
154
155
156
157
        self.assertEqual(len(metadata), 1)
        self.assertEqual(metadata[db_vm_meta.meta_key], db_vm_meta.meta_value)
        self.assertSuccess(response)

158
159
160
161
162
163
164
    def test_server_fqdn(self):
        vm = mfactory.VirtualMachineFactory()
        with override_settings(settings,
                               CYCLADES_SERVERS_FQDN="vm.example.org"):
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:fqdn"], "vm.example.org")
165
166
        with override_settings(settings, CYCLADES_SERVERS_FQDN=
                               "snf-%(id)s.vm.example.org"):
167
168
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
169
170
            self.assertEqual(server["SNF:fqdn"],
                             "snf-%d.vm.example.org" % vm.id)
171
        with override_settings(settings,
172
173
                               CYCLADES_SERVERS_FQDN=
                               "snf-%(id)s.vm-%(id)s.example.org"):
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:fqdn"], "snf-%d.vm-%d.example.org" %
                             (vm.id, vm.id))
        # No setting, no NICs
        with override_settings(settings,
                               CYCLADES_SERVERS_FQDN=None):
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:fqdn"], "")

        # IPv6 NIC
        nic = mfactory.NetworkInterfaceFactory(machine=vm, ipv4=None,
                                               ipv6="babe::", state="ACTIVE",
                                               network__public=True)
        with override_settings(settings,
                               CYCLADES_SERVERS_FQDN=None):
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:fqdn"], nic.ipv6)

        # IPv4 NIC
196
197
198
        nic = mfactory.NetworkInterfaceFactory(machine=vm,
                                               network__public=True,
                                               state="ACTIVE")
199
200
201
202
203
204
        with override_settings(settings,
                               CYCLADES_SERVERS_FQDN=None):
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:fqdn"], nic.ipv4)

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    def test_server_port_forwarding(self):
        vm = mfactory.VirtualMachineFactory()
        ports = {
            22: ("foo", 61000),
            80: lambda ip, id, fqdn, user: ("bar", 61001)}
        with override_settings(settings,
                               CYCLADES_PORT_FORWARDING=ports):
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:port_forwarding"],
                             {"22": {"host": "foo", "port": "61000"},
                              "80": {"host": "bar", "port": "61001"}})

        def _port_from_ip(ip, base):
            fields = ip.split('.', 4)
            return (base + 256*int(fields[2]) + int(fields[3]))

        ports = {
            22: lambda ip, id, fqdn, user:
            ip and ("gate", _port_from_ip(ip, 10000)) or None}
        with override_settings(settings,
                               CYCLADES_PORT_FORWARDING=ports):
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:port_forwarding"], {})

        mfactory.NetworkInterfaceFactory(machine=vm, ipv4="192.168.2.2",
                                         network__public=True)
        with override_settings(settings,
                               CYCLADES_PORT_FORWARDING=ports):
            response = self.myget("servers/%d" % vm.id, vm.userid)
            server = json.loads(response.content)['server']
            self.assertEqual(server["SNF:port_forwarding"],
                             {"22": {"host": "gate", "port": "10514"}})

240
241
242
243
244
245
246
247
248
249
250
251
252
    def test_server_building_nics(self):
        db_vm = self.vm2
        user = self.vm2.userid
        net1 = mfactory.NetworkFactory()
        net2 = mfactory.NetworkFactory()
        net3 = mfactory.NetworkFactory()
        mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net1,
                                         state="BUILDING")
        nic2 = mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net2,
                                                state="ACTIVE")
        mfactory.NetworkInterfaceFactory(machine=self.vm2, network=net3,
                                         state="BUILDING")

253
        response = self.myget('servers/%d' % db_vm.id, user)
254
        server = json.loads(response.content)['server']
Christos Stavrakakis's avatar
Christos Stavrakakis committed
255
        nics = server["attachments"]
256
257
258
        self.assertEqual(len(nics), 1)
        self.assertEqual(nics[0]["network_id"], str(nic2.network_id))

Christos Stavrakakis's avatar
Christos Stavrakakis committed
259
260
261
262
    def test_noauthorized(self):
        """Test 404 for detail of other user vm"""
        db_vm = self.vm2

263
        response = self.myget('servers/%d' % db_vm.id, 'wrong_user')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
264
265
266
267
        self.assertItemNotFound(response)

    def test_wrong_server(self):
        """Test 404 response if server does not exist."""
268
        response = self.myget('servers/%d' % 5000)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
269
270
271
272
273
274
        self.assertItemNotFound(response)

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

275
        response = self.mypost('servers', params={})
Christos Stavrakakis's avatar
Christos Stavrakakis committed
276
277
278
279
280
        self.assertBadRequest(response)

    def test_rename_server(self):
        vm = self.vm2
        request = {'server': {'name': 'new_name'}}
281
282
        response = self.myput('servers/%d' % vm.id, vm.userid,
                              json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
283
284
285
        self.assertSuccess(response)
        self.assertEqual(VirtualMachine.objects.get(id=vm.id).name, "new_name")

286
287
288
289
    def test_catch_wrong_api_paths(self):
        response = self.myget('nonexistent')
        self.assertEqual(response.status_code, 400)
        try:
290
            json.loads(response.content)
291
292
293
        except ValueError:
            self.assertTrue(False)

294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
    def test_method_not_allowed(self, *args):
        # /servers/ allows only POST, GET
        response = self.myput('servers', '', '')
        self.assertMethodNotAllowed(response)
        response = self.mydelete('servers')
        self.assertMethodNotAllowed(response)

        # /servers/<srvid>/ allows only GET, PUT, DELETE
        response = self.mypost("servers/42")
        self.assertMethodNotAllowed(response)

        # /imags/<srvid>/metadata/ allows only POST, GET
        response = self.myput('servers/42/metadata', '', '')
        self.assertMethodNotAllowed(response)
        response = self.mydelete('servers/42/metadata')
        self.assertMethodNotAllowed(response)

        # /imags/<srvid>/metadata/ allows only POST, GET
        response = self.myput('servers/42/metadata', '', '')
        self.assertMethodNotAllowed(response)
        response = self.mydelete('servers/42/metadata')
        self.assertMethodNotAllowed(response)

        # /imags/<srvid>/metadata/<key> allows only PUT, GET, DELETE
        response = self.mypost('servers/42/metadata/foo')
        self.assertMethodNotAllowed(response)

Christos Stavrakakis's avatar
Christos Stavrakakis committed
321

322
323
324
325
326
fixed_image = Mock()
fixed_image.return_value = {'location': 'pithos://foo',
                            'checksum': '1234',
                            "id": 1,
                            "name": "test_image",
327
                            "size": "41242",
328
329
330
331
                            'disk_format': 'diskdump'}


@patch('synnefo.api.util.get_image', fixed_image)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
332
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
333
class ServerCreateAPITest(ComputeAPITest):
334
335
    def setUp(self):
        self.flavor = mfactory.FlavorFactory()
Christos Stavrakakis's avatar
Christos Stavrakakis committed
336
        # Create public network and backend
337
338
339
340
341
342
        self.network = mfactory.NetworkFactory(public=True)
        self.backend = mfactory.BackendFactory()
        mfactory.BackendNetworkFactory(network=self.network,
                                       backend=self.backend,
                                       operstate="ACTIVE")
        self.request = {
343
344
345
346
347
348
349
350
351
352
            "server": {
                "name": "new-server-test",
                "userid": "test_user",
                "imageRef": 1,
                "flavorRef": self.flavor.id,
                "metadata": {
                    "My Server Name": "Apache1"
                },
                "personality": []
            }
Christos Stavrakakis's avatar
Christos Stavrakakis committed
353
        }
354
355
356
357
358
359
360
361

    def test_create_server(self, mrapi):
        """Test if the create server call returns the expected response
           if a valid request has been speficied."""

        mrapi().CreateInstance.return_value = 12
        with override_settings(settings, DEFAULT_INSTANCE_NETWORKS=[]):
            with mocked_quotaholder():
Christos Stavrakakis's avatar
Christos Stavrakakis committed
362
                response = self.mypost('servers', 'test_user',
363
                                       json.dumps(self.request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
364
365
366
367
368
369
        self.assertEqual(response.status_code, 202)
        mrapi().CreateInstance.assert_called_once()

        api_server = json.loads(response.content)['server']
        self.assertEqual(api_server['status'], "BUILD")
        self.assertEqual(api_server['progress'], 0)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
370
        self.assertEqual(api_server['metadata'],
371
                         {"My Server Name":  "Apache1"})
Christos Stavrakakis's avatar
Christos Stavrakakis committed
372
373
374
375
376
377
        self.assertTrue('adminPass' in api_server)

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

378
        # Test drained flag in Network:
379
380
        self.network.drained = True
        self.network.save()
381
        with mocked_quotaholder():
382
            response = self.mypost('servers', 'test_user',
383
                                   json.dumps(self.request), 'json')
384
385
        self.assertEqual(response.status_code, 503, "serviceUnavailable")

386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
    def test_create_network_settings(self, mrapi):
        mrapi().CreateInstance.return_value = 12
        bnet1 = mfactory.BackendNetworkFactory(operstate="ACTIVE",
                                               backend=self.backend)
        bnet2 = mfactory.BackendNetworkFactory(operstate="ACTIVE",
                                               backend=self.backend)
        bnet3 = mfactory.BackendNetworkFactory(network__userid="test_user",
                                               operstate="ACTIVE",
                                               backend=self.backend)
        bnet4 = mfactory.BackendNetworkFactory(network__userid="test_user",
                                               operstate="ACTIVE",
                                               backend=self.backend)
        # User requested private networks
        request = deepcopy(self.request)
        request["server"]["networks"] = [bnet3.network.id, bnet4.network.id]
        with override_settings(settings,
402
403
404
405
                               DEFAULT_INSTANCE_NETWORKS=[
                                   "SNF:ANY_PUBLIC",
                                   bnet1.network.id,
                                   bnet2.network.id]):
406
            with mocked_quotaholder():
Christos Stavrakakis's avatar
Christos Stavrakakis committed
407
                response = self.mypost('servers', 'test_user',
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
                                       json.dumps(request), 'json')
        self.assertEqual(response.status_code, 202)
        name, args, kwargs = mrapi().CreateInstance.mock_calls[0]
        self.assertEqual(len(kwargs["nics"]), 5)
        self.assertEqual(kwargs["nics"][0]["network"],
                         self.network.backend_id)
        self.assertEqual(kwargs["nics"][1]["network"],
                         bnet1.network.backend_id)
        self.assertEqual(kwargs["nics"][2]["network"],
                         bnet2.network.backend_id)
        self.assertEqual(kwargs["nics"][3]["network"],
                         bnet3.network.backend_id)
        self.assertEqual(kwargs["nics"][4]["network"],
                         bnet4.network.backend_id)

423
        request["server"]["floating_ips"] = []
424
        with override_settings(settings,
425
                               DEFAULT_INSTANCE_NETWORKS=[bnet2.network.id]):
426
            with mocked_quotaholder():
Christos Stavrakakis's avatar
Christos Stavrakakis committed
427
                response = self.mypost('servers', 'test_user',
428
429
430
431
432
433
434
435
436
437
438
439
440
                                       json.dumps(request), 'json')
        self.assertEqual(response.status_code, 202)
        name, args, kwargs = mrapi().CreateInstance.mock_calls[1]
        self.assertEqual(len(kwargs["nics"]), 3)
        self.assertEqual(kwargs["nics"][0]["network"],
                         bnet2.network.backend_id)
        self.assertEqual(kwargs["nics"][1]["network"],
                         bnet3.network.backend_id)
        self.assertEqual(kwargs["nics"][2]["network"],
                         bnet4.network.backend_id)

        # test invalid network in DEFAULT_INSTANCE_NETWORKS
        with override_settings(settings, DEFAULT_INSTANCE_NETWORKS=[42]):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
441
            response = self.mypost('servers', 'test_user',
442
443
444
445
446
447
                                   json.dumps(request), 'json')
        self.assertFault(response, 500, "internalServerError")

        # test connect to public netwok
        request = deepcopy(self.request)
        request["server"]["networks"] = [self.network.id]
448
449
        with override_settings(settings,
                               DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
450
            response = self.mypost('servers', 'test_user',
451
                                   json.dumps(request), 'json')
452
453
454
455
        self.assertFault(response, 403, "forbidden")
        # test wrong user
        request = deepcopy(self.request)
        request["server"]["networks"] = [bnet3.network.id]
456
457
        with override_settings(settings,
                               DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]):
458
            with mocked_quotaholder():
Christos Stavrakakis's avatar
Christos Stavrakakis committed
459
                response = self.mypost('servers', 'dummy_user',
460
461
462
                                       json.dumps(request), 'json')
        self.assertItemNotFound(response)

463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
        # Test floating IPs
        request = deepcopy(self.request)
        request["server"]["networks"] = [bnet4.network.id]
        network = mfactory.NetworkFactory(subnet="10.0.0.0/24")
        mfactory.BackendNetworkFactory(network=network,
                                       backend=self.backend,
                                       operstate="ACTIVE")
        fp1 = mfactory.FloatingIPFactory(ipv4="10.0.0.2",
                                         userid="test_user",
                                         network=network, machine=None)
        fp2 = mfactory.FloatingIPFactory(ipv4="10.0.0.3", network=network,
                                         userid="test_user",
                                         machine=None)
        request["server"]["floating_ips"] = [fp1.ipv4, fp2.ipv4]
        with override_settings(settings,
478
                               DEFAULT_INSTANCE_NETWORKS=[bnet3.network.id]):
479
            with mocked_quotaholder():
Christos Stavrakakis's avatar
Christos Stavrakakis committed
480
                response = self.mypost('servers', 'test_user',
481
                                       json.dumps(request), 'json')
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
        self.assertEqual(response.status_code, 202)
        api_server = json.loads(response.content)['server']
        vm = VirtualMachine.objects.get(id=api_server["id"])
        fp1 = FloatingIP.objects.get(id=fp1.id)
        fp2 = FloatingIP.objects.get(id=fp2.id)
        self.assertEqual(fp1.machine, vm)
        self.assertEqual(fp2.machine, vm)
        name, args, kwargs = mrapi().CreateInstance.mock_calls[2]
        self.assertEqual(len(kwargs["nics"]), 4)
        self.assertEqual(kwargs["nics"][0]["network"],
                         bnet3.network.backend_id)
        self.assertEqual(kwargs["nics"][1]["network"], network.backend_id)
        self.assertEqual(kwargs["nics"][1]["ip"], fp1.ipv4)
        self.assertEqual(kwargs["nics"][2]["network"], network.backend_id)
        self.assertEqual(kwargs["nics"][2]["ip"], fp2.ipv4)
        self.assertEqual(kwargs["nics"][3]["network"],
                         bnet4.network.backend_id)

500
501
502
503
    def test_create_server_no_flavor(self, mrapi):
        request = deepcopy(self.request)
        request["server"]["flavorRef"] = 42
        with mocked_quotaholder():
Christos Stavrakakis's avatar
Christos Stavrakakis committed
504
            response = self.mypost('servers', 'test_user',
505
                                   json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
506
507
508
509
        self.assertItemNotFound(response)


@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
510
class ServerDestroyAPITest(ComputeAPITest):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
511
512
    def test_delete_server(self, mrapi):
        vm = mfactory.VirtualMachineFactory()
513
        mrapi().DeleteInstance.return_value = 12
Christos Stavrakakis's avatar
Christos Stavrakakis committed
514
        response = self.mydelete('servers/%d' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
515
516
517
518
519
        self.assertEqual(response.status_code, 204)
        mrapi().DeleteInstance.assert_called_once()

    def test_non_existing_delete_server(self, mrapi):
        vm = mfactory.VirtualMachineFactory()
520
        response = self.mydelete('servers/%d' % 42, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
521
522
523
524
        self.assertItemNotFound(response)
        self.assertFalse(mrapi.mock_calls)


525
class ServerMetadataAPITest(ComputeAPITest):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
526
527
528
    def setUp(self):
        self.vm = mfactory.VirtualMachineFactory()
        self.metadata = mfactory.VirtualMachineMetadataFactory(vm=self.vm)
529
        super(ServerMetadataAPITest, self).setUp()
Christos Stavrakakis's avatar
Christos Stavrakakis committed
530
531
532
533
534

    def test_get_metadata(self):
        vm = self.vm
        create_meta = lambda: mfactory.VirtualMachineMetadataFactory(vm=vm)
        metadata = [create_meta(), create_meta(), create_meta()]
535
        response = self.myget('servers/%d/metadata' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
536
        self.assertTrue(response.status_code in [200, 203])
Christos Stavrakakis's avatar
Christos Stavrakakis committed
537
        api_metadata = json.loads(response.content)['metadata']
Christos Stavrakakis's avatar
Christos Stavrakakis committed
538
539
540
541
        self.assertEqual(len(api_metadata), len(metadata) + 1)
        for db_m in metadata:
            self.assertEqual(api_metadata[db_m.meta_key], db_m.meta_value)

542
543
544
545
546
547
        request = {
            'metadata': {
                'foo': 'bar'
            },
            metadata[0].meta_key: 'bar2'
        }
548
        response = self.mypost('servers/%d/metadata' % vm.id,
549
                               vm.userid, json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
550
        metadata2 = VirtualMachineMetadata.objects.filter(vm=vm)
551
        response = self.myget('servers/%d/metadata' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
552
        self.assertTrue(response.status_code in [200, 203])
Christos Stavrakakis's avatar
Christos Stavrakakis committed
553
        api_metadata2 = json.loads(response.content)['metadata']
Christos Stavrakakis's avatar
Christos Stavrakakis committed
554
555
556
557
558
559
560
561
        self.assertTrue('foo' in api_metadata2.keys())
        self.assertTrue(api_metadata2[metadata[0].meta_key], 'bar2')
        self.assertEqual(len(api_metadata2), len(metadata2))
        for db_m in metadata2:
            self.assertEqual(api_metadata2[db_m.meta_key], db_m.meta_value)

        # Create new meta
        request = {'meta': {'foo2': 'bar2'}}
562
563
        response = self.myput('servers/%d/metadata/foo2' % vm.id,
                              vm.userid, json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
564
565

        # Get the new meta
566
        response = self.myget('servers/%d/metadata/foo2' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
567
568
569
570
        meta = json.loads(response.content)['meta']
        self.assertEqual(meta['foo2'], 'bar2')

        # Delete the new meta
571
        response = self.mydelete('servers/%d/metadata/foo2' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
572
573
574
        self.assertEqual(response.status_code, 204)

        # Try to get the deleted meta: should raise 404
575
        response = self.myget('servers/%d/metadata/foo2' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
576
577
578
579
        self.assertEqual(response.status_code, 404)

    def test_invalid_metadata(self):
        vm = self.vm
580
        response = self.mypost('servers/%d/metadata' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
581
582
583
584
        self.assertBadRequest(response)
        self.assertEqual(len(vm.metadata.all()), 1)

    def test_invalid_metadata_server(self):
585
        response = self.mypost('servers/42/metadata', 'user')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
586
587
588
589
        self.assertItemNotFound(response)

    def test_get_meta_invalid_key(self):
        vm = self.vm
590
        response = self.myget('servers/%d/metadata/foo2' % vm.id, vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
591
592
593
594
595
        self.assertItemNotFound(response)


@patch('synnefo.api.util.get_image')
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
596
class ServerActionAPITest(ComputeAPITest):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
597
598
599
600
601
    def test_actions(self, mrapi, mimage):
        actions = ['start', 'shutdown', 'reboot']
        vm = mfactory.VirtualMachineFactory()
        vm.operstate = "STOPPED"
        vm.save()
602
603
604
605
606
607
608
609
610
611
612
        mrapi().StartupInstance.return_value = 0
        mrapi().ShutdownInstance.return_value = 1
        mrapi().RebootInstance.return_value = 2
        for jobId, action in enumerate(actions):
            if action in ["shutdown", "reboot"]:
                vm.operstate = "STARTED"
            else:
                vm.operstate = "STOPPED"
            vm.task = None
            vm.task_job_id = None
            vm.save()
Christos Stavrakakis's avatar
Christos Stavrakakis committed
613
614
            val = {'type': 'HARD'} if action == 'reboot' else {}
            request = {action: val}
615
616
            response = self.mypost('servers/%d/action' % vm.id,
                                   vm.userid, json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
617
618
            self.assertEqual(response.status_code, 202)
            if action == 'shutdown':
619
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).task,
Christos Stavrakakis's avatar
Christos Stavrakakis committed
620
621
                                 "STOP")
            else:
622
                self.assertEqual(VirtualMachine.objects.get(id=vm.id).task,
Christos Stavrakakis's avatar
Christos Stavrakakis committed
623
                                 action.upper())
624
625
            self.assertEqual(VirtualMachine.objects.get(id=vm.id).task_job_id,
                             jobId)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
626
627
628

    def test_action_in_building_vm(self, mrapi, mimage):
        """Test building in progress"""
629
630
631
        vm = mfactory.VirtualMachineFactory(operstate="BUILD")
        request = {'start': {}}
        with mocked_quotaholder():
Christos Stavrakakis's avatar
Christos Stavrakakis committed
632
            response = self.mypost('servers/%d/action' % vm.id,
633
                                   vm.userid, json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
634
635
636
637
638
639
        self.assertEqual(response.status_code, 409)
        self.assertFalse(mrapi.mock_calls)

    def test_destroy_build_vm(self, mrapi, mimage):
        """Test building in progress"""
        vm = mfactory.VirtualMachineFactory()
640
        mrapi().DeleteInstance.return_value = 2
Christos Stavrakakis's avatar
Christos Stavrakakis committed
641
        response = self.mydelete('servers/%d' % vm.id,
642
                                 vm.userid)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
643
644
645
646
647
648
649
650
        self.assertSuccess(response)
        mrapi().RemoveInstance.assert_called_once()

    def test_firewall(self, mrapi, mimage):
        vm = mfactory.VirtualMachineFactory()
        vm.operstate = "STOPPED"
        vm.save()
        request = {'firewallProfile': {'profile': 'PROTECTED'}}
651
652
        response = self.mypost('servers/%d/action' % vm.id,
                               vm.userid, json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
653
654
655
656
657
658
659
660
        self.assertEqual(response.status_code, 202)
        mrapi().ModifyInstance.assert_called_once()

    def test_unsupported_firewall(self, mrapi, mimage):
        vm = mfactory.VirtualMachineFactory()
        vm.operstate = "STOPPED"
        vm.save()
        request = {'firewallProfile': {'profile': 'FOO'}}
661
662
        response = self.mypost('servers/%d/action' % vm.id,
                               vm.userid, json.dumps(request), 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
663
664
665
        self.assertBadRequest(response)
        self.assertFalse(mrapi.mock_calls)

666
667
668
669
670
    def test_resize_vm(self, mrapi, mimage):
        flavor = mfactory.FlavorFactory(cpu=1, ram=1024)
        # Check building VM
        vm = self.get_vm(flavor=flavor, operstate="BUILD")
        request = {'resize': {'flavorRef': flavor.id}}
Christos Stavrakakis's avatar
Christos Stavrakakis committed
671
        response = self.mypost('servers/%d/action' % vm.id,
672
                               vm.userid, json.dumps(request), 'json')
673
674
675
676
        self.assertFault(response, 409, "buildInProgress")
        # Check same Flavor
        vm = self.get_vm(flavor=flavor, operstate="STOPPED")
        request = {'resize': {'flavorRef': flavor.id}}
Christos Stavrakakis's avatar
Christos Stavrakakis committed
677
        response = self.mypost('servers/%d/action' % vm.id,
678
                               vm.userid, json.dumps(request), 'json')
679
        self.assertBadRequest(response)
680
681
682
683
684
        # Check flavor with different disk
        flavor2 = mfactory.FlavorFactory(disk=1024)
        flavor3 = mfactory.FlavorFactory(disk=2048)
        vm = self.get_vm(flavor=flavor2, operstate="STOPPED")
        request = {'resize': {'flavorRef': flavor3.id}}
Christos Stavrakakis's avatar
Christos Stavrakakis committed
685
        response = self.mypost('servers/%d/action' % vm.id,
686
                               vm.userid, json.dumps(request), 'json')
687
688
689
690
691
        self.assertBadRequest(response)
        flavor2 = mfactory.FlavorFactory(disk_template="foo")
        flavor3 = mfactory.FlavorFactory(disk_template="baz")
        vm = self.get_vm(flavor=flavor2, operstate="STOPPED")
        request = {'resize': {'flavorRef': flavor3.id}}
Christos Stavrakakis's avatar
Christos Stavrakakis committed
692
        response = self.mypost('servers/%d/action' % vm.id,
693
                               vm.userid, json.dumps(request), 'json')
694
695
696
697
698
699
700
701
        self.assertBadRequest(response)
        # Check success
        vm = self.get_vm(flavor=flavor, operstate="STOPPED")
        flavor4 = mfactory.FlavorFactory(disk_template=flavor.disk_template,
                                         disk=flavor.disk,
                                         cpu=4, ram=2048)
        request = {'resize': {'flavorRef': flavor4.id}}
        mrapi().ModifyInstance.return_value = 42
Christos Stavrakakis's avatar
Christos Stavrakakis committed
702
        response = self.mypost('servers/%d/action' % vm.id,
703
                               vm.userid, json.dumps(request), 'json')
704
705
        self.assertEqual(response.status_code, 202)
        vm = VirtualMachine.objects.get(id=vm.id)
706
        self.assertEqual(vm.task_job_id, 42)
707
708
709
710
711
        name, args, kwargs = mrapi().ModifyInstance.mock_calls[0]
        self.assertEqual(kwargs["beparams"]["vcpus"], 4)
        self.assertEqual(kwargs["beparams"]["minmem"], 2048)
        self.assertEqual(kwargs["beparams"]["maxmem"], 2048)

712
713
714
715
716
717
    def test_action_on_resizing_vm(self, mrapi, mimage):
        vm = mfactory.VirtualMachineFactory()
        vm.operstate = "RESIZE"
        vm.save()
        for action in VirtualMachine.ACTIONS:
            request = {action[0]: ""}
Christos Stavrakakis's avatar
Christos Stavrakakis committed
718
            response = self.mypost('servers/%d/action' % vm.id,
719
                                   vm.userid, json.dumps(request), 'json')
720
721
722
            self.assertBadRequest(response)
        # however you can destroy
        mrapi().DeleteInstance.return_value = 42
Christos Stavrakakis's avatar
Christos Stavrakakis committed
723
724
        response = self.mydelete('servers/%d' % vm.id,
                                 vm.userid)
725
726
        self.assertSuccess(response)

727
728
729
730
731
732
733
    def get_vm(self, flavor, operstate):
        vm = mfactory.VirtualMachineFactory(flavor=flavor)
        vm.operstate = operstate
        vm.backendjobstatus = "success"
        vm.save()
        return vm

Christos Stavrakakis's avatar
Christos Stavrakakis committed
734

735
class ServerVNCConsole(ComputeAPITest):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
736
737
    def test_not_active_server(self):
        """Test console req for not ACTIVE server returns badRequest"""
738
        vm = mfactory.VirtualMachineFactory(operstate="BUILD")
Christos Stavrakakis's avatar
Christos Stavrakakis committed
739
        data = json.dumps({'console': {'type': 'vnc'}})
740
741
        response = self.mypost('servers/%d/action' % vm.id,
                               vm.userid, data, 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
742
743
744
745
746
747
748
749
750
        self.assertBadRequest(response)

    def test_active_server(self):
        """Test console req for ACTIVE server"""
        vm = mfactory.VirtualMachineFactory()
        vm.operstate = 'STARTED'
        vm.save()

        data = json.dumps({'console': {'type': 'vnc'}})
751
        with override_settings(settings, TEST=True):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
752
            response = self.mypost('servers/%d/action' % vm.id,
753
                                   vm.userid, data, 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
        self.assertEqual(response.status_code, 200)
        reply = json.loads(response.content)
        self.assertEqual(reply.keys(), ['console'])
        console = reply['console']
        self.assertEqual(console['type'], 'vnc')
        self.assertEqual(set(console.keys()),
                         set(['type', 'host', 'port', 'password']))

    def test_wrong_console_type(self):
        """Test console req for ACTIVE server"""
        vm = mfactory.VirtualMachineFactory()
        vm.operstate = 'STARTED'
        vm.save()

        data = json.dumps({'console': {'type': 'foo'}})
769
770
        response = self.mypost('servers/%d/action' % vm.id,
                               vm.userid, data, 'json')
Christos Stavrakakis's avatar
Christos Stavrakakis committed
771
        self.assertBadRequest(response)