floating_ips.py 17.4 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
37
38
39
from synnefo.db import models_factory as mf
from synnefo.db.models_factory import (NetworkFactory,
                                       VirtualMachineFactory)
40
41

from mock import patch
42
from functools import partial
43

Christos Stavrakakis's avatar
Christos Stavrakakis committed
44
45
46
from synnefo.cyclades_settings import cyclades_services
from synnefo.lib.services import get_service_path
from synnefo.lib import join_urls
47

Christos Stavrakakis's avatar
Christos Stavrakakis committed
48
49

compute_path = get_service_path(cyclades_services, "compute", version="v2.0")
50
URL = join_urls(compute_path, "floatingips")
Christos Stavrakakis's avatar
Christos Stavrakakis committed
51
52
NETWORKS_URL = join_urls(compute_path, "networks")
SERVERS_URL = join_urls(compute_path, "servers")
53

54
55
56

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

59

60
class FloatingIPAPITest(BaseAPITest):
61
62
63
64
65
66
    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")

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

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

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

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

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

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

131
    def test_reserve_no_pool(self):
132
133
134
135
136
137
138
139
140
141
        # 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")
        self.assertEqual(response.status_code, 404)
142

143
        # Full network
144
145
146
147
148
        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)
149
150
151
        request = {"floatingip": {
            'floating_network_id': net.id}
            }
152
153
        response = self.post(URL, "test_user", json.dumps(request), "json")
        self.assertConflict(response)
154

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

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

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

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

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    @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()
        api_ip = json.loads(response.content, "utf-8")["floating_ip"]
        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)

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

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

306

Christos Stavrakakis's avatar
Christos Stavrakakis committed
307
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
308
309


310
class FloatingIPPoolsAPITest(BaseAPITest):
311
312
313
314
315
316
    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):
317
318
319
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
                                          public=True,
                                          subnet__cidr="192.168.2.0/30",
320
321
322
                                          subnet__gateway="192.168.2.1",
                                          subnet__pool__size=1,
                                          subnet__pool__offset=1)
323
324
325
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
326
327
328
        response = self.get(POOLS_URL)
        self.assertSuccess(response)
        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
335
336
        self.vm = VirtualMachineFactory()
        self.vm.operstate = "ACTIVE"
        self.vm.save()
337
338

    def test_bad_request(self):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
339
        url = SERVERS_URL + "/%s/action" % self.vm.id
340
341
342
343
344
345
346
347
348
349
        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
350
        url = SERVERS_URL + "/%s/action" % self.vm.id
351
352
353
354
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
        self.assertItemNotFound(response)
        # In use
355
356
        ip = mf.IPv4AddressFactory(floating_ip=True, userid=self.vm.userid)
        request = {"addFloatingIp": {"address": ip.address}}
357
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
358
        self.assertConflict(response)
359
        # Success
360
361
362
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
                                   userid=self.vm.userid)
        request = {"addFloatingIp": {"address": ip.address}}
363
        mock().ModifyInstance.return_value = 1
364
365
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
        self.assertEqual(response.status_code, 202)
366
367
368
        ip_after = floating_ips.get(id=ip.id)
        self.assertEqual(ip_after.nic.machine, self.vm)
        nic = self.vm.nics.get()
369
370
        nic.state = "ACTIVE"
        nic.save()
371
372
373
374
375
        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")
376
377
378
379

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