Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / tests / floating_ips.py @ 828a28df

History | View | Annotate | Download (20.2 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 django.utils import simplejson as json
35
from snf_django.utils.testing import BaseAPITest, mocked_quotaholder
36
from synnefo.db.models import IPAddress, Network, Subnet, IPPoolTable
37
from synnefo.db import models_factory as mf
38

    
39
from mock import patch, Mock
40

    
41
from synnefo.cyclades_settings import cyclades_services
42
from synnefo.lib.services import get_service_path
43
from synnefo.lib import join_urls
44

    
45

    
46
network_path = get_service_path(cyclades_services, "network", version="v2.0")
47
URL = join_urls(network_path, "floatingips")
48
NETWORKS_URL = join_urls(network_path, "networks")
49
SERVERS_URL = join_urls(network_path, "servers")
50

    
51

    
52
floating_ips = IPAddress.objects.filter(floating_ip=True)
53

    
54

    
55
class FloatingIPAPITest(BaseAPITest):
56
    def setUp(self):
57
        self.pool = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
58
                                                public=True,
59
                                                subnet__cidr="192.168.2.0/24",
60
                                                subnet__gateway="192.168.2.1")
61

    
62
    def test_no_floating_ip(self):
63
        response = self.get(URL)
64
        self.assertSuccess(response)
65
        self.assertEqual(json.loads(response.content)["floatingips"], [])
66

    
67
    def test_list_ips(self):
68
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
69
        with mocked_quotaholder():
70
            response = self.get(URL, "user1")
71
        self.assertSuccess(response)
72
        api_ip = json.loads(response.content)["floatingips"][0]
73
        self.assertEqual(api_ip,
74
                         {"instance_id": str(ip.nic.machine_id),
75
                          "floating_ip_address": ip.address,
76
                          "fixed_ip_address": None,
77
                          "id": str(ip.id),
78
                          "port_id": str(ip.nic.id),
79
                          "deleted": False,
80
                          "floating_network_id": str(ip.network_id),
81
                          "tenant_id": ip.userid,
82
                          "user_id": ip.userid})
83

    
84
    def test_get_ip(self):
85
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
86
        with mocked_quotaholder():
87
            response = self.get(URL + "/%s" % ip.id, "user1")
88
        self.assertSuccess(response)
89
        api_ip = json.loads(response.content)["floatingip"]
90
        self.assertEqual(api_ip,
91
                         {"instance_id": str(ip.nic.machine_id),
92
                          "floating_ip_address": ip.address,
93
                          "fixed_ip_address": None,
94
                          "id": str(ip.id),
95
                          "port_id": str(ip.nic.id),
96
                          "deleted": False,
97
                          "floating_network_id": str(ip.network_id),
98
                          "tenant_id": ip.userid,
99
                          "user_id": ip.userid})
100

    
101
    def test_wrong_user(self):
102
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
103
        response = self.delete(URL + "/%s" % ip.id, "user2")
104
        self.assertItemNotFound(response)
105

    
106
    def test_deleted_ip(self):
107
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True,
108
                                   deleted=True)
109
        response = self.delete(URL + "/%s" % ip.id, "user1")
110
        self.assertItemNotFound(response)
111

    
112
    def test_reserve(self):
113
        request = {"floatingip": {
114
            "floating_network_id": self.pool.id}
115
            }
116
        with mocked_quotaholder():
117
            response = self.post(URL, "test_user", json.dumps(request), "json")
118
        self.assertSuccess(response)
119
        api_ip = json.loads(response.content, encoding="utf-8")["floatingip"]
120
        ip = floating_ips.get()
121
        self.assertEqual(ip.address, "192.168.2.2")
122
        self.assertEqual(ip.nic, None)
123
        self.assertEqual(ip.network, self.pool)
124
        self.assertEqual(api_ip,
125
                         {"instance_id": None,
126
                          "floating_ip_address": "192.168.2.2",
127
                          "fixed_ip_address": None,
128
                          "id": str(ip.id),
129
                          "port_id": None,
130
                          "deleted": False,
131
                          "floating_network_id": str(self.pool.id),
132
                          "tenant_id": ip.userid,
133
                          "user_id": ip.userid})
134

    
135
    def test_reserve_empty_body(self):
136
        """Test reserve FIP without specifying network."""
137
        request = {"floatingip": {}}
138
        # delete all pools..
139
        IPPoolTable.objects.all().delete()
140
        Subnet.objects.all().delete()
141
        Network.objects.all().delete()
142
        # CASE: no floating IP pool
143
        with mocked_quotaholder():
144
            response = self.post(URL, "test_user", json.dumps(request), "json")
145
        self.assertConflict(response)
146
        # CASE: Full floating IP pool
147
        mf.NetworkWithSubnetFactory(floating_ip_pool=True, public=True,
148
                                    subnet__pool__size=0)
149
        with mocked_quotaholder():
150
            response = self.post(URL, "test_user", json.dumps(request), "json")
151
        self.assertConflict(response)
152
        # CASE: Available floating IP pool
153
        p1 = mf.NetworkWithSubnetFactory(floating_ip_pool=True, public=True,
154
                                         subnet__cidr="192.168.2.0/30",
155
                                         subnet__pool__size=1)
156
        with mocked_quotaholder():
157
            response = self.post(URL, "test_user", json.dumps(request), "json")
158
        self.assertSuccess(response)
159
        floating_ip = json.loads(response.content)["floatingip"]
160
        db_fip = IPAddress.objects.get(id=floating_ip["id"])
161
        self.assertEqual(db_fip.address, floating_ip["floating_ip_address"])
162
        self.assertTrue(db_fip.floating_ip)
163
        # Test that address is reserved
164
        ip_pool = p1.get_ip_pools()[0]
165
        self.assertFalse(ip_pool.is_available(db_fip.address))
166

    
167
    def test_reserve_no_pool(self):
168
        # Network is not a floating IP pool
169
        pool2 = mf.NetworkWithSubnetFactory(floating_ip_pool=False,
170
                                            public=True,
171
                                            subnet__cidr="192.168.2.0/24",
172
                                            subnet__gateway="192.168.2.1")
173
        request = {"floatingip": {
174
            'floating_network_id': pool2.id}
175
            }
176
        response = self.post(URL, "test_user", json.dumps(request), "json")
177
        self.assertConflict(response)
178

    
179
        # Full network
180
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
181
                                          public=True,
182
                                          subnet__cidr="192.168.2.0/31",
183
                                          subnet__gateway="192.168.2.1",
184
                                          subnet__pool__size=0)
185
        request = {"floatingip": {
186
            'floating_network_id': net.id}
187
            }
188
        response = self.post(URL, "test_user", json.dumps(request), "json")
189
        self.assertConflict(response)
190

    
191
    def test_reserve_with_address(self):
192
        request = {"floatingip": {
193
            "floating_network_id": self.pool.id,
194
            "floating_ip_address": "192.168.2.10"}
195
            }
196
        with mocked_quotaholder():
197
            response = self.post(URL, "test_user", json.dumps(request), "json")
198
        self.assertSuccess(response)
199
        ip = floating_ips.get()
200
        self.assertEqual(json.loads(response.content)["floatingip"],
201
                         {"instance_id": None,
202
                          "floating_ip_address": "192.168.2.10",
203
                          "fixed_ip_address": None,
204
                          "id": str(ip.id),
205
                          "port_id": None,
206
                          "deleted": False,
207
                          "floating_network_id": str(self.pool.id),
208
                          "tenant_id": ip.userid,
209
                          "user_id": ip.userid})
210

    
211
        # Already reserved
212
        with mocked_quotaholder():
213
            response = self.post(URL, "test_user", json.dumps(request), "json")
214
        self.assertFault(response, 409, "conflict")
215

    
216
        # Used by instance
217
        self.pool.reserve_address("192.168.2.20")
218
        request = {"floatingip": {
219
            "floating_network_id": self.pool.id,
220
            "floating_ip_address": "192.168.2.20"}
221
            }
222
        with mocked_quotaholder():
223
            response = self.post(URL, "test_user", json.dumps(request), "json")
224
        self.assertFault(response, 409, "conflict")
225

    
226
        # Address out of pool
227
        request = {"floatingip": {
228
            "floating_network_id": self.pool.id,
229
            "floating_ip_address": "192.168.3.5"}
230
            }
231
        with mocked_quotaholder():
232
            response = self.post(URL, "test_user", json.dumps(request), "json")
233
        self.assertBadRequest(response)
234

    
235
    '''
236
    @patch("synnefo.db.models.get_rapi_client")
237
    def test_reserve_and_connect(self, mrapi):
238
        vm = mf.VirtualMachineFactory(userid="test_user")
239
        request = {"floatingip": {
240
            "floating_network_id": self.pool.id,
241
            "floating_ip_address": "192.168.2.12",
242
            "device_id": vm.id}
243
            }
244
        response = self.post(URL, "test_user", json.dumps(request), "json")
245
        ip = floating_ips.get()
246
        api_ip = json.loads(response.content, "utf-8")["floatingip"]
247
        self.assertEqual(api_ip,
248
                         {"instance_id": str(vm.id),
249
                          "floating_ip_address": "192.168.2.12",
250
                          "fixed_ip_address": None,
251
                          "id": str(ip.id),
252
                          "port_id": str(vm.nics.all()[0].id),
253
                          "floating_network_id": str(self.pool.id)})
254

255
    @patch("synnefo.db.models.get_rapi_client")
256
    def test_update_attach(self, mrapi):
257
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
258
        vm = mf.VirtualMachineFactory(userid="user1")
259
        request = {"floatingip": {
260
            "device_id": vm.id}
261
            }
262
        with mocked_quotaholder():
263
            response = self.put(URL + "/%s" % ip.id, "user1",
264
                                json.dumps(request), "json")
265
        self.assertEqual(response.status_code, 202)
266

267
    def test_update_attach_conflict(self):
268
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
269
        vm = mf.VirtualMachineFactory(userid="user1")
270
        request = {"floatingip": {
271
            "device_id": vm.id}
272
            }
273
        with mocked_quotaholder():
274
            response = self.put(URL + "/%s" % ip.id, "user1",
275
                                json.dumps(request), "json")
276
        self.assertEqual(response.status_code, 409)
277

278
    @patch("synnefo.db.models.get_rapi_client")
279
    def test_update_dettach(self, mrapi):
280
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
281
        request = {"floatingip": {
282
            "device_id": None}
283
            }
284
        mrapi().ModifyInstance.return_value = 42
285
        with mocked_quotaholder():
286
            response = self.put(URL + "/%s" % ip.id, "user1",
287
                                json.dumps(request), "json")
288
        self.assertEqual(response.status_code, 202)
289

290
    def test_update_dettach_unassociated(self):
291
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
292
        request = {"floatingip": {}}
293
        with mocked_quotaholder():
294
            response = self.put(URL + "/%s" % ip.id, "user1",
295
                                json.dumps(request), "json")
296
        self.assertEqual(response.status_code, 400)
297

298
    def test_release_in_use(self):
299
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
300
        vm = mf.VirtualMachineFactory(userid="user1")
301
        request = {"floatingip": {
302
            "device_id": vm.id}
303
            }
304
        with mocked_quotaholder():
305
            response = self.put(URL + "/%s" % ip.id, "user1",
306
                                json.dumps(request), "json")
307
        self.assertEqual(response.status_code, 409)
308

309
    @patch("synnefo.db.models.get_rapi_client")
310
    def test_update_dettach(self, mrapi):
311
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
312
        request = {"floatingip": {
313
            "device_id": None}
314
            }
315
        mrapi().ModifyInstance.return_value = 42
316
        with mocked_quotaholder():
317
            response = self.put(URL + "/%s" % ip.id, "user1",
318
                                json.dumps(request), "json")
319
        self.assertEqual(response.status_code, 202)
320

321
    def test_update_dettach_unassociated(self):
322
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
323
        request = {"floatingip": {}}
324
        with mocked_quotaholder():
325
            response = self.put(URL + "/%s" % ip.id, "user1",
326
                                json.dumps(request), "json")
327
        self.assertEqual(response.status_code, 400)
328

329
    def test_release_in_use(self):
330
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
331
        vm = ip.nic.machine
332
        with mocked_quotaholder():
333
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
334
        self.assertFault(response, 409, "conflict")
335
    '''
336

    
337
    def test_update(self):
338
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
339
        with mocked_quotaholder():
340
            response = self.put(URL + "/%s" % ip.id, ip.userid)
341
        self.assertEqual(response.status_code, 501)
342

    
343
    def test_release(self):
344
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
345
        with mocked_quotaholder():
346
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
347
        self.assertSuccess(response)
348
        ips_after = floating_ips.filter(id=ip.id)
349
        self.assertEqual(len(ips_after), 0)
350

    
351
    @patch("synnefo.logic.backend", Mock())
352
    def test_delete_network_with_floating_ips(self):
353
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True,
354
                                   network=self.pool, nic=None)
355
        # Mark the network as non-pubic to not get 403
356
        network = ip.network
357
        network.public = False
358
        network.save()
359
        # Cannot remove network with floating IPs
360
        with mocked_quotaholder():
361
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
362
                                   self.pool.userid)
363
        self.assertConflict(response)
364
        # But we can with only deleted Floating Ips
365
        ip.deleted = True
366
        ip.save()
367
        with mocked_quotaholder():
368
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
369
                                   self.pool.userid)
370
        self.assertSuccess(response)
371

    
372
'''
373
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
374

375

376
class FloatingIPPoolsAPITest(BaseAPITest):
377
    def test_no_pool(self):
378
        response = self.get(POOLS_URL)
379
        self.assertSuccess(response)
380
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])
381

382
    def test_list_pools(self):
383
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
384
                                          public=True,
385
                                          subnet__cidr="192.168.2.0/30",
386
                                          subnet__gateway="192.168.2.1",
387
                                          subnet__pool__size=1,
388
                                          subnet__pool__offset=1)
389
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
390
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
391
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
392
        response = self.get(POOLS_URL)
393
        self.assertSuccess(response)
394
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
395
                         [{"name": str(net.id), "size": 1, "free": 1}])
396

397

398
class FloatingIPActionsTest(BaseAPITest):
399
    def setUp(self):
400
        self.vm = VirtualMachineFactory()
401
        self.vm.operstate = "ACTIVE"
402
        self.vm.save()
403

404
    def test_bad_request(self):
405
        url = SERVERS_URL + "/%s/action" % self.vm.id
406
        response = self.post(url, self.vm.userid, json.dumps({}), "json")
407
        self.assertBadRequest(response)
408
        response = self.post(url, self.vm.userid,
409
                             json.dumps({"addFloatingIp": {}}),
410
                             "json")
411
        self.assertBadRequest(response)
412

413
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
414
    def test_add_floating_ip(self, mock):
415
        # Not exists
416
        url = SERVERS_URL + "/%s/action" % self.vm.id
417
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
418
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
419
        self.assertItemNotFound(response)
420
        # In use
421
        ip = mf.IPv4AddressFactory(floating_ip=True, userid=self.vm.userid)
422
        request = {"addFloatingIp": {"address": ip.address}}
423
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
424
        self.assertConflict(response)
425
        # Success
426
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
427
                                   userid=self.vm.userid)
428
        request = {"addFloatingIp": {"address": ip.address}}
429
        mock().ModifyInstance.return_value = 1
430
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
431
        self.assertEqual(response.status_code, 202)
432
        ip_after = floating_ips.get(id=ip.id)
433
        self.assertEqual(ip_after.nic.machine, self.vm)
434
        nic = self.vm.nics.get()
435
        nic.state = "ACTIVE"
436
        nic.save()
437
        response = self.get(SERVERS_URL + "/%s" % self.vm.id,
438
                            self.vm.userid)
439
        self.assertSuccess(response)
440
        nic = json.loads(response.content)["server"]["attachments"][0]
441
        self.assertEqual(nic["OS-EXT-IPS:type"], "floating")
442

443
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
444
    def test_remove_floating_ip(self, mock):
445
        # Not exists
446
        url = SERVERS_URL + "/%s/action" % self.vm.id
447
        request = {"removeFloatingIp": {"address": "10.0.0.1"}}
448
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
449
        self.assertBadRequest(response)
450
        # Not In Use
451
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
452
                                   userid=self.vm.userid)
453
        request = {"removeFloatingIp": {"address": ip.address}}
454
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
455
        self.assertBadRequest(response)
456
        # Success
457
        ip = mf.IPv4AddressFactory(floating_ip=True,
458
                                   userid=self.vm.userid, nic__machine=self.vm)
459
        request = {"removeFloatingIp": {"address": ip.address}}
460
        mock().ModifyInstance.return_value = 2
461
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
462
        self.assertEqual(response.status_code, 202)
463
        # Yet used. Wait for the callbacks
464
        ip_after = floating_ips.get(id=ip.id)
465
        self.assertEqual(ip_after.nic.machine, self.vm)
466
'''