Commit 056eee6c authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Fix default/forced server networking

Fix the default behaviour for networking of servers that are being
created. There are two settings controlling this behaviour:

* CYCLADES_DEFAULT_SERVER_NETWORKS: This setting contains list of
  networks to connect a newly created server to, if the user has not
  specified them explicitly in the POST /server API call. Each member of
  the list may be a network UUID, a tuple of network UUIDs,
  "SNF:ANY_PUBLIC_IPV4" [any public network with an IPv4 subnet
  defined], "SNF:ANY_PUBLIC_IPV6 [any public network with only an IPV6 subnet
  defined],  or "SNF:ANY_PUBLIC" [any public network].

  Access control and quota policy are enforced, just as if the user had
  specified the value of CYCLADES_DEFAULT_SERVER_NETWORKS in the content
  of the POST /call, after processing of "SNF:*" directives.

  Default value: ["SNF:ANY_PUBLIC"]

* CYCLADES_FORCED_SERVER_NETWORKS: This setting contains a list of
  networks which every new server will be forced to connect to,
  regardless of the contents of the POST /servers call, or the value of
  CYCLADES_DEFAULT_SERVER_NETWORKS.  Its format is identical to that of
  CYCLADES_DEFAULT_SERVER_NETWORKS.

 No access control or quota policy are enforced.  The server will get
 all IPv4/IPv6 addresses needed to connect to the networks specified in
 CYCLADES_FORCED_SERVER_NETWORKS, regardless of the state of the
 floating IP pool of the user, and without allocating any floating IPs.

 Default value: ["SNF:ANY_PUBLIC_IPV6"]

Also this commit changes how API handles requests that specify a public
network without specifying a floating IP address: If the request does
not contain the 'fixed_ip' attribute, the server will try to
automatically use one of the free floating IP addresses of the user
that are allocated from the specified network.
parent 882c4f21
......@@ -17,11 +17,30 @@
## Network Configuration
##
#
## List of network IDs. All created instances will get a NIC connected to each
## network of this list. If the special network ID "SNF:ANY_PUBLIC" is used,
## Cyclades will automatically choose a public network and connect the server to
## it.
#DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]
## CYCLADES_DEFAULT_SERVER_NETWORKS setting contains a list of networks to
## connect a newly created server to, *if the user has not* specified them
## explicitly in the POST /server API call.
## Each member of the list may be a network UUID, a tuple of network UUIDs,
## "SNF:ANY_PUBLIC_IPV4" [any public network with an IPv4 subnet defined],
## "SNF:ANY_PUBLIC_IPV6 [any public network with only an IPV6 subnet defined],
## or "SNF:ANY_PUBLIC" [any public network].
##
## Access control and quota policy are enforced, just as if the user had
## specified the value of CYCLADES_DEFAULT_SERVER_NETWORKS in the content
## of the POST /call, after processing of "SNF:*" directives."
#CYCLADES_DEFAULT_SERVER_NETWORKS = ["SNF:ANY_PUBLIC"]
#
## This setting contains a list of networks which every new server
## will be forced to connect to, regardless of the contents of the POST
## /servers call, or the value of CYCLADES_DEFAULT_SERVER_NETWORKS.
## Its format is identical to that of CYCLADES_DEFAULT_SERVER_NETWORKS.
#
## WARNING: No access control or quota policy are enforced.
## The server will get all IPv4/IPv6 addresses needed to connect to the
## networks specified in CYCLADES_FORCED_SERVER_NETWORKS, regardless
## of the state of the floating IP pool of the user, and without
## allocating any floating IPs."
#CYCLADES_FORCED_SERVER_NETWORKS = ["SNF:ANY_PUBLIC_IPV6"]
#
#
## Maximum allowed network size for private networks.
......
......@@ -118,13 +118,18 @@ class Command(BaseCommand):
network_ids = parse_list(options["network_ids"])
port_ids = parse_list(options["port_ids"])
floating_ip_ids = parse_list(options["floating_ip_ids"])
floating_ips = \
map(lambda x: common.get_floating_ip_by_id(x, for_update=True),
floating_ip_ids)
floating_ips = map(lambda fp: {"uuid": fp.network_id,
"fixed_ip": fp.address},
floating_ips)
networks = map(lambda x: {"uuid": x}, network_ids)
ports = map(lambda x: {"port": x}, port_ids)
server = servers.create(user_id, name, password, flavor, image,
networks=(ports+networks),
floating_ips=floating_ip_ids,
networks=(floating_ips+ports+networks),
use_backend=backend)
pprint.pprint_server(server, stdout=self.stdout)
......
......@@ -378,8 +378,9 @@ def create_server(request):
flavor_id = server['flavorRef']
personality = server.get('personality', [])
assert isinstance(personality, list)
networks = server.get("networks", [])
assert isinstance(networks, list)
networks = server.get("networks")
if networks is not None:
assert isinstance(networks, list)
except (KeyError, AssertionError):
raise faults.BadRequest("Malformed request")
......
......@@ -340,7 +340,6 @@ class ServerAPITest(ComputeAPITest):
response = self.mypost('servers/42/metadata/foo')
self.assertMethodNotAllowed(response)
fixed_image = Mock()
fixed_image.return_value = {'location': 'pithos://foo',
'checksum': '1234',
......@@ -355,13 +354,7 @@ fixed_image.return_value = {'location': 'pithos://foo',
class ServerCreateAPITest(ComputeAPITest):
def setUp(self):
self.flavor = mfactory.FlavorFactory()
# Create public network and backend
subnet = mfactory.IPv4SubnetFactory(network__public=True)
self.network = subnet.network
self.backend = mfactory.BackendFactory()
mfactory.BackendNetworkFactory(network=self.network,
backend=self.backend,
operstate="ACTIVE")
self.request = {
"server": {
"name": "new-server-test",
......@@ -374,13 +367,20 @@ class ServerCreateAPITest(ComputeAPITest):
"personality": []
}
}
# Create dummy public IPv6 network
sub6 = mfactory.IPv6SubnetFactory(network__public=True)
self.net6 = sub6.network
self.network_settings = {
"CYCLADES_DEFAULT_SERVER_NETWORKS": [],
"CYCLADES_FORCED_SERVER_NETWORKS": ["SNF:ANY_PUBLIC_IPV6"]
}
def test_create_server(self, mrapi):
"""Test if the create server call returns the expected response
if a valid request has been speficied."""
mrapi().CreateInstance.return_value = 12
with override_settings(settings, DEFAULT_INSTANCE_NETWORKS=[]):
with override_settings(settings, **self.network_settings):
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(self.request), 'json')
......@@ -398,180 +398,195 @@ class ServerCreateAPITest(ComputeAPITest):
self.assertEqual(api_server['name'], db_vm.name)
self.assertEqual(api_server['status'], db_vm.operstate)
# Test drained flag in Network:
self.network.drained = True
self.network.save()
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(self.request), 'json')
self.assertEqual(response.status_code, 503, "serviceUnavailable")
def test_create_server_with_port(self, mrapi):
mrapi().CreateInstance.return_value = 42
ip = mfactory.IPv4AddressFactory(nic__machine=None)
port1 = ip.nic
def test_create_server_no_flavor(self, mrapi):
request = deepcopy(self.request)
request["server"]["networks"] = [{"port": port1.id}]
with mocked_quotaholder():
response = self.mypost("servers", port1.userid,
json.dumps(request), 'json')
request["server"]["flavorRef"] = 42
with override_settings(settings, **self.network_settings):
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
self.assertItemNotFound(response)
def test_create_server_error(self, mrapi):
"""Test if the create server call returns the expected response
if a valid request has been speficied."""
mrapi().CreateInstance.side_effect = GanetiApiError("..ganeti is down")
request = self.request
with override_settings(settings, **self.network_settings):
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
self.assertEqual(response.status_code, 202)
vm_id = json.loads(response.content)["server"]["id"]
port1 = NetworkInterface.objects.get(id=port1.id)
self.assertEqual(port1.machine_id, vm_id)
# 409 if already used
with mocked_quotaholder():
response = self.mypost("servers", port1.userid,
json.dumps(request), 'json')
self.assertConflict(response)
# Test permissions
ip = mfactory.IPv4AddressFactory(userid="user1", nic__userid="user1")
port2 = ip.nic
request["server"]["networks"] = [{"port": port2.id}]
with mocked_quotaholder():
response = self.mypost("servers", "user2",
json.dumps(request), 'json')
self.assertEqual(response.status_code, 404)
mrapi().CreateInstance.assert_called_once()
vm = VirtualMachine.objects.get()
# The VM has not been deleted
self.assertFalse(vm.deleted)
# but is in "ERROR" operstate
self.assertEqual(vm.operstate, "ERROR")
def test_create_network_settings(self, mrapi):
def test_create_network_info(self, mrapi):
mrapi().CreateInstance.return_value = 12
# Create public network and backend
subnet1 = mfactory.IPv4SubnetFactory(network__userid="test_user")
bnet1 = mfactory.BackendNetworkFactory(network=subnet1.network,
backend=self.backend,
operstate="ACTIVE")
subnet2 = mfactory.IPv4SubnetFactory(network__userid="test_user")
bnet2 = mfactory.BackendNetworkFactory(network=subnet2.network,
backend=self.backend,
operstate="ACTIVE")
# User requested private networks
s1 = mfactory.IPv4SubnetFactory(network__userid="test")
s2 = mfactory.IPv6SubnetFactory(network__userid="test")
# and a public IPv6
request = deepcopy(self.request)
request["server"]["networks"] = [{"uuid": bnet1.network.id},
{"uuid": bnet2.network.id}]
with override_settings(settings,
DEFAULT_INSTANCE_NETWORKS=[
"SNF:ANY_PUBLIC"]):
request["server"]["networks"] = [{"uuid": s1.network_id},
{"uuid": s2.network_id}]
with override_settings(settings, **self.network_settings):
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
response = self.mypost('servers', "test",
json.dumps(request), 'json')
self.assertEqual(response.status_code, 202)
name, args, kwargs = mrapi().CreateInstance.mock_calls[0]
self.assertEqual(len(kwargs["nics"]), 3)
self.assertEqual(kwargs["nics"][0]["network"],
self.network.backend_id)
self.assertEqual(kwargs["nics"][1]["network"],
bnet1.network.backend_id)
self.assertEqual(kwargs["nics"][2]["network"],
bnet2.network.backend_id)
subnet3 = mfactory.IPv4SubnetFactory(network__public=True,
network__floating_ip_pool=True)
bnet3 = mfactory.BackendNetworkFactory(network=subnet3.network,
backend=self.backend,
operstate="ACTIVE")
request["server"]["floating_ips"] = []
with override_settings(settings,
DEFAULT_INSTANCE_NETWORKS=[bnet3.network.id]):
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
self.assertEqual(response.status_code, 202)
name, args, kwargs = mrapi().CreateInstance.mock_calls[1]
self.assertEqual(len(kwargs["nics"]), 3)
self.assertEqual(kwargs["nics"][0]["network"],
bnet3.network.backend_id)
self.assertEqual(kwargs["nics"][1]["network"],
bnet1.network.backend_id)
self.assertEqual(kwargs["nics"][2]["network"],
bnet2.network.backend_id)
# test invalid network in DEFAULT_INSTANCE_NETWORKS
with override_settings(settings, DEFAULT_INSTANCE_NETWORKS=[42]):
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
self.assertFault(response, 500, "internalServerError")
# test connect to public netwok
self.assertEqual(kwargs["nics"][0]["network"], self.net6.backend_id)
self.assertEqual(kwargs["nics"][1]["network"], s1.network.backend_id)
self.assertEqual(kwargs["nics"][2]["network"], s2.network.backend_id)
# but fail if others user network
s3 = mfactory.IPv6SubnetFactory(network__userid="test_other")
request = deepcopy(self.request)
request["server"]["networks"] = [{"uuid": self.network.id}]
with override_settings(settings,
DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]):
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
self.assertBadRequest(response)
request["server"]["networks"] = [{"uuid": s3.network_id}]
response = self.mypost('servers', "test", json.dumps(request), 'json')
self.assertEqual(response.status_code, 404)
# test wrong user
# User requested public networks
# but no floating IP..
s1 = mfactory.IPv4SubnetFactory(network__public=True)
request = deepcopy(self.request)
request["server"]["networks"] = [{"uuid": bnet1.network.id}]
with override_settings(settings,
DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]):
with mocked_quotaholder():
response = self.mypost('servers', 'dummy_user',
json.dumps(request), 'json')
self.assertItemNotFound(response)
request["server"]["networks"] = [{"uuid": s1.network_id}]
response = self.mypost('servers', "test", json.dumps(request), 'json')
self.assertEqual(response.status_code, 409)
# Test floating IPs
# Add one floating IP
fp1 = mfactory.IPv4AddressFactory(userid="test", subnet=s1,
network=s1.network,
floating_ip=True, nic=None)
self.assertEqual(fp1.nic, None)
request = deepcopy(self.request)
fp1 = mfactory.FloatingIPFactory(address="10.0.0.2",
userid="test_user",
network=self.network,
nic=None)
fp2 = mfactory.FloatingIPFactory(address="10.0.0.3",
userid="test_user",
network=self.network,
nic=None)
request["server"]["networks"] = [{"uuid": bnet1.network.id},
{"uuid": fp1.network.id,
"fixed_ip": fp1.address},
{"uuid": fp2.network.id,
"fixed_ip": fp2.address}]
with override_settings(settings,
DEFAULT_INSTANCE_NETWORKS=[bnet3.network.id]):
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
request["server"]["networks"] = [{"uuid": s1.network_id,
"fixed_ip": fp1.address}]
with mocked_quotaholder():
with override_settings(settings, **self.network_settings):
response = self.mypost('servers', "test",
json.dumps(request), 'json')
self.assertEqual(response.status_code, 202)
api_server = json.loads(response.content)['server']
vm = VirtualMachine.objects.get(id=api_server["id"])
fp1 = IPAddress.objects.get(floating_ip=True, id=fp1.id)
fp2 = IPAddress.objects.get(floating_ip=True, id=fp2.id)
self.assertEqual(fp1.nic.machine, vm)
self.assertEqual(fp2.nic.machine, vm)
name, args, kwargs = mrapi().CreateInstance.mock_calls[2]
self.assertEqual(len(kwargs["nics"]), 4)
self.assertEqual(kwargs["nics"][0]["network"],
bnet3.network.backend_id)
self.assertEqual(kwargs["nics"][1]["network"],
bnet1.network.backend_id)
self.assertEqual(kwargs["nics"][2]["network"], fp1.network.backend_id)
self.assertEqual(kwargs["nics"][2]["ip"], fp1.address)
self.assertEqual(kwargs["nics"][3]["network"], fp2.network.backend_id)
self.assertEqual(kwargs["nics"][3]["ip"], fp2.address)
server_id = json.loads(response.content)["server"]["id"]
fp1 = IPAddress.objects.get(id=fp1.id)
self.assertEqual(fp1.nic.machine_id, server_id)
def test_create_server_no_flavor(self, mrapi):
# check used floating IP
response = self.mypost('servers', "test", json.dumps(request), 'json')
self.assertEqual(response.status_code, 409)
# Add more floating IP. but check auto-reserve
fp2 = mfactory.IPv4AddressFactory(userid="test", subnet=s1,
network=s1.network,
floating_ip=True, nic=None)
self.assertEqual(fp2.nic, None)
request = deepcopy(self.request)
request["server"]["flavorRef"] = 42
request["server"]["networks"] = [{"uuid": s1.network_id}]
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
self.assertItemNotFound(response)
with override_settings(settings, **self.network_settings):
response = self.mypost('servers', "test",
json.dumps(request), 'json')
self.assertEqual(response.status_code, 202)
server_id = json.loads(response.content)["server"]["id"]
fp2 = IPAddress.objects.get(id=fp2.id)
self.assertEqual(fp2.nic.machine_id, server_id)
def test_create_server_error(self, mrapi):
"""Test if the create server call returns the expected response
if a valid request has been speficied."""
mrapi().CreateInstance.side_effect = GanetiApiError("..ganeti is down")
name, args, kwargs = mrapi().CreateInstance.mock_calls[-1]
self.assertEqual(len(kwargs["nics"]), 2)
self.assertEqual(kwargs["nics"][0]["network"], self.net6.backend_id)
self.assertEqual(kwargs["nics"][1]["network"], fp2.network.backend_id)
request = self.request
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
def test_create_network_settings(self, mrapi):
mrapi().CreateInstance.return_value = 12
# User requested private networks
# no public IPv4
network_settings = {
"CYCLADES_DEFAULT_SERVER_NETWORKS": [],
"CYCLADES_FORCED_SERVER_NETWORKS": ["SNF:ANY_PUBLIC_IPV4"]
}
with override_settings(settings, **network_settings):
response = self.mypost('servers', "test", json.dumps(self.request),
'json')
self.assertEqual(response.status_code, 503)
# no public IPv4, IPv6 exists
network_settings = {
"CYCLADES_DEFAULT_SERVER_NETWORKS": [],
"CYCLADES_FORCED_SERVER_NETWORKS": ["SNF:ANY_PUBLIC"]
}
with override_settings(settings, **network_settings):
response = self.mypost('servers', "test", json.dumps(self.request),
'json')
self.assertEqual(response.status_code, 202)
mrapi().CreateInstance.assert_called_once()
vm = VirtualMachine.objects.get()
# The VM has not been deleted
self.assertFalse(vm.deleted)
# but is in "ERROR" operstate
self.assertEqual(vm.operstate, "ERROR")
server_id = json.loads(response.content)["server"]["id"]
vm = VirtualMachine.objects.get(id=server_id)
self.assertEqual(vm.nics.get().ipv4_address, None)
# IPv4 exists
mfactory.IPv4SubnetFactory(network__public=True,
cidr="192.168.2.0/24",
pool__offset=2,
pool__size=1)
with override_settings(settings, **network_settings):
response = self.mypost('servers', "test", json.dumps(self.request),
'json')
self.assertEqual(response.status_code, 202)
server_id = json.loads(response.content)["server"]["id"]
vm = VirtualMachine.objects.get(id=server_id)
self.assertEqual(vm.nics.get().ipv4_address, "192.168.2.2")
# Fixed networks
net1 = mfactory.NetworkFactory(userid="test")
net2 = mfactory.NetworkFactory(userid="test")
net3 = mfactory.NetworkFactory(userid="test")
network_settings = {
"CYCLADES_DEFAULT_SERVER_NETWORKS": [],
"CYCLADES_FORCED_SERVER_NETWORKS": [net1.id, [net2.id, net3.id],
(net3.id, net2.id)]
}
with override_settings(settings, **network_settings):
response = self.mypost('servers', "test", json.dumps(self.request),
'json')
self.assertEqual(response.status_code, 202)
server_id = json.loads(response.content)["server"]["id"]
vm = VirtualMachine.objects.get(id=server_id)
self.assertEqual(len(vm.nics.all()), 3)
def test_create_server_with_port(self, mrapi):
mrapi().CreateInstance.return_value = 42
ip = mfactory.IPv4AddressFactory(nic__machine=None)
port1 = ip.nic
request = deepcopy(self.request)
request["server"]["networks"] = [{"port": port1.id}]
with override_settings(settings, **self.network_settings):
with mocked_quotaholder():
response = self.mypost("servers", port1.userid,
json.dumps(request), 'json')
self.assertEqual(response.status_code, 202)
vm_id = json.loads(response.content)["server"]["id"]
port1 = NetworkInterface.objects.get(id=port1.id)
self.assertEqual(port1.machine_id, vm_id)
# 409 if already used
with override_settings(settings, **self.network_settings):
with mocked_quotaholder():
response = self.mypost("servers", port1.userid,
json.dumps(request), 'json')
self.assertConflict(response)
# Test permissions
ip = mfactory.IPv4AddressFactory(userid="user1", nic__userid="user1")
port2 = ip.nic
request["server"]["networks"] = [{"port": port2.id}]
with override_settings(settings, **self.network_settings):
with mocked_quotaholder():
response = self.mypost("servers", "user2",
json.dumps(request), 'json')
self.assertEqual(response.status_code, 404)
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
......
......@@ -17,11 +17,30 @@ POLL_LIMIT = 3600
# Network Configuration
#
# List of network IDs. All created instances will get a NIC connected to each
# network of this list. If the special network ID "SNF:ANY_PUBLIC" is used,
# Cyclades will automatically choose a public network and connect the server to
# it.
DEFAULT_INSTANCE_NETWORKS = ["SNF:ANY_PUBLIC"]
# CYCLADES_DEFAULT_SERVER_NETWORKS setting contains a list of networks to
# connect a newly created server to, *if the user has not* specified them
# explicitly in the POST /server API call.
# Each member of the list may be a network UUID, a tuple of network UUIDs,
# "SNF:ANY_PUBLIC_IPV4" [any public network with an IPv4 subnet defined],
# "SNF:ANY_PUBLIC_IPV6 [any public network with only an IPV6 subnet defined],
# or "SNF:ANY_PUBLIC" [any public network].
#
# Access control and quota policy are enforced, just as if the user had
# specified the value of CYCLADES_DEFAULT_SERVER_NETWORKS in the content
# of the POST /call, after processing of "SNF:*" directives."
CYCLADES_DEFAULT_SERVER_NETWORKS = ["SNF:ANY_PUBLIC"]
# This setting contains a list of networks which every new server
# will be forced to connect to, regardless of the contents of the POST
# /servers call, or the value of CYCLADES_DEFAULT_SERVER_NETWORKS.
# Its format is identical to that of CYCLADES_DEFAULT_SERVER_NETWORKS.
# WARNING: No access control or quota policy are enforced.
# The server will get all IPv4/IPv6 addresses needed to connect to the
# networks specified in CYCLADES_FORCED_SERVER_NETWORKS, regardless
# of the state of the floating IP pool of the user, and without
# allocating any floating IPs."
CYCLADES_FORCED_SERVER_NETWORKS = ["SNF:ANY_PUBLIC_IPV6"]
# Maximum allowed network size for private networks.
MAX_CIDR_BLOCK = 22
......
......@@ -34,7 +34,6 @@ from django.utils import importlib
from django.conf import settings
from synnefo.db.models import Backend
from synnefo.logic import backend as backend_mod
from synnefo.api import util
log = logging.getLogger(__name__)
......@@ -112,9 +111,6 @@ def get_available_backends(flavor):
backends = backends.filter(offline=False, drained=False,
disk_templates__contains=disk_template)
backends = list(backends)
if "SNF:ANY_PUBLIC" in settings.DEFAULT_INSTANCE_NETWORKS:
backends = filter(lambda x: util.backend_has_free_public_ip(x),
backends)
return backends
......
......@@ -61,7 +61,7 @@ def allocate_ip(network, userid, address=None, floating_ip=False):
(address, network.id))
def allocate_public_ip(userid, floating_ip=False, backend=None):
def allocate_public_ip(userid, floating_ip=False, backend=None, networks=None):
"""Try to allocate a public or floating IP address.
Try to allocate a a public IPv4 address from one of the available networks.
......@@ -78,6 +78,8 @@ def allocate_public_ip(userid, floating_ip=False, backend=None):
.filter(subnet__network__deleted=False)\
.filter(subnet__network__public=True)\
.filter(subnet__network__drained=False)
if networks is not None:
ip_pool_rows = ip_pool_rows.filter(subnet__network__in=networks)
if floating_ip:
ip_pool_rows = ip_pool_rows\
.filter(subnet__network__floating_ip_pool=True)
......@@ -99,10 +101,7 @@ def allocate_public_ip(userid, floating_ip=False, backend=None):
log_msg += " Backend: %s" % backend
log.error(log_msg)
exception_msg = "Can not allocate a %s IP address." % ip_type
if floating_ip:
raise faults.Conflict(exception_msg)
else:
raise faults.ServiceUnavailable(exception_msg)
raise faults.Conflict(exception_msg)
@transaction.commit_on_success
......@@ -131,6 +130,34 @@ def create_floating_ip(userid, network=None, address=None):
return floating_ip
def get_free_floating_ip(userid, network=None):
"""Get one of the free available floating IPs of the user.
Get one of the users floating IPs that is not connected to any port
or server. If network is specified, the floating IP must be from
that network.
"""
floating_ips = IPAddress.objects\
.filter(userid=userid, deleted=False, nic=None)
if network is not None:
floating_ips = floating_ips.filter(network=network)
for floating_ip in floating_ips:
floating_ip = IPAddress.objects.select_for_update()\
.get(id=floating_ip.id)
if floating_ip.nic is None:
return floating_ip
msg = "Cannot allocate a floating IP for connecting new server to"
if network is not None:
msg += " network '%s'." % network.id
else:
msg += " a public network."
msg += " Please create more floating IPs."
raise faults.Conflict(msg)