Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (12.1 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
from mock import patch, Mock
42

    
43

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

    
46

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

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

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

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

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

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

    
103
    def test_reserve_full(self):
104
        net = NetworkFactory(userid="test_user", subnet="192.168.2.0/32",
105
                             gateway=None, public=True)
106
        request = {'pool': net.id}
107
        with mocked_quotaholder():
108
            response = self.post(URL, "test_user", json.dumps(request), "json")
109
        self.assertEqual(response.status_code, 413)
110

    
111
    def test_reserve_with_address(self):
112
        net = NetworkFactory(userid="test_user", subnet="192.168.2.0/24",
113
                             gateway=None, public=True)
114
        request = {'pool': net.id, "address": "192.168.2.10"}
115
        with mocked_quotaholder():
116
            response = self.post(URL, "test_user", json.dumps(request), "json")
117
        self.assertSuccess(response)
118
        self.assertEqual(json.loads(response.content)["floating_ip"],
119
                         {"instance_id": None, "ip": "192.168.2.10",
120
                          "fixed_ip": None, "id": "1", "pool": "1"})
121

    
122
        # Already reserved
123
        FloatingIPFactory(network=net, ipv4="192.168.2.3")
124
        request = {'pool': net.id, "address": "192.168.2.3"}
125
        with mocked_quotaholder():
126
            response = self.post(URL, "test_user", json.dumps(request), "json")
127
        self.assertFault(response, 409, "conflict")
128

    
129
        # Already used
130
        pool = net.get_pool()
131
        pool.reserve("192.168.2.5")
132
        pool.save()
133
        # ..by another_user
134
        nic = NetworkInterfaceFactory(network=net, ipv4="192.168.2.5",
135
                                      machine__userid="test2")
136
        request = {'pool': net.id, "address": "192.168.2.5"}
137
        with mocked_quotaholder():
138
            response = self.post(URL, "test_user", json.dumps(request), "json")
139
        self.assertFault(response, 409, "conflict")
140
        # ..and by him
141
        nic.delete()
142
        NetworkInterfaceFactory(network=net, ipv4="192.168.2.5",
143
                                machine__userid="test_user")
144
        request = {'pool': net.id, "address": "192.168.2.5"}
145
        with mocked_quotaholder():
146
            response = self.post(URL, "test_user", json.dumps(request), "json")
147
        self.assertSuccess(response)
148

    
149
        # Address out of pool
150
        request = {'pool': net.id, "address": "192.168.3.5"}
151
        with mocked_quotaholder():
152
            response = self.post(URL, "test_user", json.dumps(request), "json")
153
        self.assertBadRequest(response)
154

    
155
    def test_release_in_use(self):
156
        ip = FloatingIPFactory()
157
        vm = ip.machine
158
        vm.operstate = "ACTIVE"
159
        vm.userid = ip.userid
160
        vm.save()
161
        vm.nics.create(index=0, ipv4=ip.ipv4, network=ip.network,
162
                       state="ACTIVE")
163
        with mocked_quotaholder():
164
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
165
        self.assertFault(response, 409, "conflict")
166
        # Also send a notification to remove the NIC and assert that FIP is in
167
        # use until notification from ganeti arrives
168
        request = {"removeFloatingIp": {"address": ip.ipv4}}
169
        url = "/api/v1.1/servers/%s/action" % vm.id
170
        with patch('synnefo.logic.rapi_pool.GanetiRapiClient') as c:
171
            c().ModifyInstance.return_value = 10
172
            response = self.post(url, vm.userid, json.dumps(request),
173
                                 "json")
174
        self.assertEqual(response.status_code, 202)
175
        with mocked_quotaholder():
176
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
177
        self.assertFault(response, 409, "conflict")
178

    
179
    def test_release(self):
180
        ip = FloatingIPFactory(machine=None)
181
        with mocked_quotaholder():
182
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
183
        self.assertSuccess(response)
184
        ips_after = FloatingIP.objects.filter(id=ip.id)
185
        self.assertEqual(len(ips_after), 0)
186

    
187
    @patch("synnefo.logic.backend", Mock())
188
    def test_delete_network_with_floating_ips(self):
189
        ip = FloatingIPFactory(machine=None)
190
        net = ip.network
191
        # Can not remove network with floating IPs
192
        with mocked_quotaholder():
193
            response = self.delete("/api/v1.1/networks/%s" % net.id,
194
                                   net.userid)
195
        self.assertFault(response, 421, "networkInUse")
196
        # But we can with only deleted Floating Ips
197
        ip.deleted = True
198
        ip.save()
199
        with mocked_quotaholder():
200
            response = self.delete("/api/v1.1/networks/%s" % net.id,
201
                                   net.userid)
202
        self.assertSuccess(response)
203

    
204

    
205
POOLS_URL = "/api/v1.1/os-floating-ip-pools"
206

    
207

    
208
class FloatingIPPoolsAPITest(BaseAPITest):
209
    def test_no_pool(self):
210
        response = self.get(POOLS_URL)
211
        self.assertSuccess(response)
212
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])
213

    
214
    def test_list_pools(self):
215
        net = NetworkFactory(public=True, deleted=False)
216
        NetworkFactory(public=True, deleted=True)
217
        NetworkFactory(public=False, deleted=False)
218
        response = self.get(POOLS_URL)
219
        self.assertSuccess(response)
220
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
221
                        [{"name": str(net.id)}])
222

    
223

    
224
class FloatingIPActionsTest(BaseAPITest):
225
    def setUp(self):
226
        vm = VirtualMachineFactory()
227
        vm.operstate = "ACTIVE"
228
        vm.save()
229
        self.vm = vm
230

    
231
    def test_bad_request(self):
232
        url = "/api/v1.1/servers/%s/action" % self.vm.id
233
        response = self.post(url, self.vm.userid, json.dumps({}), "json")
234
        self.assertBadRequest(response)
235
        response = self.post(url, self.vm.userid,
236
                             json.dumps({"addFloatingIp": {}}),
237
                             "json")
238
        self.assertBadRequest(response)
239

    
240
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
241
    def test_add_floating_ip(self, mock):
242
        # Not exists
243
        url = "/api/v1.1/servers/%s/action" % self.vm.id
244
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
245
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
246
        self.assertItemNotFound(response)
247
        # In use
248
        vm1 = VirtualMachineFactory()
249
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=vm1)
250
        request = {"addFloatingIp": {"address": ip1.ipv4}}
251
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
252
        self.assertFault(response, 409, "conflict")
253
        # Success
254
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None)
255
        request = {"addFloatingIp": {"address": ip1.ipv4}}
256
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
257
        self.assertEqual(response.status_code, 202)
258
        ip1_after = FloatingIP.objects.get(id=ip1.id)
259
        self.assertEqual(ip1_after.machine, self.vm)
260
        self.assertTrue(ip1_after.in_use())
261

    
262
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
263
    def test_remove_floating_ip(self, mock):
264
        # Not exists
265
        url = "/api/v1.1/servers/%s/action" % self.vm.id
266
        request = {"removeFloatingIp": {"address": "10.0.0.1"}}
267
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
268
        self.assertItemNotFound(response)
269
        # Not In Use
270
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None)
271
        request = {"removeFloatingIp": {"address": ip1.ipv4}}
272
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
273
        self.assertItemNotFound(response)
274
        # Success
275
        ip1 = FloatingIPFactory(userid=self.vm.userid, machine=self.vm)
276
        NetworkInterfaceFactory(machine=self.vm, ipv4=ip1.ipv4)
277
        request = {"removeFloatingIp": {"address": ip1.ipv4}}
278
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
279
        self.assertEqual(response.status_code, 202)
280
        # Yet used. Wait for the callbacks
281
        ip1_after = FloatingIP.objects.get(id=ip1.id)
282
        self.assertEqual(ip1_after.machine, self.vm)
283
        self.assertTrue(ip1_after.in_use())