Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / test / floating_ips.py @ 9e8be4fb

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

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

    
44

    
45
URL = "/api/v1.1/os-floating-ips"
46

    
47

    
48
class FloatingIPAPITest(BaseAPITest):
49
    def test_no_floating_ip(self):
50
        response = self.get(URL)
51
        self.assertSuccess(response)
52
        self.assertEqual(json.loads(response.content)["floating_ips"], [])
53

    
54
    def test_list_ips(self):
55
        ip = FloatingIPFactory(userid="user1")
56
        FloatingIPFactory(userid="user1", deleted=True)
57
        with mocked_quotaholder():
58
            response = self.get(URL, "user1")
59
        self.assertSuccess(response)
60
        api_ip = json.loads(response.content)["floating_ips"][0]
61
        self.assertEqual(api_ip,
62
                         {"instance_id": str(ip.machine.id), "ip": ip.ipv4,
63
                          "fixed_ip": None, "id": str(ip.id),  "pool":
64
                          str(ip.network.id)})
65

    
66
    def test_get_ip(self):
67
        ip = FloatingIPFactory(userid="user1")
68
        with mocked_quotaholder():
69
            response = self.get(URL + "/%s" % ip.id, "user1")
70
        self.assertSuccess(response)
71
        api_ip = json.loads(response.content)["floating_ip"]
72
        self.assertEqual(api_ip,
73
                         {"instance_id": str(ip.machine.id), "ip": ip.ipv4,
74
                          "fixed_ip": None, "id": str(ip.id),  "pool":
75
                          str(ip.network.id)})
76

    
77
    def test_wrong_user(self):
78
        ip = FloatingIPFactory(userid="user1")
79
        with mocked_quotaholder():
80
            response = self.delete(URL + "/%s" % ip.id, "user2")
81
        self.assertItemNotFound(response)
82

    
83
    def test_deleted_ip(self):
84
        ip = FloatingIPFactory(userid="user1", deleted=True)
85
        with mocked_quotaholder():
86
            response = self.delete(URL + "/%s" % ip.id, "user1")
87
        self.assertItemNotFound(response)
88

    
89
    def test_reserve(self):
90
        net = NetworkFactory(userid="test_user", subnet="192.168.2.0/24",
91
                             gateway=None, public=True)
92
        request = {'pool': net.id}
93
        with mocked_quotaholder():
94
            response = self.post(URL, "test_user", json.dumps(request), "json")
95
        self.assertSuccess(response)
96
        ip = FloatingIP.objects.get()
97
        self.assertEqual(ip.ipv4, "192.168.2.1")
98
        self.assertEqual(ip.machine, None)
99
        self.assertEqual(ip.network, net)
100
        self.assertEqual(json.loads(response.content)["floating_ip"],
101
                         {"instance_id": None, "ip": "192.168.2.1",
102
                          "fixed_ip": None, "id": "1", "pool": "1"})
103

    
104
    def test_reserve_no_pool(self):
105
        # No networks
106
        with mocked_quotaholder():
107
            response = self.post(URL, "test_user", json.dumps({}), "json")
108
        self.assertFault(response, 413, "overLimit")
109
        # Full network
110
        net = NetworkFactory(userid="test_user", subnet="192.168.2.0/32",
111
                             gateway=None, public=True)
112
        with mocked_quotaholder():
113
            response = self.post(URL, "test_user", json.dumps({}), "json")
114
        self.assertFault(response, 413, "overLimit")
115
        # Success
116
        net2 = NetworkFactory(userid="test_user", subnet="192.168.2.0/24",
117
                              gateway=None, public=True)
118
        with mocked_quotaholder():
119
            response = self.post(URL, "test_user", json.dumps({}), "json")
120
        self.assertEqual(json.loads(response.content)["floating_ip"],
121
                         {"instance_id": None, "ip": "192.168.2.1",
122
                          "fixed_ip": None, "id": "1", "pool": str(net2.id)})
123

    
124
    def test_reserve_full(self):
125
        net = NetworkFactory(userid="test_user", subnet="192.168.2.0/32",
126
                             gateway=None, public=True)
127
        request = {'pool': net.id}
128
        with mocked_quotaholder():
129
            response = self.post(URL, "test_user", json.dumps(request), "json")
130
        self.assertEqual(response.status_code, 413)
131

    
132
    def test_reserve_with_address(self):
133
        net = NetworkFactory(userid="test_user", subnet="192.168.2.0/24",
134
                             gateway=None, public=True)
135
        request = {'pool': net.id, "address": "192.168.2.10"}
136
        with mocked_quotaholder():
137
            response = self.post(URL, "test_user", json.dumps(request), "json")
138
        self.assertSuccess(response)
139
        self.assertEqual(json.loads(response.content)["floating_ip"],
140
                         {"instance_id": None, "ip": "192.168.2.10",
141
                          "fixed_ip": None, "id": "1", "pool": "1"})
142

    
143
        # Already reserved
144
        FloatingIPFactory(network=net, ipv4="192.168.2.3")
145
        request = {'pool': net.id, "address": "192.168.2.3"}
146
        with mocked_quotaholder():
147
            response = self.post(URL, "test_user", json.dumps(request), "json")
148
        self.assertFault(response, 409, "conflict")
149

    
150
        # Already used
151
        pool = net.get_pool()
152
        pool.reserve("192.168.2.5")
153
        pool.save()
154
        # ..by another_user
155
        nic = NetworkInterfaceFactory(network=net, ipv4="192.168.2.5",
156
                                      machine__userid="test2")
157
        request = {'pool': net.id, "address": "192.168.2.5"}
158
        with mocked_quotaholder():
159
            response = self.post(URL, "test_user", json.dumps(request), "json")
160
        self.assertFault(response, 409, "conflict")
161
        # ..and by him
162
        nic.delete()
163
        NetworkInterfaceFactory(network=net, ipv4="192.168.2.5",
164
                                machine__userid="test_user")
165
        request = {'pool': net.id, "address": "192.168.2.5"}
166
        with mocked_quotaholder():
167
            response = self.post(URL, "test_user", json.dumps(request), "json")
168
        self.assertSuccess(response)
169

    
170
        # Address out of pool
171
        request = {'pool': net.id, "address": "192.168.3.5"}
172
        with mocked_quotaholder():
173
            response = self.post(URL, "test_user", json.dumps(request), "json")
174
        self.assertBadRequest(response)
175

    
176
    def test_release_in_use(self):
177
        ip = FloatingIPFactory()
178
        vm = ip.machine
179
        vm.operstate = "ACTIVE"
180
        vm.userid = ip.userid
181
        vm.save()
182
        vm.nics.create(index=0, ipv4=ip.ipv4, network=ip.network,
183
                       state="ACTIVE")
184
        with mocked_quotaholder():
185
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
186
        self.assertFault(response, 409, "conflict")
187
        # Also send a notification to remove the NIC and assert that FIP is in
188
        # use until notification from ganeti arrives
189
        request = {"removeFloatingIp": {"address": ip.ipv4}}
190
        url = "/api/v1.1/servers/%s/action" % vm.id
191
        with patch('synnefo.logic.rapi_pool.GanetiRapiClient') as c:
192
            c().ModifyInstance.return_value = 10
193
            response = self.post(url, vm.userid, json.dumps(request),
194
                                 "json")
195
        self.assertEqual(response.status_code, 202)
196
        with mocked_quotaholder():
197
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
198
        self.assertFault(response, 409, "conflict")
199

    
200
    def test_release(self):
201
        ip = FloatingIPFactory(machine=None)
202
        with mocked_quotaholder():
203
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
204
        self.assertSuccess(response)
205
        ips_after = FloatingIP.objects.filter(id=ip.id)
206
        self.assertEqual(len(ips_after), 0)
207

    
208
    @patch("synnefo.logic.backend", Mock())
209
    def test_delete_network_with_floating_ips(self):
210
        ip = FloatingIPFactory(machine=None, network__flavor="IP_LESS_ROUTED")
211
        net = ip.network
212
        # Can not remove network with floating IPs
213
        with mocked_quotaholder():
214
            response = self.delete("/api/v1.1/networks/%s" % net.id,
215
                                   net.userid)
216
        self.assertFault(response, 421, "networkInUse")
217
        # But we can with only deleted Floating Ips
218
        ip.deleted = True
219
        ip.save()
220
        with mocked_quotaholder():
221
            response = self.delete("/api/v1.1/networks/%s" % net.id,
222
                                   net.userid)
223
        self.assertSuccess(response)
224

    
225

    
226
POOLS_URL = "/api/v1.1/os-floating-ip-pools"
227

    
228

    
229
class FloatingIPPoolsAPITest(BaseAPITest):
230
    def test_no_pool(self):
231
        response = self.get(POOLS_URL)
232
        self.assertSuccess(response)
233
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])
234

    
235
    def test_list_pools(self):
236
        net = NetworkFactory(public=True, deleted=False)
237
        NetworkFactory(public=True, deleted=True)
238
        NetworkFactory(public=False, deleted=False)
239
        response = self.get(POOLS_URL)
240
        self.assertSuccess(response)
241
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
242
                        [{"name": str(net.id)}])
243

    
244

    
245
class FloatingIPActionsTest(BaseAPITest):
246
    def setUp(self):
247
        vm = VirtualMachineFactory()
248
        vm.operstate = "ACTIVE"
249
        vm.save()
250
        self.vm = vm
251

    
252
    def test_bad_request(self):
253
        url = "/api/v1.1/servers/%s/action" % self.vm.id
254
        response = self.post(url, self.vm.userid, json.dumps({}), "json")
255
        self.assertBadRequest(response)
256
        response = self.post(url, self.vm.userid,
257
                             json.dumps({"addFloatingIp": {}}),
258
                             "json")
259
        self.assertBadRequest(response)
260

    
261
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
262
    def test_add_floating_ip(self, mock):
263
        # Not exists
264
        url = "/api/v1.1/servers/%s/action" % self.vm.id
265
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
266
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
267
        self.assertItemNotFound(response)
268
        # In use
269
        vm1 = VirtualMachineFactory()
270
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=vm1)
271
        BackendNetworkFactory(network=ip1.network, backend=vm1.backend,
272
                              operstate='ACTIVE')
273
        request = {"addFloatingIp": {"address": ip1.ipv4}}
274
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
275
        self.assertFault(response, 409, "conflict")
276
        # Success
277
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None)
278
        BackendNetworkFactory(network=ip1.network, backend=self.vm.backend,
279
                              operstate='ACTIVE')
280
        request = {"addFloatingIp": {"address": ip1.ipv4}}
281
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
282
        self.assertEqual(response.status_code, 202)
283
        ip1_after = FloatingIP.objects.get(id=ip1.id)
284
        self.assertEqual(ip1_after.machine, self.vm)
285
        self.assertTrue(ip1_after.in_use())
286

    
287
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
288
    def test_remove_floating_ip(self, mock):
289
        # Not exists
290
        url = "/api/v1.1/servers/%s/action" % self.vm.id
291
        request = {"removeFloatingIp": {"address": "10.0.0.1"}}
292
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
293
        self.assertItemNotFound(response)
294
        # Not In Use
295
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None)
296
        request = {"removeFloatingIp": {"address": ip1.ipv4}}
297
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
298
        self.assertItemNotFound(response)
299
        # Success
300
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=self.vm)
301
        NetworkInterfaceFactory(machine=self.vm, ipv4=ip1.ipv4)
302
        request = {"removeFloatingIp": {"address": ip1.ipv4}}
303
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
304
        self.assertEqual(response.status_code, 202)
305
        # Yet used. Wait for the callbacks
306
        ip1_after = FloatingIP.objects.get(id=ip1.id)
307
        self.assertEqual(ip1_after.machine, self.vm)
308
        self.assertTrue(ip1_after.in_use())