Commit f19b455c by Őry Máté

Merge branch 'feature-hot-plug' into 'master'

Feature Hot Plug

Fixes #170, #171

 qcow2 image hot-attach/detach
🚫 CD-ROM hot attach/detach not possible in current libvirt/kvm
 NIC hot-attach/detach
parents fe84d9e6 f669d51e
...@@ -1395,6 +1395,7 @@ ...@@ -1395,6 +1395,7 @@
"raw_data": "", "raw_data": "",
"vnc_port": 1234, "vnc_port": 1234,
"num_cores": 2, "num_cores": 2,
"status": "RUNNING",
"modified": "2013-10-14T07:27:38.192Z" "modified": "2013-10-14T07:27:38.192Z"
} }
}, },
......
...@@ -199,6 +199,8 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -199,6 +199,8 @@ class VmDetailTest(LoginMixin, TestCase):
inst = Instance.objects.get(pk=1) inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner') inst.set_level(self.u1, 'owner')
inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us) inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us)
inst.status = 'RUNNING'
inst.save()
iface_count = inst.interface_set.count() iface_count = inst.interface_set.count()
c.post("/dashboard/interface/1/delete/") c.post("/dashboard/interface/1/delete/")
...@@ -211,6 +213,8 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -211,6 +213,8 @@ class VmDetailTest(LoginMixin, TestCase):
inst.set_level(self.u1, 'owner') inst.set_level(self.u1, 'owner')
vlan = Vlan.objects.get(pk=1) vlan = Vlan.objects.get(pk=1)
inst.add_interface(vlan=vlan, user=self.us) inst.add_interface(vlan=vlan, user=self.us)
inst.status = 'RUNNING'
inst.save()
iface_count = inst.interface_set.count() iface_count = inst.interface_set.count()
response = c.post("/dashboard/interface/1/delete/", response = c.post("/dashboard/interface/1/delete/",
......
...@@ -185,12 +185,24 @@ class Disk(AclBase, TimeStampedModel): ...@@ -185,12 +185,24 @@ class Disk(AclBase, TimeStampedModel):
return { return {
'qcow2-norm': 'vd', 'qcow2-norm': 'vd',
'qcow2-snap': 'vd', 'qcow2-snap': 'vd',
'iso': 'hd', 'iso': 'sd',
'raw-ro': 'vd', 'raw-ro': 'vd',
'raw-rw': 'vd', 'raw-rw': 'vd',
}[self.type] }[self.type]
@property @property
def device_bus(self):
"""Returns the proper device prefix for different types of images.
"""
return {
'qcow2-norm': 'virtio',
'qcow2-snap': 'virtio',
'iso': 'scsi',
'raw-ro': 'virtio',
'raw-rw': 'virtio',
}[self.type]
@property
def is_deletable(self): def is_deletable(self):
"""True if the associated file can be deleted. """True if the associated file can be deleted.
""" """
...@@ -251,6 +263,7 @@ class Disk(AclBase, TimeStampedModel): ...@@ -251,6 +263,7 @@ class Disk(AclBase, TimeStampedModel):
'driver_type': self.vm_format, 'driver_type': self.vm_format,
'driver_cache': 'none', 'driver_cache': 'none',
'target_device': self.device_type + self.dev_num, 'target_device': self.device_type + self.dev_num,
'target_bus': self.device_bus,
'disk_device': 'cdrom' if self.type == 'iso' else 'disk' 'disk_device': 'cdrom' if self.type == 'iso' else 'disk'
} }
......
...@@ -763,6 +763,52 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -763,6 +763,52 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
""" """
return scheduler.select_node(self, Node.objects.all()) return scheduler.select_node(self, Node.objects.all())
def attach_disk(self, disk, timeout=15):
queue_name = self.get_remote_queue_name('vm', 'fast')
return vm_tasks.attach_disk.apply_async(
args=[self.vm_name,
disk.get_vmdisk_desc()],
queue=queue_name
).get(timeout=timeout)
def detach_disk(self, disk, timeout=15):
try:
queue_name = self.get_remote_queue_name('vm', 'fast')
return vm_tasks.detach_disk.apply_async(
args=[self.vm_name,
disk.get_vmdisk_desc()],
queue=queue_name
).get(timeout=timeout)
except Exception as e:
if e.libvirtError and "not found" in str(e):
logger.debug("Disk %s was not found."
% disk.name)
else:
raise
def attach_network(self, network, timeout=15):
queue_name = self.get_remote_queue_name('vm', 'fast')
return vm_tasks.attach_network.apply_async(
args=[self.vm_name,
network.get_vmnetwork_desc()],
queue=queue_name
).get(timeout=timeout)
def detach_network(self, network, timeout=15):
try:
queue_name = self.get_remote_queue_name('vm', 'fast')
return vm_tasks.detach_network.apply_async(
args=[self.vm_name,
network.get_vmnetwork_desc()],
queue=queue_name
).get(timeout=timeout)
except Exception as e:
if e.libvirtError and "not found" in str(e):
logger.debug("Interface %s was not found."
% (network.__unicode__()))
else:
raise
def deploy_disks(self): def deploy_disks(self):
"""Deploy all associated disks. """Deploy all associated disks.
""" """
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from logging import getLogger from logging import getLogger
from re import search from re import search
from string import ascii_lowercase
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils import timezone from django.utils import timezone
...@@ -35,7 +36,6 @@ from .models import ( ...@@ -35,7 +36,6 @@ from .models import (
NodeActivity, NodeActivity,
) )
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -94,6 +94,11 @@ class AddInterfaceOperation(InstanceOperation): ...@@ -94,6 +94,11 @@ class AddInterfaceOperation(InstanceOperation):
"the VM.") "the VM.")
required_perms = () required_perms = ()
def check_precond(self):
super(AddInterfaceOperation, self).check_precond()
if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
raise self.instance.WrongStateError(self.instance)
def _operation(self, activity, user, system, vlan, managed=None): def _operation(self, activity, user, system, vlan, managed=None):
if managed is None: if managed is None:
managed = vlan.managed managed = vlan.managed
...@@ -102,6 +107,8 @@ class AddInterfaceOperation(InstanceOperation): ...@@ -102,6 +107,8 @@ class AddInterfaceOperation(InstanceOperation):
managed=managed, owner=user, vlan=vlan) managed=managed, owner=user, vlan=vlan)
if self.instance.is_running: if self.instance.is_running:
with activity.sub_activity('attach_network'):
self.instance.attach_network(net)
net.deploy() net.deploy()
return net return net
...@@ -115,6 +122,7 @@ register_operation(AddInterfaceOperation) ...@@ -115,6 +122,7 @@ register_operation(AddInterfaceOperation)
class CreateDiskOperation(InstanceOperation): class CreateDiskOperation(InstanceOperation):
activity_code_suffix = 'create_disk' activity_code_suffix = 'create_disk'
id = 'create_disk' id = 'create_disk'
name = _("create disk") name = _("create disk")
...@@ -123,20 +131,29 @@ class CreateDiskOperation(InstanceOperation): ...@@ -123,20 +131,29 @@ class CreateDiskOperation(InstanceOperation):
def check_precond(self): def check_precond(self):
super(CreateDiskOperation, self).check_precond() super(CreateDiskOperation, self).check_precond()
# TODO remove check when hot-attach is implemented if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
if self.instance.status not in ['STOPPED', 'PENDING']:
raise self.instance.WrongStateError(self.instance) raise self.instance.WrongStateError(self.instance)
def _operation(self, user, size, name=None): def _operation(self, user, size, activity, name=None):
# TODO implement with hot-attach when it'll be available
from storage.models import Disk from storage.models import Disk
if not name: if not name:
name = "new disk" name = "new disk"
disk = Disk.create(size=size, name=name, type="qcow2-norm") disk = Disk.create(size=size, name=name, type="qcow2-norm")
disk.full_clean() disk.full_clean()
devnums = list(ascii_lowercase)
for d in self.instance.disks.all():
devnums.remove(d.dev_num)
disk.dev_num = devnums.pop(0)
disk.save()
self.instance.disks.add(disk) self.instance.disks.add(disk)
if self.instance.is_running:
with activity.sub_activity('deploying_disk'):
disk.deploy()
with activity.sub_activity('attach_disk'):
self.instance.attach_disk(disk)
def get_activity_name(self, kwargs): def get_activity_name(self, kwargs):
return create_readable(ugettext_noop("create %(size)s disk"), return create_readable(ugettext_noop("create %(size)s disk"),
size=kwargs['size']) size=kwargs['size'])
...@@ -156,21 +173,29 @@ class DownloadDiskOperation(InstanceOperation): ...@@ -156,21 +173,29 @@ class DownloadDiskOperation(InstanceOperation):
def check_precond(self): def check_precond(self):
super(DownloadDiskOperation, self).check_precond() super(DownloadDiskOperation, self).check_precond()
# TODO remove check when hot-attach is implemented if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
if self.instance.status not in ['STOPPED', 'PENDING']:
raise self.instance.WrongStateError(self.instance) raise self.instance.WrongStateError(self.instance)
def _operation(self, user, url, task, activity, name=None): def _operation(self, user, url, task, activity, name=None):
activity.result = url activity.result = url
# TODO implement with hot-attach when it'll be available
from storage.models import Disk from storage.models import Disk
disk = Disk.download(url=url, name=name, task=task) disk = Disk.download(url=url, name=name, task=task)
devnums = list(ascii_lowercase)
for d in self.instance.disks.all():
devnums.remove(d.dev_num)
disk.dev_num = devnums.pop(0)
disk.full_clean() disk.full_clean()
disk.save()
self.instance.disks.add(disk) self.instance.disks.add(disk)
activity.readable_name = create_readable( activity.readable_name = create_readable(
ugettext_noop("download %(name)s"), name=disk.name) ugettext_noop("download %(name)s"), name=disk.name)
# TODO iso (cd) hot-plug is not supported by kvm/guests
if self.instance.is_running and disk.type not in ["iso"]:
with activity.sub_activity('attach_disk'):
self.instance.attach_disk(disk)
register_operation(DownloadDiskOperation) register_operation(DownloadDiskOperation)
...@@ -366,8 +391,15 @@ class RemoveInterfaceOperation(InstanceOperation): ...@@ -366,8 +391,15 @@ class RemoveInterfaceOperation(InstanceOperation):
description = _("Remove the specified network interface from the VM.") description = _("Remove the specified network interface from the VM.")
required_perms = () required_perms = ()
def check_precond(self):
super(RemoveInterfaceOperation, self).check_precond()
if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
raise self.instance.WrongStateError(self.instance)
def _operation(self, activity, user, system, interface): def _operation(self, activity, user, system, interface):
if self.instance.is_running: if self.instance.is_running:
with activity.sub_activity('detach_network'):
self.instance.detach_network(interface)
interface.shutdown() interface.shutdown()
interface.destroy() interface.destroy()
...@@ -386,12 +418,13 @@ class RemoveDiskOperation(InstanceOperation): ...@@ -386,12 +418,13 @@ class RemoveDiskOperation(InstanceOperation):
def check_precond(self): def check_precond(self):
super(RemoveDiskOperation, self).check_precond() super(RemoveDiskOperation, self).check_precond()
# TODO remove check when hot-detach is implemented if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
if self.instance.status not in ['STOPPED']:
raise self.instance.WrongStateError(self.instance) raise self.instance.WrongStateError(self.instance)
def _operation(self, activity, user, system, disk): def _operation(self, activity, user, system, disk):
# TODO implement with hot-detach when it'll be available if self.instance.is_running and disk.type not in ["iso"]:
with activity.sub_activity('detach_disk'):
self.instance.detach_disk(disk)
return self.instance.disks.remove(disk) return self.instance.disks.remove(disk)
......
...@@ -62,6 +62,26 @@ def get_queues(): ...@@ -62,6 +62,26 @@ def get_queues():
return result return result
@celery.task(name='vmdriver.attach_disk')
def attach_disk(vm, disk):
pass
@celery.task(name='vmdriver.detach_disk')
def detach_disk(vm, disk):
pass
@celery.task(name='vmdriver.attach_network')
def attach_network(vm, net):
pass
@celery.task(name='vmdriver.detach_network')
def detach_network(vm, net):
pass
@celery.task(name='vmdriver.create') @celery.task(name='vmdriver.create')
def deploy(params): def deploy(params):
pass pass
......
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 sign in to comment