Revision e4f484da

b/snf-cyclades-app/synnefo/api/floating_ips.py
42 42
from synnefo import quotas
43 43
from synnefo.db.models import Network, IPAddress
44 44
from synnefo.db import pools
45

  
45
from synnefo.logic import servers, backend
46 46

  
47 47
from logging import getLogger
48 48
log = getLogger(__name__)
49 49

  
50
'''
50 51
ips_urlpatterns = patterns(
51 52
    'synnefo.api.floating_ips',
52 53
    (r'^(?:/|.json|.xml)?$', 'demux'),
......
57 58
    "synnefo.api.floating_ips",
58 59
    (r'^(?:/|.json|.xml)?$', 'list_floating_ip_pools'),
59 60
)
61
'''
62

  
63
ips_urlpatterns = patterns(
64
    'synnefo.api.floating_ips',
65
    (r'^(?:/|.json|.xml)?$', 'demux'),
66
    (r'^/detail(?:.json|.xml)?$', 'list_floating_ips', {'detail': True}),
67
    (r'^/(\w+)(?:/|.json|.xml)?$', 'floating_ip_demux'))
60 68

  
61 69

  
62 70
def demux(request):
......
73 81
        return get_floating_ip(request, floating_ip_id)
74 82
    elif request.method == 'DELETE':
75 83
        return release_floating_ip(request, floating_ip_id)
84
    elif request.method == 'PUT':
85
        return update_floating_ip(request, floating_ip_id)
76 86
    else:
77 87
        return api.api_method_not_allowed(request)
78 88

  
79 89

  
80 90
def ip_to_dict(floating_ip):
81 91
    machine_id = None
92
    port_id = None
82 93
    if floating_ip.nic is not None:
83 94
        machine_id = floating_ip.nic.machine_id
84
    return {"fixed_ip": None,
95
        port_id = floating_ip.nic.id
96
    return {"fixed_ip_address": None,
85 97
            "id": str(floating_ip.id),
86 98
            "instance_id": str(machine_id) if machine_id else None,
87
            "ip": floating_ip.address,
88
            "pool": str(floating_ip.network_id)}
99
            "floating_ip_address": floating_ip.address,
100
            "port_id": str(floating_ip.nic.id) if port_id else None,
101
            "floating_network_id": str(floating_ip.network_id)}
89 102

  
90 103

  
91 104
@api.api_method(http_method="GET", user_required=True, logger=log,
......
125 138
def allocate_floating_ip(request):
126 139
    """Allocate a floating IP."""
127 140
    req = utils.get_request_dict(request)
141
    floating_ip_dict = api.utils.get_attribute(req, "floatingip",
142
                                               required=True)
128 143
    log.info('allocate_floating_ip %s', req)
129 144

  
130 145
    userid = request.user_uniq
131
    pool = req.get("pool", None)
132
    address = req.get("address", None)
133

  
134
    if pool is None:
135
        # User did not specified a pool. Choose a random public IP
136
        try:
137
            floating_ip = util.allocate_public_ip(userid=userid,
138
                                                  floating_ip=True)
139
        except pools.EmptyPool:
140
            raise faults.Conflict("No more IP addresses available.")
141
    else:
142
        try:
143
            network_id = int(pool)
144
        except ValueError:
145
            raise faults.BadRequest("Invalid pool ID.")
146
        network = util.get_network(network_id, userid, for_update=True,
147
                                   non_deleted=True)
148
        if not network.floating_ip_pool:
149
            # Check that it is a floating IP pool
150
            raise faults.ItemNotFound("Floating IP pool %s does not exist." %
151
                                      network_id)
152
        floating_ip = util.allocate_ip(network, userid, address=address,
153
                                       floating_ip=True)
146

  
147
    # the network_pool is a mandatory field
148
    network_pool = api.utils.get_attribute(floating_ip_dict,
149
                                           "floating_network_id",
150
                                           required=True)
151
    device_id = api.utils.get_attribute(floating_ip_dict, "device_id",
152
                                        required=False)
153
    address = api.utils.get_attribute(floating_ip_dict, "floating_ip_address",
154
                                      required=False)
155

  
156
    if device_id:
157
        vm = util.get_vm(device_id, userid, non_deleted=False)
158

  
159
    try:
160
        network_id = int(network_pool)
161
    except ValueError:
162
        raise faults.BadRequest("Invalid networkd ID.")
163
    network = util.get_network(network_id, userid, for_update=True,
164
                               non_deleted=True)
165
    if not network.floating_ip_pool:
166
        # Check that it is a floating IP pool
167
        raise faults.ItemNotFound("Floating IP pool %s does not exist." %
168
                                  network_id)
169
    floating_ip = util.allocate_ip(network, userid, address=address,
170
                                   floating_ip=True)
154 171

  
155 172
    quotas.issue_and_accept_commission(floating_ip)
156 173
    transaction.commit()
157 174

  
158 175
    log.info("User '%s' allocated floating IP '%s'", userid, floating_ip)
159 176

  
177
    # connect to the given vm if any
178
    if device_id:
179
        nic, ipaddress = servers.create_nic(vm, ipaddress=floating_ip)
180
        servers.backend.connect_to_network(vm, nic)
181

  
160 182
    request.serialization = "json"
161 183
    data = json.dumps({"floating_ip": ip_to_dict(floating_ip)})
162 184
    return HttpResponse(data, status=200)
......
194 216
    return HttpResponse(status=204)
195 217

  
196 218

  
219
@api.api_method(http_method='PUT', user_required=True, logger=log,
220
                serializations=["json"])
221
@transaction.commit_on_success
222
def update_floating_ip(request, floating_ip_id):
223
    """Update a floating IP."""
224
    userid = request.user_uniq
225
    log.info("update_floating_ip '%s'. User '%s'.", floating_ip_id, userid)
226

  
227
    req = utils.get_request_dict(request)
228
    info = api.utils.get_attribute(req, "floatingip", required=True)
229

  
230
    device_id = api.utils.get_attribute(info, "device_id", required=False)
231

  
232
    floating_ip = util.get_floating_ip_by_id(userid, floating_ip_id,
233
                                             for_update=True)
234
    if device_id:
235
        # attach
236
        vm = util.get_vm(device_id, userid)
237
        nic, floating_ip = servers.create_nic(vm, ipaddress=floating_ip)
238
        backend.connect_to_network(vm, nic)
239
    else:
240
        # dettach
241
        nic = floating_ip.nic
242
        if not nic:
243
            raise faults.BadRequest("The floating IP is not associated\
244
                                    with any device")
245
        vm = nic.machine
246
        servers.disconnect(vm, nic)
247
    return HttpResponse(status=202)
248

  
249

  
197 250
# Floating IP pools
198 251
@api.api_method(http_method='GET', user_required=True, logger=log,
199 252
                serializations=["json"])
b/snf-cyclades-app/synnefo/api/management/commands/floating-ip-dettach.py
35 35

  
36 36
from django.core.management.base import BaseCommand, CommandError
37 37
from synnefo.management.common import get_floating_ip_by_address
38
from synnefo.logic import backend
38
from synnefo.logic import servers
39 39

  
40 40
class Command(BaseCommand):
41 41
    can_import_settings = True
......
58 58
        nic = floating_ip.nic
59 59
        vm = nic.machine
60 60

  
61
        backend.disconnect_from_network(vm, nic)
61
        servers.disconnect(vm, nic)
62 62
        self.stdout.write("Dettached floating IP %s from  %s.\n"
63 63
                           % (floating_ip_id, vm))
b/snf-cyclades-app/synnefo/api/tests/floating_ips.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
import json
35

  
34
from django.utils import simplejson as json
36 35
from snf_django.utils.testing import BaseAPITest, mocked_quotaholder
37 36
from synnefo.db.models import IPAddress
38 37
from synnefo.db import models_factory as mf
39 38
from synnefo.db.models_factory import (NetworkFactory,
40 39
                                       VirtualMachineFactory)
41
from mock import patch, Mock
40

  
41
from mock import patch
42 42
from functools import partial
43 43

  
44 44
from synnefo.cyclades_settings import cyclades_services
......
47 47

  
48 48

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

  
......
77 77
        api_ip = json.loads(response.content)["floating_ips"][0]
78 78
        self.assertEqual(api_ip,
79 79
                         {"instance_id": str(ip.nic.machine_id),
80
                          "ip": ip.address,
81
                          "fixed_ip": None,
80
                          "floating_ip_address": ip.address,
81
                          "fixed_ip_address": None,
82 82
                          "id": str(ip.id),
83
                          "pool": str(ip.network_id)})
83
                          "port_id": str(ip.nic.id),
84
                          "floating_network_id": str(ip.network_id)})
84 85

  
85 86
    def test_get_ip(self):
86 87
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
......
90 91
        api_ip = json.loads(response.content)["floating_ip"]
91 92
        self.assertEqual(api_ip,
92 93
                         {"instance_id": str(ip.nic.machine_id),
93
                          "ip": ip.address,
94
                          "fixed_ip": None,
94
                          "floating_ip_address": ip.address,
95
                          "fixed_ip_address": None,
95 96
                          "id": str(ip.id),
96
                          "pool": str(ip.network_id)})
97
                          "port_id": str(ip.nic.id),
98
                          "floating_network_id": str(ip.network_id)})
97 99

  
98 100
    def test_wrong_user(self):
99 101
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
......
107 109
        self.assertItemNotFound(response)
108 110

  
109 111
    def test_reserve(self):
110
        request = {'pool': self.pool.id}
112
        request = {"floatingip": {
113
            "floating_network_id": self.pool.id}
114
            }
111 115
        with mocked_quotaholder():
112 116
            response = self.post(URL, "test_user", json.dumps(request), "json")
113 117
        self.assertSuccess(response)
118
        api_ip = json.loads(response.content, encoding="utf-8")["floating_ip"]
114 119
        ip = floating_ips.get()
115 120
        self.assertEqual(ip.address, "192.168.2.2")
116 121
        self.assertEqual(ip.nic, None)
117 122
        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)})
123
        self.assertEqual(api_ip,
124
                         {"instance_id": None,
125
                          "floating_ip_address": "192.168.2.2",
126
                          "fixed_ip_address": None,
127
                          "id": str(ip.id),
128
                          "port_id": None,
129
                          "floating_network_id": str(self.pool.id)})
122 130

  
123 131
    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')
132
        # Network is not a floating IP pool
133
        pool2 = mf.NetworkWithSubnetFactory(floating_ip_pool=False,
134
                                            public=True,
135
                                            subnet__cidr="192.168.2.0/24",
136
                                            subnet__gateway="192.168.2.1")
137
        request = {"floatingip": {
138
            'floating_network_id': pool2.id}
139
            }
140
        response = self.post(URL, "test_user", json.dumps(request), "json")
141
        self.assertEqual(response.status_code, 404)
128 142

  
129 143
        # Full network
130 144
        net = mf.NetworkWithSubnetFactory(floating_ip_pool=True,
......
132 146
                                          subnet__cidr="192.168.2.0/31",
133 147
                                          subnet__gateway="192.168.2.1",
134 148
                                          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}
149
        request = {"floatingip": {
150
            'floating_network_id': net.id}
151
            }
139 152
        response = self.post(URL, "test_user", json.dumps(request), "json")
140 153
        self.assertConflict(response)
141 154

  
142 155
    def test_reserve_with_address(self):
143
        request = {'pool': self.pool.id, "address": "192.168.2.10"}
156
        request = {"floatingip": {
157
            "floating_network_id": self.pool.id,
158
            "floating_ip_address": "192.168.2.10"}
159
            }
144 160
        with mocked_quotaholder():
145 161
            response = self.post(URL, "test_user", json.dumps(request), "json")
146 162
        self.assertSuccess(response)
147 163
        ip = floating_ips.get()
148 164
        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)})
165
                         {"instance_id": None,
166
                          "floating_ip_address": "192.168.2.10",
167
                          "fixed_ip_address": None,
168
                          "id": str(ip.id),
169
                          "port_id": None,
170
                          "floating_network_id": str(self.pool.id)})
152 171

  
153 172
        # Already reserved
154 173
        with mocked_quotaholder():
......
157 176

  
158 177
        # Used by instance
159 178
        self.pool.reserve_address("192.168.2.20")
160
        request = {'pool': self.pool.id, "address": "192.168.2.20"}
179
        request = {"floatingip": {
180
            "floating_network_id": self.pool.id,
181
            "floating_ip_address": "192.168.2.20"}
182
            }
161 183
        with mocked_quotaholder():
162 184
            response = self.post(URL, "test_user", json.dumps(request), "json")
163 185
        self.assertFault(response, 409, "conflict")
164 186

  
165 187
        # Address out of pool
166
        request = {'pool': self.pool.id, "address": "192.168.3.5"}
188
        request = {"floatingip": {
189
            "floating_network_id": self.pool.id,
190
            "floating_ip_address": "192.168.3.5"}
191
            }
167 192
        with mocked_quotaholder():
168 193
            response = self.post(URL, "test_user", json.dumps(request), "json")
169 194
        self.assertBadRequest(response)
170 195

  
196
    @patch("synnefo.db.models.get_rapi_client")
197
    def test_reserve_and_connect(self, mrapi):
198
        vm = mf.VirtualMachineFactory(userid="test_user")
199
        request = {"floatingip": {
200
            "floating_network_id": self.pool.id,
201
            "floating_ip_address": "192.168.2.12",
202
            "device_id": vm.id}
203
            }
204
        response = self.post(URL, "test_user", json.dumps(request), "json")
205
        ip = floating_ips.get()
206
        api_ip = json.loads(response.content, "utf-8")["floating_ip"]
207
        self.assertEqual(api_ip,
208
                         {"instance_id": str(vm.id),
209
                          "floating_ip_address": "192.168.2.12",
210
                          "fixed_ip_address": None,
211
                          "id": str(ip.id),
212
                          "port_id": str(vm.nics.all()[0].id),
213
                          "floating_network_id": str(self.pool.id)})
214

  
215
    @patch("synnefo.db.models.get_rapi_client")
216
    def test_update_attach(self, mrapi):
217
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
218
        vm = mf.VirtualMachineFactory(userid="user1")
219
        request = {"floatingip": {
220
            "device_id": vm.id}
221
            }
222
        with mocked_quotaholder():
223
            response = self.put(URL + "/%s" % ip.id, "user1",
224
                                json.dumps(request), "json")
225
        self.assertEqual(response.status_code, 202)
226

  
227
    def test_update_attach_conflict(self):
228
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
229
        vm = mf.VirtualMachineFactory(userid="user1")
230
        request = {"floatingip": {
231
            "device_id": vm.id}
232
            }
233
        with mocked_quotaholder():
234
            response = self.put(URL + "/%s" % ip.id, "user1",
235
                                json.dumps(request), "json")
236
        self.assertEqual(response.status_code, 409)
237

  
238
    @patch("synnefo.db.models.get_rapi_client")
239
    def test_update_dettach(self, mrapi):
240
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
241
        request = {"floatingip": {
242
            "device_id": None}
243
            }
244
        mrapi().ModifyInstance.return_value = 42
245
        with mocked_quotaholder():
246
            response = self.put(URL + "/%s" % ip.id, "user1",
247
                                json.dumps(request), "json")
248
        self.assertEqual(response.status_code, 202)
249

  
250
    def test_update_dettach_unassociated(self):
251
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True, nic=None)
252
        request = {"floatingip": {}}
253
        with mocked_quotaholder():
254
            response = self.put(URL + "/%s" % ip.id, "user1",
255
                                json.dumps(request), "json")
256
        self.assertEqual(response.status_code, 400)
257

  
171 258
    def test_release_in_use(self):
172 259
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True)
173 260
        vm = ip.nic.machine
......
194 281
        self.assertSuccess(response)
195 282
        ips_after = floating_ips.filter(id=ip.id)
196 283
        self.assertEqual(len(ips_after), 0)
197

  
284
'''
198 285
    @patch("synnefo.logic.backend", Mock())
199 286
    def test_delete_network_with_floating_ips(self):
200 287
        ip = mf.IPv4AddressFactory(userid="user1", floating_ip=True,
......
310 397
        # Yet used. Wait for the callbacks
311 398
        ip_after = floating_ips.get(id=ip.id)
312 399
        self.assertEqual(ip_after.nic.machine, self.vm)
400
'''
b/snf-cyclades-app/synnefo/api/urls.py
51 51
    (r'^ports', include(ports)),
52 52
    (r'^subnets', include(subnets)),
53 53
    (r'^extensions', include(extensions)),
54
    (r'^os-floating-ips', include(floating_ips.ips_urlpatterns)),
55
    (r'^os-floating-ip-pools', include(floating_ips.pools_urlpatterns)),
54
    (r'^floatingips', include(floating_ips.ips_urlpatterns)),
55
#    (r'^os-floating-ip-pools', include(floating_ips.pools_urlpatterns)),
56 56
)
57 57

  
58 58

  
b/snf-cyclades-app/synnefo/db/models_factory.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
import factory
35
from ipaddr import IPNetwork
35 36
from synnefo.db import models
36 37
from random import choice
37 38
from string import letters, digits

Also available in: Unified diff