Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (17.4 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
37
from synnefo.db import models_factory as mf
38
from synnefo.db.models_factory import (NetworkFactory,
39
                                       VirtualMachineFactory)
40

    
41
from mock import patch
42
from functools import partial
43

    
44
from synnefo.cyclades_settings import cyclades_services
45
from synnefo.lib.services import get_service_path
46
from synnefo.lib import join_urls
47

    
48

    
49
compute_path = get_service_path(cyclades_services, "compute", version="v2.0")
50
URL = join_urls(compute_path, "floatingips")
51
NETWORKS_URL = join_urls(compute_path, "networks")
52
SERVERS_URL = join_urls(compute_path, "servers")
53

    
54

    
55
floating_ips = IPAddress.objects.filter(floating_ip=True)
56
FloatingIPPoolFactory = partial(NetworkFactory, public=True, deleted=False,
57
                                floating_ip_pool=True)
58

    
59

    
60
class FloatingIPAPITest(BaseAPITest):
61
    def setUp(self):
62
        self.pool = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
63
                                                public=True,
64
                                                subnet__cidr="192.168.2.0/24",
65
                                                subnet__gateway="192.168.2.1")
66

    
67
    def test_no_floating_ip(self):
68
        response = self.get(URL)
69
        self.assertSuccess(response)
70
        self.assertEqual(json.loads(response.content)["floating_ips"], [])
71

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

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

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

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

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

    
131
    def test_reserve_no_pool(self):
132
        # Network is not a floating IP pool
133
        pool2 = mf.NetworkWithSubnetFactory(floating_ip_pool=False,
134
                                            public=True,
135
                                            subnet__cidr="192.168.2.0/24",
136
                                            subnet__gateway="192.168.2.1")
137
        request = {"floatingip": {
138
            'floating_network_id': pool2.id}
139
            }
140
        response = self.post(URL, "test_user", json.dumps(request), "json")
141
        self.assertEqual(response.status_code, 404)
142

    
143
        # Full network
144
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
145
                                          public=True,
146
                                          subnet__cidr="192.168.2.0/31",
147
                                          subnet__gateway="192.168.2.1",
148
                                          subnet__pool__size=0)
149
        request = {"floatingip": {
150
            'floating_network_id': net.id}
151
            }
152
        response = self.post(URL, "test_user", json.dumps(request), "json")
153
        self.assertConflict(response)
154

    
155
    def test_reserve_with_address(self):
156
        request = {"floatingip": {
157
            "floating_network_id": self.pool.id,
158
            "floating_ip_address": "192.168.2.10"}
159
            }
160
        with mocked_quotaholder():
161
            response = self.post(URL, "test_user", json.dumps(request), "json")
162
        self.assertSuccess(response)
163
        ip = floating_ips.get()
164
        self.assertEqual(json.loads(response.content)["floating_ip"],
165
                         {"instance_id": None,
166
                          "floating_ip_address": "192.168.2.10",
167
                          "fixed_ip_address": None,
168
                          "id": str(ip.id),
169
                          "port_id": None,
170
                          "floating_network_id": str(self.pool.id)})
171

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

    
177
        # Used by instance
178
        self.pool.reserve_address("192.168.2.20")
179
        request = {"floatingip": {
180
            "floating_network_id": self.pool.id,
181
            "floating_ip_address": "192.168.2.20"}
182
            }
183
        with mocked_quotaholder():
184
            response = self.post(URL, "test_user", json.dumps(request), "json")
185
        self.assertFault(response, 409, "conflict")
186

    
187
        # Address out of pool
188
        request = {"floatingip": {
189
            "floating_network_id": self.pool.id,
190
            "floating_ip_address": "192.168.3.5"}
191
            }
192
        with mocked_quotaholder():
193
            response = self.post(URL, "test_user", json.dumps(request), "json")
194
        self.assertBadRequest(response)
195

    
196
    @patch("synnefo.db.models.get_rapi_client")
197
    def test_reserve_and_connect(self, mrapi):
198
        vm = mf.VirtualMachineFactory(userid="test_user")
199
        request = {"floatingip": {
200
            "floating_network_id": self.pool.id,
201
            "floating_ip_address": "192.168.2.12",
202
            "device_id": vm.id}
203
            }
204
        response = self.post(URL, "test_user", json.dumps(request), "json")
205
        ip = floating_ips.get()
206
        api_ip = json.loads(response.content, "utf-8")["floating_ip"]
207
        self.assertEqual(api_ip,
208
                         {"instance_id": str(vm.id),
209
                          "floating_ip_address": "192.168.2.12",
210
                          "fixed_ip_address": None,
211
                          "id": str(ip.id),
212
                          "port_id": str(vm.nics.all()[0].id),
213
                          "floating_network_id": str(self.pool.id)})
214

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

    
227
    def test_update_attach_conflict(self):
228
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
229
        vm = mf.VirtualMachineFactory(userid="user1")
230
        request = {"floatingip": {
231
            "device_id": vm.id}
232
            }
233
        with mocked_quotaholder():
234
            response = self.put(URL + "/%s" % ip.id, "user1",
235
                                json.dumps(request), "json")
236
        self.assertEqual(response.status_code, 409)
237

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

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

    
258
    def test_release_in_use(self):
259
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
260
        vm = ip.nic.machine
261
        with mocked_quotaholder():
262
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
263
        self.assertFault(response, 409, "conflict")
264
        # Also send a notification to remove the NIC and assert that FIP is in
265
        # use until notification from ganeti arrives
266
        request = {"removeFloatingIp": {"address": ip.address}}
267
        url = SERVERS_URL + "/%s/action" % vm.id
268
        with patch('synnefo.logic.rapi_pool.GanetiRapiClient') as c:
269
            c().ModifyInstance.return_value = 10
270
            response = self.post(url, vm.userid, json.dumps(request),
271
                                 "json")
272
        self.assertEqual(response.status_code, 202)
273
        with mocked_quotaholder():
274
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
275
        self.assertFault(response, 409, "conflict")
276

    
277
    def test_release(self):
278
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
279
        with mocked_quotaholder():
280
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
281
        self.assertSuccess(response)
282
        ips_after = floating_ips.filter(id=ip.id)
283
        self.assertEqual(len(ips_after), 0)
284
'''
285
    @patch("synnefo.logic.backend", Mock())
286
    def test_delete_network_with_floating_ips(self):
287
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True,
288
                                   network=self.pool, nic=None)
289
        # Mark the network as non-pubic to not get 403
290
        network = ip.network
291
        network.public = False
292
        network.save()
293
        # Can not remove network with floating IPs
294
        with mocked_quotaholder():
295
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
296
                                   self.pool.userid)
297
        self.assertConflict(response)
298
        # But we can with only deleted Floating Ips
299
        ip.deleted = True
300
        ip.save()
301
        with mocked_quotaholder():
302
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
303
                                   self.pool.userid)
304
        self.assertSuccess(response)
305

306

307
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
308

309

310
class FloatingIPPoolsAPITest(BaseAPITest):
311
    def test_no_pool(self):
312
        response = self.get(POOLS_URL)
313
        self.assertSuccess(response)
314
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])
315

316
    def test_list_pools(self):
317
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
318
                                          public=True,
319
                                          subnet__cidr="192.168.2.0/30",
320
                                          subnet__gateway="192.168.2.1",
321
                                          subnet__pool__size=1,
322
                                          subnet__pool__offset=1)
323
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
324
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
325
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
326
        response = self.get(POOLS_URL)
327
        self.assertSuccess(response)
328
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
329
                         [{"name": str(net.id), "size": 1, "free": 1}])
330

331

332
class FloatingIPActionsTest(BaseAPITest):
333
    def setUp(self):
334
        self.vm = VirtualMachineFactory()
335
        self.vm.operstate = "ACTIVE"
336
        self.vm.save()
337

338
    def test_bad_request(self):
339
        url = SERVERS_URL + "/%s/action" % self.vm.id
340
        response = self.post(url, self.vm.userid, json.dumps({}), "json")
341
        self.assertBadRequest(response)
342
        response = self.post(url, self.vm.userid,
343
                             json.dumps({"addFloatingIp": {}}),
344
                             "json")
345
        self.assertBadRequest(response)
346

347
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
348
    def test_add_floating_ip(self, mock):
349
        # Not exists
350
        url = SERVERS_URL + "/%s/action" % self.vm.id
351
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
352
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
353
        self.assertItemNotFound(response)
354
        # In use
355
        ip = mf.IPv4AddressFactory(floating_ip=True, userid=self.vm.userid)
356
        request = {"addFloatingIp": {"address": ip.address}}
357
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
358
        self.assertConflict(response)
359
        # Success
360
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
361
                                   userid=self.vm.userid)
362
        request = {"addFloatingIp": {"address": ip.address}}
363
        mock().ModifyInstance.return_value = 1
364
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
365
        self.assertEqual(response.status_code, 202)
366
        ip_after = floating_ips.get(id=ip.id)
367
        self.assertEqual(ip_after.nic.machine, self.vm)
368
        nic = self.vm.nics.get()
369
        nic.state = "ACTIVE"
370
        nic.save()
371
        response = self.get(SERVERS_URL + "/%s" % self.vm.id,
372
                            self.vm.userid)
373
        self.assertSuccess(response)
374
        nic = json.loads(response.content)["server"]["attachments"][0]
375
        self.assertEqual(nic["OS-EXT-IPS:type"], "floating")
376

377
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
378
    def test_remove_floating_ip(self, mock):
379
        # Not exists
380
        url = SERVERS_URL + "/%s/action" % self.vm.id
381
        request = {"removeFloatingIp": {"address": "10.0.0.1"}}
382
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
383
        self.assertBadRequest(response)
384
        # Not In Use
385
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
386
                                   userid=self.vm.userid)
387
        request = {"removeFloatingIp": {"address": ip.address}}
388
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
389
        self.assertBadRequest(response)
390
        # Success
391
        ip = mf.IPv4AddressFactory(floating_ip=True,
392
                                   userid=self.vm.userid, nic__machine=self.vm)
393
        request = {"removeFloatingIp": {"address": ip.address}}
394
        mock().ModifyInstance.return_value = 2
395
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
396
        self.assertEqual(response.status_code, 202)
397
        # Yet used. Wait for the callbacks
398
        ip_after = floating_ips.get(id=ip.id)
399
        self.assertEqual(ip_after.nic.machine, self.vm)
400
'''