Commit 1a4633ef authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Use Ganeti NIC UUIDs

Since Ganeti 2.8, it is supported to refer to devices (NICs and Disks)
not only by their index but also by their name or UUID. This commit
updates Synnefo to refer to devices by their name. Synnefo will set the
name of the devices which will be a UUID. We do not use Ganeti's UUIDs
because the UUID can not be known, until the NIC is created in the VM.

Modify backend methods for connecting and removing a NIC from a VM, to
not refer to NICs by index, but use the NICs backend_uuid. Also, set the
name of the NIC to the backend_uuid, when creating a NIC.

API relevant methods, will create a NIC object in BUILDING state,
instead of creating the NIC when the message arrives from the backend.

Modify NetworkInterface model:
* remove 'dirty' attribute since it is no longer needed
* make index null, since when the NIC is in building state, the index
  can not be known.
* add property for getting the UUID of the NIC in the Ganeti backend.

Fix relevant tests.
parent 81ad065c
......@@ -154,7 +154,7 @@ def vm_to_dict(vm, detail=False):
metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
d['metadata'] = metadata
vm_nics = vm.nics.filter(state="ACTIVE").order_by("index")
vm_nics = vm.nics.filter(state="ACTIVE").order_by("id")
attachments = map(nic_to_dict, vm_nics)
d['attachments'] = attachments
d['addresses'] = attachments_to_addresses(attachments)
......@@ -515,7 +515,7 @@ def list_addresses_by_network(request, server_id, network_id):
log.debug('list_addresses_by_network %s %s', server_id, network_id)
machine = util.get_vm(server_id, request.user_uniq)
network = util.get_network(network_id, request.user_uniq)
nics = machine.nics.filter(network=network, state="ACTIVE").all()
nics = machine.nics.filter(network=network, state="ACTIVE")
addresses = attachments_to_addresses(map(nic_to_dict, nics))
if request.serialization == 'xml':
......@@ -871,15 +871,16 @@ def remove(request, net, args):
if attachment is None:
raise faults.BadRequest("Missing 'attachment' attribute.")
# attachment string: nic-<vm-id>-<nic-index>
_, server_id, nic_index = attachment.split("-", 2)
# attachment string: nic-<vm-id>-<nic-id>
_, server_id, nic_id = attachment.split("-", 2)
server_id = int(server_id)
nic_index = int(nic_index)
nic_id = int(nic_id)
except (ValueError, TypeError):
raise faults.BadRequest("Invalid 'attachment' attribute.")
vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
servers.disconnect(vm, nic_index=nic_index)
nic = util.get_nic(vm, nic_id)
servers.disconnect(vm, nic)
return HttpResponse(status=202)
......@@ -93,7 +93,7 @@ class NetworkAPITest(ComputeAPITest):
self.assertEqual(db_net.gateway6, api_net['gateway6'])
self.assertEqual(db_net.dhcp, api_net['dhcp'])
self.assertEqual(db_net.public, api_net['public'])
db_nics = ["nic-%d-%d" % (, nic.index) for nic in
db_nics = ["nic-%d-%d" % (, for nic in
self.assertEqual(db_nics, api_net['attachments'])
......@@ -407,18 +407,13 @@ class NetworkAPITest(ComputeAPITest):
net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net)
mrapi().ModifyInstance.return_value = 1
request = {'remove': {'attachment': 'nic-%s-%s' % (, nic.index)}}
request = {'remove': {'attachment': 'nic-%s-%s' % (,}}
response = self.mypost('networks/%d/action' %,
net.userid, json.dumps(request), 'json')
self.assertEqual(response.status_code, 202)
vm.task = None
vm.task_job_id = None
# Remove dirty nic
response = self.mypost('networks/%d/action' %,
net.userid, json.dumps(request), 'json')
self.assertFault(response, 409, 'buildInProgress')
def test_remove_nic_malformed(self, mrapi):
user = 'userr'
......@@ -426,7 +421,7 @@ class NetworkAPITest(ComputeAPITest):
net = mfactory.NetworkFactory(state='ACTIVE', userid=user)
nic = mfactory.NetworkInterfaceFactory(machine=vm, network=net)
request = {'remove':
{'att234achment': 'nic-%s-%s' % (, nic.index)}
{'att234achment': 'nic-%s-%s' % (,}
response = self.mypost('networks/%d/action' %,
net.userid, json.dumps(request), 'json')
......@@ -144,7 +144,7 @@ class ServerAPITest(ComputeAPITest):
self.assertEqual(api_nic['ipv4'], nic.ipv4)
self.assertEqual(api_nic['ipv6'], nic.ipv6)
self.assertEqual(api_nic['OS-EXT-IPS:type'], "fixed")
self.assertEqual(api_nic['id'], 'nic-%s-%s' % (, nic.index))
self.assertEqual(api_nic['id'], 'nic-%s-%s' % (,
api_address = server["addresses"]
self.assertEqual(api_address[str(], [
{"version": 4, "addr": nic.ipv4, "OS-EXT-IPS:type": "fixed"},
......@@ -285,27 +285,13 @@ def get_network_free_address(network):
return address
def get_nic(machine, network):
def get_nic(vm, nic_id):
return NetworkInterface.objects.get(machine=machine, network=network)
return vm.nics.get(id=nic_id)
except NetworkInterface.DoesNotExist:
raise faults.ItemNotFound('Server not connected to this network.')
def get_nic_from_index(vm, nic_index):
"""Returns the nic_index-th nic of a vm
Error Response Codes: itemNotFound (404), badMediaType (415)
matching_nics = vm.nics.filter(index=nic_index)
matching_nics_len = len(matching_nics)
if matching_nics_len < 1:
raise faults.ItemNotFound('NIC not found on VM')
elif matching_nics_len > 1:
raise faults.BadMediaType('NIC index conflict on VM')
nic = matching_nics[0]
return nic
def render_metadata(request, metadata, use_values=False, status=200):
if request.serialization == 'xml':
data = render_to_string('metadata.xml', {'metadata': metadata})
......@@ -328,7 +314,7 @@ def render_meta(request, meta, status=200):
def construct_nic_id(nic):
return "-".join(["nic", unicode(, unicode(nic.index)])
return "-".join(["nic", unicode(, unicode(])
def verify_personality(personality):
......@@ -374,7 +374,7 @@ class VirtualMachine(models.Model):
get_latest_by = 'created'
def __unicode__(self):
return "<vm: %s>" % str(
return u"<vm:%s@backend:%s>" % (, self.backend_id)
# Error classes
class InvalidBackendIdError(Exception):
......@@ -687,13 +687,17 @@ class NetworkInterface(models.Model):
ipv6 = models.CharField(max_length=100, null=True)
firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
max_length=30, null=True)
dirty = models.BooleanField(default=False)
state = models.CharField(max_length=32, null=False, default="ACTIVE",
def backend_uuid(self):
"""Return the backend id by prepending backend-prefix."""
return "%snic-%s" % (settings.BACKEND_PREFIX_ID, str(
def __unicode__(self):
return "<%s:vm:%s network:%s ipv4:%s ipv6:%s>" % \
(self.index, self.machine_id, self.network_id, self.ipv4,
(, self.machine_id, self.network_id, self.ipv4,
......@@ -239,7 +239,6 @@ def _process_net_status(vm, etime, nics):
if ipv4:
nic['dirty'] = False
# Dummy save the network, because UI uses changed-since for VMs
# and Networks in order to show the VM NICs
......@@ -512,7 +511,9 @@ def create_instance(vm, nics, flavor, image):
kw['disks'][0]['provider'] = provider
kw['disks'][0]['origin'] = flavor.disk_origin
kw['nics'] = [{"network":, "ip": nic.ipv4}
kw['nics'] = [{"name": nic.backend_uuid,
"ip": nic.ipv4}
for nic in nics]
backend = vm.backend
depend_jobs = []
......@@ -779,9 +780,11 @@ def connect_to_network(vm, nic):
depends = [[job, ["success", "error", "canceled"]] for job in depend_jobs]
nic = {'ip': nic.ipv4, 'network': network.backend_id}
nic = {'name': nic.backend_uuid,
'network': network.backend_id,
'ip': nic.ipv4}
log.debug("Connecting NIC %s to VM %s", nic, vm)
log.debug("Adding NIC %s to VM %s", nic, vm)
kwargs = {
"instance": vm.backend_vm_id,
......@@ -798,7 +801,7 @@ def connect_to_network(vm, nic):
def disconnect_from_network(vm, nic):
log.debug("Removing nic of VM %s, with index %s", vm, str(nic.index))
log.debug("Removing NIC %s of VM %s", nic, vm)
kwargs = {
"instance": vm.backend_vm_id,
......@@ -355,28 +355,16 @@ def connect(vm, network):
if network.subnet is not None and network.dhcp:
# Get a free IP from the address pool.
address = util.get_network_free_address(network)"Connecting VM %s to Network %s(%s)", vm, network, address)
nic = NetworkInterface.objects.create(machine=vm,
nic = NetworkInterface.objects.create(machine=vm, network=network,
ipv4=address, state="BUILDING")"Connecting VM %s to Network %s. NIC: %s", vm, network, nic)
return backend.connect_to_network(vm, nic)
def disconnect(vm, nic_index):
nic = util.get_nic_from_index(vm, nic_index)"Removing NIC %s from VM %s", str(nic.index), vm)
if nic.dirty:
raise faults.BuildInProgress('Machine is busy.')
def disconnect(vm, nic):"Removing NIC %s from VM %s", nic, vm)
return backend.disconnect_from_network(vm, nic)
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