floating_ips.py 18.3 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, Mock
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
    def test_no_floating_ip(self):
        response = self.get(URL)
        self.assertSuccess(response)
70
        self.assertEqual(json.loads(response.content)["floatingips"], [])
71
72

    def test_list_ips(self):
73
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
74
75
76
        with mocked_quotaholder():
            response = self.get(URL, "user1")
        self.assertSuccess(response)
77
        api_ip = json.loads(response.content)["floatingips"][0]
78
        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
        with mocked_quotaholder():
            response = self.get(URL + "/%s" % ip.id, "user1")
        self.assertSuccess(response)
91
        api_ip = json.loads(response.content)["floatingip"]
92
        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")["floatingip"]
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)["floatingip"],
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
    @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()
207
        api_ip = json.loads(response.content, "utf-8")["floatingip"]
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
258
        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)

259
    def test_release_in_use(self):
260
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
261
262
263
264
        vm = mf.VirtualMachineFactory(userid="user1")
        request = {"floatingip": {
            "device_id": vm.id}
            }
265
        with mocked_quotaholder():
266
267
268
269
270
271
272
273
274
275
276
277
278
279
            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")
280
        self.assertEqual(response.status_code, 202)
281
282
283
284
285
286
287
288
289
290
291
292

    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
293
294
295
        with mocked_quotaholder():
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
        self.assertFault(response, 409, "conflict")
296
297
298
299
300
301
302
    '''

    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)
303
304

    def test_release(self):
305
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
306
307
308
        with mocked_quotaholder():
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
        self.assertSuccess(response)
309
        ips_after = floating_ips.filter(id=ip.id)
310
        self.assertEqual(len(ips_after), 0)
311

312
313
    @patch("synnefo.logic.backend", Mock())
    def test_delete_network_with_floating_ips(self):
314
315
316
317
318
319
        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()
320
321
        # Can not remove network with floating IPs
        with mocked_quotaholder():
322
323
324
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
                                   self.pool.userid)
        self.assertConflict(response)
325
326
327
328
        # But we can with only deleted Floating Ips
        ip.deleted = True
        ip.save()
        with mocked_quotaholder():
329
330
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
                                   self.pool.userid)
331
332
        self.assertSuccess(response)

333
'''
Christos Stavrakakis's avatar
Christos Stavrakakis committed
334
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
335
336


337
class FloatingIPPoolsAPITest(BaseAPITest):
338
339
340
341
342
343
    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):
344
345
346
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
                                          public=True,
                                          subnet__cidr="192.168.2.0/30",
347
348
349
                                          subnet__gateway="192.168.2.1",
                                          subnet__pool__size=1,
                                          subnet__pool__offset=1)
350
351
352
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
353
354
355
        response = self.get(POOLS_URL)
        self.assertSuccess(response)
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
356
                         [{"name": str(net.id), "size": 1, "free": 1}])
357
358


359
class FloatingIPActionsTest(BaseAPITest):
360
    def setUp(self):
361
362
363
        self.vm = VirtualMachineFactory()
        self.vm.operstate = "ACTIVE"
        self.vm.save()
364
365

    def test_bad_request(self):
Christos Stavrakakis's avatar
Christos Stavrakakis committed
366
        url = SERVERS_URL + "/%s/action" % self.vm.id
367
368
369
370
371
372
373
374
375
376
        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
377
        url = SERVERS_URL + "/%s/action" % self.vm.id
378
379
380
381
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
        self.assertItemNotFound(response)
        # In use
382
383
        ip = mf.IPv4AddressFactory(floating_ip=True, userid=self.vm.userid)
        request = {"addFloatingIp": {"address": ip.address}}
384
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
385
        self.assertConflict(response)
386
        # Success
387
388
389
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
                                   userid=self.vm.userid)
        request = {"addFloatingIp": {"address": ip.address}}
390
        mock().ModifyInstance.return_value = 1
391
392
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
        self.assertEqual(response.status_code, 202)
393
394
395
        ip_after = floating_ips.get(id=ip.id)
        self.assertEqual(ip_after.nic.machine, self.vm)
        nic = self.vm.nics.get()
396
397
        nic.state = "ACTIVE"
        nic.save()
398
399
400
401
402
        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")
403
404
405
406

    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
    def test_remove_floating_ip(self, mock):
        # Not exists
Christos Stavrakakis's avatar
Christos Stavrakakis committed
407
        url = SERVERS_URL + "/%s/action" % self.vm.id
408
409
        request = {"removeFloatingIp": {"address": "10.0.0.1"}}
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
410
        self.assertBadRequest(response)
411
        # Not In Use
412
413
414
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
                                   userid=self.vm.userid)
        request = {"removeFloatingIp": {"address": ip.address}}
415
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
416
        self.assertBadRequest(response)
417
        # Success
418
419
420
        ip = mf.IPv4AddressFactory(floating_ip=True,
                                   userid=self.vm.userid, nic__machine=self.vm)
        request = {"removeFloatingIp": {"address": ip.address}}
421
        mock().ModifyInstance.return_value = 2
422
423
424
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
        self.assertEqual(response.status_code, 202)
        # Yet used. Wait for the callbacks
425
426
        ip_after = floating_ips.get(id=ip.id)
        self.assertEqual(ip_after.nic.machine, self.vm)
427
'''