Commit 4734e3cf authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Choose floating-ips when creating server

Extend POST /servers API call, to take an optional attribute
'floating_ips', which is a list of floating IP addresses. The server
will have one NIC for each of this addresses after the
'DEFAULT_INSTANCE_NETWORKS' and before any private networks that the
user has chosen. All of the addresses must first be reserved through the
/os-floating-ips API.
parent 084129f6
......@@ -40,6 +40,7 @@ from django.utils import simplejson as json
from snf_django.lib import api
from snf_django.lib.api import faults, utils
from synnefo.api import util
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata)
from synnefo.logic import servers, utils as logic_utils
......@@ -287,6 +288,8 @@ def create_server(request):
assert isinstance(personality, list)
private_networks = server.get("networks", [])
assert isinstance(private_networks, list)
floating_ips = server.get("floating_ips", [])
assert isinstance(floating_ips, list)
except (KeyError, AssertionError):
raise faults.BadRequest("Malformed request")
......@@ -301,7 +304,8 @@ def create_server(request):
vm = servers.create(user_id, name, password, flavor, image,
metadata=metadata, personality=personality,
server = vm_to_dict(vm, detail=True)
server['status'] = 'BUILD'
......@@ -36,7 +36,8 @@ from copy import deepcopy
from snf_django.utils.testing import (BaseAPITest, mocked_quotaholder,
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata,
from synnefo.db import models_factory as mfactory
from synnefo.logic.utils import get_rsapi_state
from synnefo.cyclades_settings import cyclades_services
......@@ -44,7 +45,7 @@ from import get_service_path
from synnefo.lib import join_urls
from synnefo import settings
from mock import patch, Mock, call
from mock import patch, Mock
class ComputeAPITest(BaseAPITest):
......@@ -333,6 +334,7 @@ class ServerCreateAPITest(ComputeAPITest):
request["server"]["floating_ips"] = []
with override_settings(settings,
with mocked_quotaholder():
......@@ -370,6 +372,43 @@ class ServerCreateAPITest(ComputeAPITest):
json.dumps(request), 'json')
# Test floating IPs
request = deepcopy(self.request)
request["server"]["networks"] = []
network = mfactory.NetworkFactory(subnet="")
fp1 = mfactory.FloatingIPFactory(ipv4="",
network=network, machine=None)
fp2 = mfactory.FloatingIPFactory(ipv4="", network=network,
request["server"]["floating_ips"] = [fp1.ipv4, fp2.ipv4]
with override_settings(settings,
with mocked_quotaholder():
response ='/api/v1.1/servers', 'test_user',
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 = FloatingIP.objects.get(
fp2 = FloatingIP.objects.get(
self.assertEqual(fp1.machine, vm)
self.assertEqual(fp2.machine, vm)
name, args, kwargs = mrapi().CreateInstance.mock_calls[2]
self.assertEqual(len(kwargs["nics"]), 4)
self.assertEqual(kwargs["nics"][1]["network"], network.backend_id)
self.assertEqual(kwargs["nics"][1]["ip"], fp1.ipv4)
self.assertEqual(kwargs["nics"][2]["network"], network.backend_id)
self.assertEqual(kwargs["nics"][2]["ip"], fp2.ipv4)
def test_create_server_no_flavor(self, mrapi):
request = deepcopy(self.request)
request["server"]["flavorRef"] = 42
......@@ -126,7 +126,7 @@ def server_command(action):
def create(userid, name, password, flavor, image, metadata={},
personality=[], network=None, private_networks=None,
floating_ips=None, use_backend=None):
if use_backend is None:
# Allocate backend to host the server. Commit after allocation to
# release the locks hold by the backend allocator.
......@@ -176,7 +176,7 @@ def create(userid, name, password, flavor, image, metadata={},
'img_properties': json.dumps(image['metadata']),
nics = create_instance_nics(vm, userid, private_networks)
nics = create_instance_nics(vm, userid, private_networks, floating_ips)
# Also we must create the VM metadata in the same transaction.
for key, val in metadata.items():
......@@ -222,7 +222,7 @@ def create(userid, name, password, flavor, image, metadata={},
return vm
def create_instance_nics(vm, userid, private_networks):
def create_instance_nics(vm, userid, private_networks=[], floating_ips=[]):
"""Create NICs for VirtualMachine.
Helper function for allocating IP addresses and creating NICs in the DB
......@@ -248,6 +248,12 @@ def create_instance_nics(vm, userid, private_networks):
if network.dhcp:
address = util.get_network_free_address(network)
attachments.append((network, address))
for address in floating_ips:
floating_ip = add_floating_ip_to_vm(address=address,
network =
attachments.append((network, address))
for network_id in private_networks:
network, address = None, None
network = util.get_network(network_id, userid, non_deleted=True)
......@@ -419,9 +425,24 @@ def console(vm, console_type):
def add_floating_ip(vm, address):
floating_ip = add_floating_ip_to_vm(vm, address)"Connecting VM %s to floating IP %s", vm, floating_ip)
return backend.connect_to_network(vm,, address)
def add_floating_ip_to_vm(vm, address):
"""Get a floating IP by it's address and add it to VirtualMachine.
Helper function for looking up a FloatingIP by it's address and associating
it with a VirtualMachine object (without adding the NIC in the Ganeti
backend!). This function also checks if the floating IP is currently used
by any instance and if it is available in the Backend that hosts the VM.
user_id = vm.userid
# Get lock in VM, to guarantee that floating IP will only by assigned once
# Get lock in VM, to guarantee that floating IP will only by assigned
# once
floating_ip = FloatingIP.objects.select_for_update()\
.get(userid=user_id, ipv4=address,
......@@ -440,9 +461,7 @@ def add_floating_ip(vm, address):
floating_ip.machine = vm"Connecting VM %s to floating IP %s", vm, floating_ip)
return backend.connect_to_network(vm,, address)
return floating_ip
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