Commit 98b8f43c authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Auto allocate a floating IP

Make 'pool' attribute of POST /os-floating-ips request optional. If
attribute is missing, the server will allocate a floating IP from one of
the available public networks.
parent f8f20840
......@@ -126,41 +126,45 @@ def allocate_floating_ip(request):
log.info('allocate_floating_ip %s', req)
userid = request.user_uniq
try:
pool = req['pool']
except KeyError:
raise faults.BadRequest("Malformed request. Missing"
" 'pool' attribute")
try:
objects = Network.objects.select_for_update()
network = objects.get(id=pool, public=True, deleted=False)
except Network.DoesNotExist:
raise faults.ItemNotFound("Pool '%s' does not exist." % pool)
pool = req.get("pool", None)
address = req.get("address", None)
machine = None
net_objects = Network.objects.select_for_update().filter(public=True,
deleted=False)
try:
if address is None:
address = util.get_network_free_address(network) # Get X-Lock
if pool is None:
# User did not specified a pool. Choose a random public IP
network, address = util.allocate_public_ip(net_objects)
else:
if FloatingIP.objects.filter(network=network,
deleted=False,
ipv4=address).exists():
msg = "Floating IP '%s' is reserved" % address
raise faults.Conflict(msg)
pool = network.get_pool() # Gets X-Lock
if not pool.contains(address):
raise faults.BadRequest("Invalid address")
if not pool.is_available(address):
try:
network.nics.get(ipv4=address,
machine__userid=userid)
except NetworkInterface.DoesNotExist:
msg = "Address '%s' is already in use" % address
try:
network = net_objects.get(id=pool)
except Network.DoesNotExist:
raise faults.ItemNotFound("Pool '%s' does not exist." % pool)
if address is None:
# User did not specified an IP address. Choose a random one
# Gets X-Lock on IP pool
address = util.get_network_free_address(network)
else:
# User specified an IP address. Check that it is not a used
# floating IP
if FloatingIP.objects.filter(network=network,
deleted=False,
ipv4=address).exists():
msg = "Floating IP '%s' is reserved" % address
raise faults.Conflict(msg)
pool.reserve(address)
pool.save()
pool = network.get_pool() # Gets X-Lock
# Check address belongs to pool
if not pool.contains(address):
raise faults.BadRequest("Invalid address")
if pool.is_available(address):
pool.reserve(address)
pool.save()
# If address is not available, check that it belongs to the
# same user
elif not network.nics.filter(ipv4=address,
machine__userid=userid).exists():
msg = "Address '%s' is already in use" % address
raise faults.Conflict(msg)
floating_ip = FloatingIP.objects.create(ipv4=address, network=network,
userid=userid, machine=machine)
quotas.issue_and_accept_commission(floating_ip)
......
......@@ -37,7 +37,8 @@ from snf_django.utils.testing import BaseAPITest, mocked_quotaholder
from synnefo.db.models import FloatingIP
from synnefo.db.models_factory import (FloatingIPFactory, NetworkFactory,
VirtualMachineFactory,
NetworkInterfaceFactory)
NetworkInterfaceFactory,
BackendNetworkFactory)
from mock import patch, Mock
......@@ -100,6 +101,26 @@ class FloatingIPAPITest(BaseAPITest):
{"instance_id": None, "ip": "192.168.2.1",
"fixed_ip": None, "id": "1", "pool": "1"})
def test_reserve_no_pool(self):
# No networks
with mocked_quotaholder():
response = self.post(URL, "test_user", json.dumps({}), "json")
self.assertFault(response, 413, "overLimit")
# Full network
net = NetworkFactory(userid="test_user", subnet="192.168.2.0/32",
gateway=None, public=True)
with mocked_quotaholder():
response = self.post(URL, "test_user", json.dumps({}), "json")
self.assertFault(response, 413, "overLimit")
# Success
net2 = NetworkFactory(userid="test_user", subnet="192.168.2.0/24",
gateway=None, public=True)
with mocked_quotaholder():
response = self.post(URL, "test_user", json.dumps({}), "json")
self.assertEqual(json.loads(response.content)["floating_ip"],
{"instance_id": None, "ip": "192.168.2.1",
"fixed_ip": None, "id": "1", "pool": str(net2.id)})
def test_reserve_full(self):
net = NetworkFactory(userid="test_user", subnet="192.168.2.0/32",
gateway=None, public=True)
......@@ -186,7 +207,7 @@ class FloatingIPAPITest(BaseAPITest):
@patch("synnefo.logic.backend", Mock())
def test_delete_network_with_floating_ips(self):
ip = FloatingIPFactory(machine=None)
ip = FloatingIPFactory(machine=None, network__flavor="IP_LESS_ROUTED")
net = ip.network
# Can not remove network with floating IPs
with mocked_quotaholder():
......@@ -247,11 +268,15 @@ class FloatingIPActionsTest(BaseAPITest):
# In use
vm1 = VirtualMachineFactory()
ip1 = FloatingIPFactory(userid=self.vm.userid, machine=vm1)
BackendNetworkFactory(network=ip1.network, backend=vm1.backend,
operstate='ACTIVE')
request = {"addFloatingIp": {"address": ip1.ipv4}}
response = self.post(url, self.vm.userid, json.dumps(request), "json")
self.assertFault(response, 409, "conflict")
# Success
ip1 = FloatingIPFactory(userid=self.vm.userid, machine=None)
BackendNetworkFactory(network=ip1.network, backend=self.vm.backend,
operstate='ACTIVE')
request = {"addFloatingIp": {"address": ip1.ipv4}}
response = self.post(url, self.vm.userid, json.dumps(request), "json")
self.assertEqual(response.status_code, 202)
......
......@@ -213,7 +213,7 @@ def get_flavor_provider(flavor):
return disk_template, provider
def get_network(network_id, user_id, for_update=False):
def get_network(network_id, user_id, for_update=False, non_deleted=False):
"""Return a Network instance or raise ItemNotFound."""
try:
......@@ -221,7 +221,11 @@ def get_network(network_id, user_id, for_update=False):
objects = Network.objects
if for_update:
objects = objects.select_for_update()
return objects.get(Q(userid=user_id) | Q(public=True), id=network_id)
network = objects.get(Q(userid=user_id) | Q(public=True),
id=network_id)
if non_deleted and network.deleted:
raise faults.BadRequest("Networkhas been deleted.")
return network
except (ValueError, Network.DoesNotExist):
raise faults.ItemNotFound('Network not found.')
......@@ -336,6 +340,23 @@ def get_network_free_address(network):
return address
def allocate_public_ip(networks=None):
"""Allocate an IP address from public networks."""
if networks is None:
networks = Network.objects.select_for_update().filter(public=True,
deleted=False)
for network in networks:
try:
address = get_network_free_address(network)
except:
pass
else:
return network, address
msg = "Can not allocate public IP. Public networks are full."
log.error(msg)
raise faults.OverLimit(msg)
def get_nic(machine, network):
try:
return NetworkInterface.objects.get(machine=machine, network=network)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment