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