Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.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
import json
35

    
36
from snf_django.utils.testing import BaseAPITest, mocked_quotaholder
37
from synnefo.db.models import IPAddress
38
from synnefo.db.models_factory import (FloatingIPFactory, NetworkFactory,
39
                                       VirtualMachineFactory,
40
                                       NetworkInterfaceFactory,
41
                                       BackendNetworkFactory)
42
from mock import patch, Mock
43
from functools import partial
44

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

    
49

    
50
compute_path = get_service_path(cyclades_services, "compute", version="v2.0")
51
URL = join_urls(compute_path, "os-floating-ips")
52
NETWORKS_URL = join_urls(compute_path, "networks")
53
SERVERS_URL = join_urls(compute_path, "servers")
54

    
55

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

    
60

    
61
class FloatingIPAPITest(BaseAPITest):
62
    def test_no_floating_ip(self):
63
        response = self.get(URL)
64
        self.assertSuccess(response)
65
        self.assertEqual(json.loads(response.content)["floating_ips"], [])
66

    
67
    def test_list_ips(self):
68
        ip = FloatingIPFactory(userid="user1")
69
        FloatingIPFactory(userid="user1", deleted=True)
70
        with mocked_quotaholder():
71
            response = self.get(URL, "user1")
72
        self.assertSuccess(response)
73
        api_ip = json.loads(response.content)["floating_ips"][0]
74
        self.assertEqual(api_ip,
75
                         {"instance_id": str(ip.machine.id), "ip": ip.ipv4,
76
                          "fixed_ip": None, "id": str(ip.id),  "pool":
77
                          str(ip.network.id)})
78

    
79
    def test_get_ip(self):
80
        ip = FloatingIPFactory(userid="user1")
81
        with mocked_quotaholder():
82
            response = self.get(URL + "/%s" % ip.id, "user1")
83
        self.assertSuccess(response)
84
        api_ip = json.loads(response.content)["floating_ip"]
85
        self.assertEqual(api_ip,
86
                         {"instance_id": str(ip.machine.id), "ip": ip.ipv4,
87
                          "fixed_ip": None, "id": str(ip.id),  "pool":
88
                          str(ip.network.id)})
89

    
90
    def test_wrong_user(self):
91
        ip = FloatingIPFactory(userid="user1")
92
        with mocked_quotaholder():
93
            response = self.delete(URL + "/%s" % ip.id, "user2")
94
        self.assertItemNotFound(response)
95

    
96
    def test_deleted_ip(self):
97
        ip = FloatingIPFactory(userid="user1", deleted=True)
98
        with mocked_quotaholder():
99
            response = self.delete(URL + "/%s" % ip.id, "user1")
100
        self.assertItemNotFound(response)
101

    
102
    def test_reserve(self):
103
        net = FloatingIPPoolFactory(userid="test_user",
104
                                    subnet="192.168.2.0/24",
105
                                    gateway=None)
106
        request = {'pool': net.id}
107
        with mocked_quotaholder():
108
            response = self.post(URL, "test_user", json.dumps(request), "json")
109
        self.assertSuccess(response)
110
        ip = floating_ips.get()
111
        self.assertEqual(ip.ipv4, "192.168.2.1")
112
        self.assertEqual(ip.machine, None)
113
        self.assertEqual(ip.network, net)
114
        self.assertEqual(json.loads(response.content)["floating_ip"],
115
                         {"instance_id": None, "ip": "192.168.2.1",
116
                          "fixed_ip": None, "id": str(ip.id),
117
                          "pool": str(net.id)})
118

    
119
    def test_reserve_no_pool(self):
120
        # No networks
121
        with mocked_quotaholder():
122
            response = self.post(URL, "test_user", json.dumps({}), "json")
123
        self.assertFault(response, 413, "overLimit")
124
        # Full network
125
        FloatingIPPoolFactory(userid="test_user",
126
                              subnet="192.168.2.0/32",
127
                              gateway=None)
128
        with mocked_quotaholder():
129
            response = self.post(URL, "test_user", json.dumps({}), "json")
130
        self.assertFault(response, 413, "overLimit")
131
        # Success
132
        net2 = FloatingIPPoolFactory(userid="test_user",
133
                                     subnet="192.168.2.0/24",
134
                                     gateway=None)
135
        with mocked_quotaholder():
136
            response = self.post(URL, "test_user", json.dumps({}), "json")
137
        self.assertSuccess(response)
138
        ip = floating_ips.get()
139
        self.assertEqual(json.loads(response.content)["floating_ip"],
140
                         {"instance_id": None, "ip": "192.168.2.1",
141
                          "fixed_ip": None, "id": str(ip.id),
142
                          "pool": str(net2.id)})
143

    
144
    def test_reserve_full(self):
145
        net = FloatingIPPoolFactory(userid="test_user",
146
                                    subnet="192.168.2.0/32")
147
        request = {'pool': net.id}
148
        with mocked_quotaholder():
149
            response = self.post(URL, "test_user", json.dumps(request), "json")
150
        self.assertEqual(response.status_code, 413)
151

    
152
    def test_reserve_with_address(self):
153
        net = FloatingIPPoolFactory(userid="test_user",
154
                                    subnet="192.168.2.0/24")
155
        request = {'pool': net.id, "address": "192.168.2.10"}
156
        with mocked_quotaholder():
157
            response = self.post(URL, "test_user", json.dumps(request), "json")
158
        self.assertSuccess(response)
159
        ip = floating_ips.get()
160
        self.assertEqual(json.loads(response.content)["floating_ip"],
161
                         {"instance_id": None, "ip": "192.168.2.10",
162
                          "fixed_ip": None, "id": str(ip.id), "pool":
163
                          str(net.id)})
164

    
165
        # Already reserved
166
        FloatingIPFactory(network=net, ipv4="192.168.2.3")
167
        request = {'pool': net.id, "address": "192.168.2.3"}
168
        with mocked_quotaholder():
169
            response = self.post(URL, "test_user", json.dumps(request), "json")
170
        self.assertFault(response, 409, "conflict")
171

    
172
        # Already used
173
        pool = net.get_pool()
174
        pool.reserve("192.168.2.5")
175
        pool.save()
176
        # ..by another_user
177
        nic = NetworkInterfaceFactory(network=net, ipv4="192.168.2.5",
178
                                      machine__userid="test2")
179
        request = {'pool': net.id, "address": "192.168.2.5"}
180
        with mocked_quotaholder():
181
            response = self.post(URL, "test_user", json.dumps(request), "json")
182
        self.assertFault(response, 409, "conflict")
183
        # ..and by him
184
        nic.delete()
185
        NetworkInterfaceFactory(network=net, ipv4="192.168.2.5",
186
                                machine__userid="test_user")
187
        request = {'pool': net.id, "address": "192.168.2.5"}
188
        with mocked_quotaholder():
189
            response = self.post(URL, "test_user", json.dumps(request), "json")
190
        self.assertSuccess(response)
191

    
192
        # Address out of pool
193
        request = {'pool': net.id, "address": "192.168.3.5"}
194
        with mocked_quotaholder():
195
            response = self.post(URL, "test_user", json.dumps(request), "json")
196
        self.assertBadRequest(response)
197

    
198
    def test_release_in_use(self):
199
        ip = FloatingIPFactory()
200
        vm = ip.machine
201
        vm.operstate = "ACTIVE"
202
        vm.userid = ip.userid
203
        vm.save()
204
        vm.nics.create(index=0, ipv4=ip.ipv4, network=ip.network,
205
                       state="ACTIVE")
206
        with mocked_quotaholder():
207
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
208
        self.assertFault(response, 409, "conflict")
209
        # Also send a notification to remove the NIC and assert that FIP is in
210
        # use until notification from ganeti arrives
211
        request = {"removeFloatingIp": {"address": ip.ipv4}}
212
        url = SERVERS_URL + "/%s/action" % vm.id
213
        with patch('synnefo.logic.rapi_pool.GanetiRapiClient') as c:
214
            c().ModifyInstance.return_value = 10
215
            response = self.post(url, vm.userid, json.dumps(request),
216
                                 "json")
217
        self.assertEqual(response.status_code, 202)
218
        with mocked_quotaholder():
219
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
220
        self.assertFault(response, 409, "conflict")
221

    
222
    def test_release(self):
223
        ip = FloatingIPFactory(machine=None)
224
        with mocked_quotaholder():
225
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
226
        self.assertSuccess(response)
227
        ips_after = floating_ips.filter(id=ip.id)
228
        self.assertEqual(len(ips_after), 0)
229

    
230
    @patch("synnefo.logic.backend", Mock())
231
    def test_delete_network_with_floating_ips(self):
232
        ip = FloatingIPFactory(machine=None, network__flavor="IP_LESS_ROUTED")
233
        net = ip.network
234
        # Can not remove network with floating IPs
235
        with mocked_quotaholder():
236
            response = self.delete(NETWORKS_URL + "/%s" % net.id,
237
                                   net.userid)
238
        self.assertFault(response, 421, "networkInUse")
239
        # But we can with only deleted Floating Ips
240
        ip.deleted = True
241
        ip.save()
242
        with mocked_quotaholder():
243
            response = self.delete(NETWORKS_URL + "/%s" % net.id,
244
                                   net.userid)
245
        self.assertSuccess(response)
246

    
247

    
248
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
249

    
250

    
251
class FloatingIPPoolsAPITest(BaseAPITest):
252
    def test_no_pool(self):
253
        response = self.get(POOLS_URL)
254
        self.assertSuccess(response)
255
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])
256

    
257
    def test_list_pools(self):
258
        net = FloatingIPPoolFactory(subnet="192.168.0.0/30",
259
                                    gateway="192.168.0.1")
260
        NetworkFactory(public=True, deleted=True)
261
        NetworkFactory(public=False, deleted=False)
262
        NetworkFactory(public=True, deleted=False)
263
        response = self.get(POOLS_URL)
264
        self.assertSuccess(response)
265
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
266
                         [{"name": str(net.id), "size": 4, "free": 1}])
267

    
268

    
269
class FloatingIPActionsTest(BaseAPITest):
270
    def setUp(self):
271
        vm = VirtualMachineFactory()
272
        vm.operstate = "ACTIVE"
273
        vm.save()
274
        self.vm = vm
275

    
276
    def test_bad_request(self):
277
        url = SERVERS_URL + "/%s/action" % self.vm.id
278
        response = self.post(url, self.vm.userid, json.dumps({}), "json")
279
        self.assertBadRequest(response)
280
        response = self.post(url, self.vm.userid,
281
                             json.dumps({"addFloatingIp": {}}),
282
                             "json")
283
        self.assertBadRequest(response)
284

    
285
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
286
    def test_add_floating_ip(self, mock):
287
        # Not exists
288
        url = SERVERS_URL + "/%s/action" % self.vm.id
289
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
290
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
291
        self.assertItemNotFound(response)
292
        # In use
293
        vm1 = VirtualMachineFactory()
294
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=vm1)
295
        BackendNetworkFactory(network=ip1.network, backend=vm1.backend,
296
                              operstate='ACTIVE')
297
        request = {"addFloatingIp": {"address": ip1.ipv4}}
298
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
299
        self.assertFault(response, 409, "conflict")
300
        # Success
301
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None)
302
        BackendNetworkFactory(network=ip1.network, backend=self.vm.backend,
303
                              operstate='ACTIVE')
304
        request = {"addFloatingIp": {"address": ip1.ipv4}}
305
        mock().ModifyInstance.return_value = 1
306
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
307
        self.assertEqual(response.status_code, 202)
308
        ip1_after = floating_ips.get(id=ip1.id)
309
        self.assertEqual(ip1_after.machine, self.vm)
310
        self.assertTrue(ip1_after.in_use())
311
        nic = self.vm.nics.get(ipv4=ip1_after.ipv4)
312
        nic.state = "ACTIVE"
313
        nic.save()
314
        response = self.get(SERVERS_URL + "/%s" % self.vm.id,
315
                            self.vm.userid)
316
        self.assertSuccess(response)
317
        nic = json.loads(response.content)["server"]["attachments"][0]
318
        self.assertEqual(nic["OS-EXT-IPS:type"], "floating")
319

    
320
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
321
    def test_remove_floating_ip(self, mock):
322
        # Not exists
323
        url = SERVERS_URL + "/%s/action" % self.vm.id
324
        request = {"removeFloatingIp": {"address": "10.0.0.1"}}
325
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
326
        self.assertItemNotFound(response)
327
        # Not In Use
328
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None)
329
        request = {"removeFloatingIp": {"address": ip1.ipv4}}
330
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
331
        self.assertItemNotFound(response)
332
        # Success
333
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=self.vm)
334
        NetworkInterfaceFactory(machine=self.vm, ipv4=ip1.ipv4)
335
        request = {"removeFloatingIp": {"address": ip1.ipv4}}
336
        mock().ModifyInstance.return_value = 2
337
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
338
        self.assertEqual(response.status_code, 202)
339
        # Yet used. Wait for the callbacks
340
        ip1_after = floating_ips.get(id=ip1.id)
341
        self.assertEqual(ip1_after.machine, self.vm)
342
        self.assertTrue(ip1_after.in_use())