Statistics
| Branch: | Tag: | Revision:

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

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 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
        pool = 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
        response = self.post(URL, "test_user", json.dumps({}), "json")
135
        self.assertFault(response, 503, 'serviceUnavailable')
136

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

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

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

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

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

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

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

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

    
218

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

    
221

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

    
228
    def test_list_pools(self):
229
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
230
                                          public=True,
231
                                          subnet__cidr="192.168.2.0/30",
232
                                          subnet__gateway="192.168.2.1")
233
        mf.NetworkWithSubnetFactory(public=True, deleted=True)
234
        mf.NetworkWithSubnetFactory(public=False, deleted=False)
235
        mf.NetworkWithSubnetFactory(public=True, floating_ip_pool=False)
236
        response = self.get(POOLS_URL)
237
        self.assertSuccess(response)
238
        self.assertEqual(json.loads(response.content)["floating_ip_pools"],
239
                         [{"name": str(net.id), "size": 4, "free": 1}])
240

    
241

    
242
class FloatingIPActionsTest(BaseAPITest):
243
    def setUp(self):
244
        self.vm = VirtualMachineFactory()
245
        self.vm.operstate = "ACTIVE"
246
        self.vm.save()
247

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

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

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