Statistics
| Branch: | Tag: | Revision:

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

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

    
82
    def test_get_ip(self):
83
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
84
        with mocked_quotaholder():
85
            response = self.get(URL + "/%s" % ip.id, "user1")
86
        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
                          "floating_ip_address": ip.address,
91
                          "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
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
99
        response = self.delete(URL + "/%s" % ip.id, "user2")
100
        self.assertItemNotFound(response)
101

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

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

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

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

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

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

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

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

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

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

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

259
    def test_update_attach_conflict(self):
260
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
261
        vm = mf.VirtualMachineFactory(userid="user1")
262
        request = {"floatingip": {
263
            "device_id": vm.id}
264
            }
265
        with mocked_quotaholder():
266
            response = self.put(URL + "/%s" % ip.id, "user1",
267
                                json.dumps(request), "json")
268
        self.assertEqual(response.status_code, 409)
269

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

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

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

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

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

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

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

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

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

    
364
'''
365
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
366

367

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

374
    def test_list_pools(self):
375
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
376
                                          public=True,
377
                                          subnet__cidr="192.168.2.0/30",
378
                                          subnet__gateway="192.168.2.1",
379
                                          subnet__pool__size=1,
380
                                          subnet__pool__offset=1)
381
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
382
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
383
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
384
        response = self.get(POOLS_URL)
385
        self.assertSuccess(response)
386
        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
        self.vm = VirtualMachineFactory()
393
        self.vm.operstate = "ACTIVE"
394
        self.vm.save()
395

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

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

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