Commit 4943dc30 authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

Add dirty bit to NetworkInterface

Extend NetworkInterface model with a dirty bit, indicating whether
the index of the NIC is consistent with the one in Ganeti. This flag is
set when deleting a NIC, and is switched off after processing the
notification from Ganeti. Removing a NIC with the dirty bit on is not
allowed.
parent 9534ce30
......@@ -39,7 +39,8 @@ from django.http import HttpResponse
from django.template.loader import render_to_string
from django.utils import simplejson as json
from synnefo.api.faults import BadRequest, ServiceUnavailable, ItemNotFound
from synnefo.api.faults import (BadRequest, ServiceUnavailable,
ItemNotFound, BuildInProgress)
from synnefo.api.util import random_password, get_vm, get_nic_from_index
from synnefo.db.models import NetworkInterface
from synnefo.logic import backend
......@@ -326,5 +327,11 @@ def remove(request, net, args):
raise BadRequest('Malformed Request.')
vm = get_vm(server_id, request.user_uniq)
nic = get_nic_from_index(vm, nic_index)
backend.disconnect_nic_from_vm(vm, nic)
if nic.dirty:
raise BuildInProgress('Machine is busy.')
else:
vm.nics.all().update(dirty=True)
backend.disconnect_from_network(vm, nic)
return HttpResponse(status=202)
......@@ -60,8 +60,11 @@ class BuildInProgress(Fault):
class OverLimit(Fault):
code = 413
class ServiceUnavailable(Fault):
code = 503
class BadMediaType(Fault):
code = 415
class NetworkInUse(Fault):
code = 421
class ServiceUnavailable(Fault):
code = 503
......@@ -44,7 +44,8 @@ from synnefo import settings
from synnefo.api import util
from synnefo.api.actions import network_actions
from synnefo.api.common import method_not_allowed
from synnefo.api.faults import BadRequest, OverLimit, Unauthorized
from synnefo.api.faults import (BadRequest, OverLimit,
Unauthorized, NetworkInUse)
from synnefo.db.models import Network, Pool, BridgePool, MacPrefixPool
from synnefo.logic import backend
......@@ -250,6 +251,9 @@ def delete_network(request, network_id):
if net.public:
raise Unauthorized('Can not delete the public network.')
if net.machines.all(): # Nics attached on network
raise NetworkInUse('Machines are connected to network.')
net.action = 'DESTROY'
net.save()
......
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'NetworkInterface.dirty'
db.add_column('db_networkinterface', 'dirty', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'NetworkInterface.dirty'
db.delete_column('db_networkinterface', 'dirty')
models = {
'db.backend': {
'Meta': {'object_name': 'Backend'},
'clustername': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
'ctotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'drained': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'dtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'mtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'offline': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'pinst_cnt': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5080'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'})
},
'db.backendnetwork': {
'Meta': {'object_name': 'BackendNetwork'},
'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'networks'", 'to': "orm['db.Backend']"}),
'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'backend_networks'", 'to': "orm['db.Network']"}),
'operstate': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '30'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'db.bridgepool': {
'Meta': {'object_name': 'BridgePool'},
'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
'value': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
},
'db.flavor': {
'Meta': {'unique_together': "(('cpu', 'ram', 'disk', 'disk_template'),)", 'object_name': 'Flavor'},
'cpu': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'disk_template': ('django.db.models.fields.CharField', [], {'default': "'drbd'", 'max_length': '32'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'db.macprefixpool': {
'Meta': {'object_name': 'MacPrefixPool'},
'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
'value': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
},
'db.network': {
'Meta': {'object_name': 'Network'},
'action': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '32', 'null': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'dhcp': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'gateway': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'link': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'mac_prefix': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'through': "orm['db.NetworkInterface']", 'symmetrical': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '32'}),
'subnet': ('django.db.models.fields.CharField', [], {'default': "'10.0.0.0/24'", 'max_length': '32'}),
'type': ('django.db.models.fields.CharField', [], {'default': "'PRIVATE_VLAN'", 'max_length': '50'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'userid': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'})
},
'db.networkinterface': {
'Meta': {'object_name': 'NetworkInterface'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'dirty': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'firewall_profile': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.IntegerField', [], {}),
'ipv4': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}),
'ipv6': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
'mac': ('django.db.models.fields.CharField', [], {'max_length': '17', 'null': 'True'}),
'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.VirtualMachine']"}),
'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.Network']"}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'db.virtualmachine': {
'Meta': {'object_name': 'VirtualMachine'},
'action': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'virtual_machines'", 'null': 'True', 'to': "orm['db.Backend']"}),
'backend_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
'buildpercentage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}),
'hostid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'imageid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'operstate': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'userid': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'db.virtualmachinemetadata': {
'Meta': {'unique_together': "(('meta_key', 'vm'),)", 'object_name': 'VirtualMachineMetadata'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
'vm': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'metadata'", 'to': "orm['db.VirtualMachine']"})
}
}
complete_apps = ['db']
......@@ -601,6 +601,7 @@ 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)
def __unicode__(self):
return '%s@%s' % (self.machine.name, self.network.name)
......
......@@ -135,7 +135,8 @@ def process_net_status(vm, etime, nics):
mac=nic.get('mac', ''),
ipv4=nic.get('ip', ''),
ipv6=nic.get('ipv6', ''),
firewall_profile=firewall_profile)
firewall_profile=firewall_profile,
dirty=False)
vm.backendtime = etime
vm.save()
......@@ -441,7 +442,7 @@ def delete_network(network, backends=None):
def disconnect_network(network, backends=None):
"""Disconnect a network from virtualmachines and nodegroups.
"""Disconnect a network from all nodegroups.
@param network: Network object
@param backends: List of Backend objects. None defaults to all.
......@@ -455,20 +456,15 @@ def disconnect_network(network, backends=None):
for backend in backends:
client = backend.client
jobs = []
for vm in network.machines.filter(backend=backend):
job = disconnect_from_network(vm, network)
jobs.append(job)
jobs2 = []
for group in client.GetGroups():
job = client.DisconnectNetwork(network.backend_id, group, jobs)
jobs2.append(job)
backend_jobs.append((backend, jobs2))
job = client.DisconnectNetwork(network.backend_id, group)
jobs.append(job)
backend_jobs.append((backend, jobs))
return backend_jobs
def disconnect_from_network(vm, network):
def disconnect_from_network(vm, nic):
"""Disconnect a virtual machine from a network by removing it's nic.
@param vm: VirtualMachine object
......@@ -476,21 +472,6 @@ def disconnect_from_network(vm, network):
"""
nics = vm.nics.filter(network__public=False).order_by('index')
ops = [('remove', nic.index, {}) for nic in nics if nic.network == network]
if not ops: # Vm not connected to network
return
job = vm.client.ModifyInstance(vm.backend_vm_id, nics=ops[::-1],
hotplug=True, dry_run=settings.TEST)
return job
def disconnect_nic_from_vm(vm, nic):
"""Remove a NetworkInterface from a VirtualMachine.
"""
op = [('remove', nic.index, {})]
return vm.client.ModifyInstance(vm.backend_vm_id, nics=op,
hotplug=True, dry_run=settings.TEST)
......
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