floating_ips.py 19.8 KB
Newer Older
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
# 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.

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

Christos Stavrakakis's avatar
Christos Stavrakakis committed
41
42
43
from synnefo.cyclades_settings import cyclades_services
from synnefo.lib.services import get_service_path
from synnefo.lib import join_urls
44

Christos Stavrakakis's avatar
Christos Stavrakakis committed
45

46
47
48
49
network_path = get_service_path(cyclades_services, "network", version="v2.0")
URL = join_urls(network_path, "floatingips")
NETWORKS_URL = join_urls(network_path, "networks")
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
57
58
59
60
61
    def setUp(self):
        self.pool = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
                                                public=True,
                                                subnet__cidr="192.168.2.0/24",
                                                subnet__gateway="192.168.2.1")

62
63
64
    def test_no_floating_ip(self):
        response = self.get(URL)
        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
70
71
        with mocked_quotaholder():
            response = self.get(URL, "user1")
        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
76
                          "floating_ip_address": ip.address,
                          "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
82

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

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

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

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

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

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

173
        # Full network
174
175
176
177
178
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
                                          public=True,
                                          subnet__cidr="192.168.2.0/31",
                                          subnet__gateway="192.168.2.1",
                                          subnet__pool__size=0)
179
180
181
        request = {"floatingip": {
            'floating_network_id': net.id}
            }
182
183
        response = self.post(URL, "test_user", json.dumps(request), "json")
        self.assertConflict(response)
184

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

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

208
209
        # Used by instance
        self.pool.reserve_address("192.168.2.20")
210
211
212
213
        request = {"floatingip": {
            "floating_network_id": self.pool.id,
            "floating_ip_address": "192.168.2.20"}
            }
214
215
216
217
218
        with mocked_quotaholder():
            response = self.post(URL, "test_user", json.dumps(request), "json")
        self.assertFault(response, 409, "conflict")

        # Address out of pool
219
220
221
222
        request = {"floatingip": {
            "floating_network_id": self.pool.id,
            "floating_ip_address": "192.168.3.5"}
            }
223
224
225
226
        with mocked_quotaholder():
            response = self.post(URL, "test_user", json.dumps(request), "json")
        self.assertBadRequest(response)

227
    '''
228
229
230
231
232
233
234
235
236
237
    @patch("synnefo.db.models.get_rapi_client")
    def test_reserve_and_connect(self, mrapi):
        vm = mf.VirtualMachineFactory(userid="test_user")
        request = {"floatingip": {
            "floating_network_id": self.pool.id,
            "floating_ip_address": "192.168.2.12",
            "device_id": vm.id}
            }
        response = self.post(URL, "test_user", json.dumps(request), "json")
        ip = floating_ips.get()
238
        api_ip = json.loads(response.content, "utf-8")["floatingip"]
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
        self.assertEqual(api_ip,
                         {"instance_id": str(vm.id),
                          "floating_ip_address": "192.168.2.12",
                          "fixed_ip_address": None,
                          "id": str(ip.id),
                          "port_id": str(vm.nics.all()[0].id),
                          "floating_network_id": str(self.pool.id)})

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

    def test_update_attach_conflict(self):
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
        vm = mf.VirtualMachineFactory(userid="user1")
        request = {"floatingip": {
            "device_id": vm.id}
            }
        with mocked_quotaholder():
            response = self.put(URL + "/%s" % ip.id, "user1",
                                json.dumps(request), "json")
        self.assertEqual(response.status_code, 409)

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

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

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

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

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

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

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

    def test_release(self):
336
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
337
338
339
        with mocked_quotaholder():
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
        self.assertSuccess(response)
340
        ips_after = floating_ips.filter(id=ip.id)
341
        self.assertEqual(len(ips_after), 0)
342

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

364
'''
Christos Stavrakakis's avatar
Christos Stavrakakis committed
365
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
366
367


368
class FloatingIPPoolsAPITest(BaseAPITest):
369
370
371
372
373
374
    def test_no_pool(self):
        response = self.get(POOLS_URL)
        self.assertSuccess(response)
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])

    def test_list_pools(self):
375
376
377
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
                                          public=True,
                                          subnet__cidr="192.168.2.0/30",
378
379
380
                                          subnet__gateway="192.168.2.1",
                                          subnet__pool__size=1,
                                          subnet__pool__offset=1)
381
382
383
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
384
385
386
        response = self.get(POOLS_URL)
        self.assertSuccess(response)
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
387
                         [{"name": str(net.id), "size": 1, "free": 1}])
388
389


390
class FloatingIPActionsTest(BaseAPITest):
391
    def setUp(self):
392
393
394
        self.vm = VirtualMachineFactory()
        self.vm.operstate = "ACTIVE"
        self.vm.save()
395
396

    def test_bad_request(self):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
397
        url = SERVERS_URL + "/%s/action" % self.vm.id
398
399
400
401
402
403
404
405
406
407
        response = self.post(url, self.vm.userid, json.dumps({}), "json")
        self.assertBadRequest(response)
        response = self.post(url, self.vm.userid,
                             json.dumps({"addFloatingIp": {}}),
                             "json")
        self.assertBadRequest(response)

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

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