Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (19.5 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
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
                          "floating_network_id": str(ip.network_id)})
80

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

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

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

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

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

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

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

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

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

    
202
        # Used by instance
203
        self.pool.reserve_address("192.168.2.20")
204
        request = {"floatingip": {
205
            "floating_network_id": self.pool.id,
206
            "floating_ip_address": "192.168.2.20"}
207
            }
208
        with mocked_quotaholder():
209
            response = self.post(URL, "test_user", json.dumps(request), "json")
210
        self.assertFault(response, 409, "conflict")
211

    
212
        # Address out of pool
213
        request = {"floatingip": {
214
            "floating_network_id": self.pool.id,
215
            "floating_ip_address": "192.168.3.5"}
216
            }
217
        with mocked_quotaholder():
218
            response = self.post(URL, "test_user", json.dumps(request), "json")
219
        self.assertBadRequest(response)
220

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

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

253
    def test_update_attach_conflict(self):
254
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
255
        vm = mf.VirtualMachineFactory(userid="user1")
256
        request = {"floatingip": {
257
            "device_id": vm.id}
258
            }
259
        with mocked_quotaholder():
260
            response = self.put(URL + "/%s" % ip.id, "user1",
261
                                json.dumps(request), "json")
262
        self.assertEqual(response.status_code, 409)
263

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

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

284
    def test_release_in_use(self):
285
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
286
        vm = mf.VirtualMachineFactory(userid="user1")
287
        request = {"floatingip": {
288
            "device_id": vm.id}
289
            }
290
        with mocked_quotaholder():
291
            response = self.put(URL + "/%s" % ip.id, "user1",
292
                                json.dumps(request), "json")
293
        self.assertEqual(response.status_code, 409)
294

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

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

315
    def test_release_in_use(self):
316
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
317
        vm = ip.nic.machine
318
        with mocked_quotaholder():
319
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
320
        self.assertFault(response, 409, "conflict")
321
    '''
322

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

    
329
    def test_release(self):
330
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
331
        with mocked_quotaholder():
332
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
333
        self.assertSuccess(response)
334
        ips_after = floating_ips.filter(id=ip.id)
335
        self.assertEqual(len(ips_after), 0)
336

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

    
358
'''
359
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
360

361

362
class FloatingIPPoolsAPITest(BaseAPITest):
363
    def test_no_pool(self):
364
        response = self.get(POOLS_URL)
365
        self.assertSuccess(response)
366
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])
367

368
    def test_list_pools(self):
369
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
370
                                          public=True,
371
                                          subnet__cidr="192.168.2.0/30",
372
                                          subnet__gateway="192.168.2.1",
373
                                          subnet__pool__size=1,
374
                                          subnet__pool__offset=1)
375
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
376
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
377
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
378
        response = self.get(POOLS_URL)
379
        self.assertSuccess(response)
380
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
381
                         [{"name": str(net.id), "size": 1, "free": 1}])
382

383

384
class FloatingIPActionsTest(BaseAPITest):
385
    def setUp(self):
386
        self.vm = VirtualMachineFactory()
387
        self.vm.operstate = "ACTIVE"
388
        self.vm.save()
389

390
    def test_bad_request(self):
391
        url = SERVERS_URL + "/%s/action" % self.vm.id
392
        response = self.post(url, self.vm.userid, json.dumps({}), "json")
393
        self.assertBadRequest(response)
394
        response = self.post(url, self.vm.userid,
395
                             json.dumps({"addFloatingIp": {}}),
396
                             "json")
397
        self.assertBadRequest(response)
398

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

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