Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / tests / floating_ips.py @ 0711e5aa

History | View | Annotate | Download (13.7 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 import models_factory as mf
39
from synnefo.db.models_factory import (NetworkFactory,
40
                                       VirtualMachineFactory)
41
from mock import patch, Mock
42
from functools import partial
43

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

    
48

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

    
54

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

    
59

    
60
class FloatingIPAPITest(BaseAPITest):
61
    def setUp(self):
62
        self.pool = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
63
                                                public=True,
64
                                                subnet__cidr="192.168.2.0/24",
65
                                                subnet__gateway="192.168.2.1")
66

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

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

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

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

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

    
109
    def test_reserve(self):
110
        request = {'pool': self.pool.id}
111
        with mocked_quotaholder():
112
            response = self.post(URL, "test_user", json.dumps(request), "json")
113
        self.assertSuccess(response)
114
        ip = floating_ips.get()
115
        self.assertEqual(ip.address, "192.168.2.2")
116
        self.assertEqual(ip.nic, None)
117
        self.assertEqual(ip.network, self.pool)
118
        self.assertEqual(json.loads(response.content)["floating_ip"],
119
                         {"instance_id": None, "ip": "192.168.2.2",
120
                          "fixed_ip": None, "id": str(ip.id),
121
                          "pool": str(self.pool.id)})
122

    
123
    def test_reserve_no_pool(self):
124
        # No floating IP pools
125
        self.pool.delete()
126
        response = self.post(URL, "test_user", json.dumps({}), "json")
127
        self.assertFault(response, 503, 'serviceUnavailable')
128

    
129
        # Full network
130
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
131
                                          public=True,
132
                                          subnet__cidr="192.168.2.0/31",
133
                                          subnet__gateway="192.168.2.1",
134
                                          subnet__pool__size=0)
135
        response = self.post(URL, "test_user", json.dumps({}), "json")
136
        self.assertFault(response, 503, 'serviceUnavailable')
137

    
138
        request = {'pool': net.id}
139
        response = self.post(URL, "test_user", json.dumps(request), "json")
140
        self.assertConflict(response)
141

    
142
    def test_reserve_with_address(self):
143
        request = {'pool': self.pool.id, "address": "192.168.2.10"}
144
        with mocked_quotaholder():
145
            response = self.post(URL, "test_user", json.dumps(request), "json")
146
        self.assertSuccess(response)
147
        ip = floating_ips.get()
148
        self.assertEqual(json.loads(response.content)["floating_ip"],
149
                         {"instance_id": None, "ip": "192.168.2.10",
150
                          "fixed_ip": None, "id": str(ip.id),
151
                          "pool": str(self.pool.id)})
152

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

    
158
        # Used by instance
159
        self.pool.reserve_address("192.168.2.20")
160
        request = {'pool': self.pool.id, "address": "192.168.2.20"}
161
        with mocked_quotaholder():
162
            response = self.post(URL, "test_user", json.dumps(request), "json")
163
        self.assertFault(response, 409, "conflict")
164

    
165
        # Address out of pool
166
        request = {'pool': self.pool.id, "address": "192.168.3.5"}
167
        with mocked_quotaholder():
168
            response = self.post(URL, "test_user", json.dumps(request), "json")
169
        self.assertBadRequest(response)
170

    
171
    def test_release_in_use(self):
172
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
173
        vm = ip.nic.machine
174
        with mocked_quotaholder():
175
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
176
        self.assertFault(response, 409, "conflict")
177
        # Also send a notification to remove the NIC and assert that FIP is in
178
        # use until notification from ganeti arrives
179
        request = {"removeFloatingIp": {"address": ip.address}}
180
        url = SERVERS_URL + "/%s/action" % vm.id
181
        with patch('synnefo.logic.rapi_pool.GanetiRapiClient') as c:
182
            c().ModifyInstance.return_value = 10
183
            response = self.post(url, vm.userid, json.dumps(request),
184
                                 "json")
185
        self.assertEqual(response.status_code, 202)
186
        with mocked_quotaholder():
187
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
188
        self.assertFault(response, 409, "conflict")
189

    
190
    def test_release(self):
191
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
192
        with mocked_quotaholder():
193
            response = self.delete(URL + "/%s" % ip.id, ip.userid)
194
        self.assertSuccess(response)
195
        ips_after = floating_ips.filter(id=ip.id)
196
        self.assertEqual(len(ips_after), 0)
197

    
198
    @patch("synnefo.logic.backend", Mock())
199
    def test_delete_network_with_floating_ips(self):
200
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True,
201
                                   network=self.pool, nic=None)
202
        # Mark the network as non-pubic to not get 403
203
        network = ip.network
204
        network.public = False
205
        network.save()
206
        # Can not remove network with floating IPs
207
        with mocked_quotaholder():
208
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
209
                                   self.pool.userid)
210
        self.assertConflict(response)
211
        # But we can with only deleted Floating Ips
212
        ip.deleted = True
213
        ip.save()
214
        with mocked_quotaholder():
215
            response = self.delete(NETWORKS_URL + "/%s" % self.pool.id,
216
                                   self.pool.userid)
217
        self.assertSuccess(response)
218

    
219

    
220
POOLS_URL = join_urls(compute_path, "os-floating-ip-pools")
221

    
222

    
223
class FloatingIPPoolsAPITest(BaseAPITest):
224
    def test_no_pool(self):
225
        response = self.get(POOLS_URL)
226
        self.assertSuccess(response)
227
        self.assertEqual(json.loads(response.content)["floating_ip_pools"], [])
228

    
229
    def test_list_pools(self):
230
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
231
                                          public=True,
232
                                          subnet__cidr="192.168.2.0/30",
233
                                          subnet__gateway="192.168.2.1",
234
                                          subnet__pool__size=1,
235
                                          subnet__pool__offset=1)
236
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
237
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
238
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=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), "size": 1, "free": 1}])
243

    
244

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

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

    
260
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
261
    def test_add_floating_ip(self, mock):
262
        # Not exists
263
        url = SERVERS_URL + "/%s/action" % self.vm.id
264
        request = {"addFloatingIp": {"address": "10.0.0.1"}}
265
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
266
        self.assertItemNotFound(response)
267
        # In use
268
        ip = mf.IPv4AddressFactory(floating_ip=True, userid=self.vm.userid)
269
        request = {"addFloatingIp": {"address": ip.address}}
270
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
271
        self.assertConflict(response)
272
        # Success
273
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
274
                                   userid=self.vm.userid)
275
        request = {"addFloatingIp": {"address": ip.address}}
276
        mock().ModifyInstance.return_value = 1
277
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
278
        self.assertEqual(response.status_code, 202)
279
        ip_after = floating_ips.get(id=ip.id)
280
        self.assertEqual(ip_after.nic.machine, self.vm)
281
        nic = self.vm.nics.get()
282
        nic.state = "ACTIVE"
283
        nic.save()
284
        response = self.get(SERVERS_URL + "/%s" % self.vm.id,
285
                            self.vm.userid)
286
        self.assertSuccess(response)
287
        nic = json.loads(response.content)["server"]["attachments"][0]
288
        self.assertEqual(nic["OS-EXT-IPS:type"], "floating")
289

    
290
    @patch('synnefo.logic.rapi_pool.GanetiRapiClient')
291
    def test_remove_floating_ip(self, mock):
292
        # Not exists
293
        url = SERVERS_URL + "/%s/action" % self.vm.id
294
        request = {"removeFloatingIp": {"address": "10.0.0.1"}}
295
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
296
        self.assertBadRequest(response)
297
        # Not In Use
298
        ip = mf.IPv4AddressFactory(floating_ip=True, nic=None,
299
                                   userid=self.vm.userid)
300
        request = {"removeFloatingIp": {"address": ip.address}}
301
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
302
        self.assertBadRequest(response)
303
        # Success
304
        ip = mf.IPv4AddressFactory(floating_ip=True,
305
                                   userid=self.vm.userid, nic__machine=self.vm)
306
        request = {"removeFloatingIp": {"address": ip.address}}
307
        mock().ModifyInstance.return_value = 2
308
        response = self.post(url, self.vm.userid, json.dumps(request), "json")
309
        self.assertEqual(response.status_code, 202)
310
        # Yet used. Wait for the callbacks
311
        ip_after = floating_ips.get(id=ip.id)
312
        self.assertEqual(ip_after.nic.machine, self.vm)