Commit 8f6b93ba by Bach Dániel

Merge branch 'feature-resize_disk' into 'master'

Feature Resize Disk

Resize disk image on a running virtual machine.
parents ed1b46ed dec67193
...@@ -790,6 +790,48 @@ class VmCreateDiskForm(forms.Form): ...@@ -790,6 +790,48 @@ class VmCreateDiskForm(forms.Form):
return helper return helper
class VmDiskResizeForm(forms.Form):
size = forms.CharField(
widget=FileSizeWidget, initial=(10 << 30), label=_('Size'),
help_text=_('Size to resize the disk in bytes or with units '
'like MB or GB.'))
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
self.disk = kwargs.pop('default')
super(VmDiskResizeForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'disk', forms.ModelChoiceField(
queryset=choices, initial=self.disk, required=True,
empty_label=None, label=_('Disk')))
if self.disk:
self.fields['disk'].widget = HiddenInput()
self.fields['size'].initial += self.disk.size
def clean(self):
cleaned_data = super(VmDiskResizeForm, self).clean()
size_in_bytes = self.cleaned_data.get("size")
disk = self.cleaned_data.get('disk')
if not size_in_bytes.isdigit() and len(size_in_bytes) > 0:
raise forms.ValidationError(_("Invalid format, you can use "
" GB or MB!"))
if int(size_in_bytes) < int(disk.size):
raise forms.ValidationError(_("Disk size must be greater than the "
"actual size."))
return cleaned_data
@property
def helper(self):
helper = FormHelper(self)
helper.form_tag = False
if self.disk:
helper.layout = Layout(
HTML(_("<label>Disk:</label> %s") % self.disk),
Field('disk'), Field('size'))
return helper
class VmDownloadDiskForm(forms.Form): class VmDownloadDiskForm(forms.Form):
name = forms.CharField(max_length=100, label=_("Name")) name = forms.CharField(max_length=100, label=_("Name"))
url = forms.CharField(label=_('URL'), validators=[URLValidator(), ]) url = forms.CharField(label=_('URL'), validators=[URLValidator(), ])
......
...@@ -11,11 +11,17 @@ ...@@ -11,11 +11,17 @@
{% endif %} {% endif %}
{% else %}<span class="disk-list-disk-percentage" data-disk-pk="{{ d.pk }}">{{ d.get_download_percentage }}</span>%{% endif %} {% else %}<span class="disk-list-disk-percentage" data-disk-pk="{{ d.pk }}">{{ d.get_download_percentage }}</span>%{% endif %}
{% if is_owner != False %} {% if is_owner != False %}
<a href="{% url "dashboard.views.disk-remove" pk=d.pk %}?next={{ request.path }}" <a href="{% url "dashboard.views.disk-remove" pk=d.pk %}?next={{ request.path }}"
data-disk-pk="{{ d.pk }}" class="btn btn-xs btn-danger pull-right disk-remove" data-disk-pk="{{ d.pk }}" class="btn btn-xs btn-danger pull-right disk-remove"
{% if not long_remove %}title="{% trans "Remove" %}"{% endif %} {% if not long_remove %}title="{% trans "Remove" %}"{% endif %}>
>
<i class="fa fa-times"></i>{% if long_remove %} {% trans "Remove" %}{% endif %} <i class="fa fa-times"></i>{% if long_remove %} {% trans "Remove" %}{% endif %}
</a> </a>
{% if op.resize_disk %}
<span class="operation-wrapper">
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}" class="btn btn-xs btn-warning pull-right operation">
<i class="fa fa-arrows-alt"></i> {% trans "Resize" %}
</a>
</span>
{% endif %}
{% endif %} {% endif %}
<div style="clear: both;"></div> <div style="clear: both;"></div>
...@@ -58,7 +58,7 @@ from ..forms import ( ...@@ -58,7 +58,7 @@ from ..forms import (
AclUserOrGroupAddForm, VmResourcesForm, TraitsForm, RawDataForm, AclUserOrGroupAddForm, VmResourcesForm, TraitsForm, RawDataForm,
VmAddInterfaceForm, VmCreateDiskForm, VmDownloadDiskForm, VmSaveForm, VmAddInterfaceForm, VmCreateDiskForm, VmDownloadDiskForm, VmSaveForm,
VmRenewForm, VmStateChangeForm, VmListSearchForm, VmCustomizeForm, VmRenewForm, VmStateChangeForm, VmListSearchForm, VmCustomizeForm,
TransferOwnershipForm, TransferOwnershipForm, VmDiskResizeForm,
) )
from ..models import Favourite, Profile from ..models import Favourite, Profile
...@@ -365,6 +365,30 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView): ...@@ -365,6 +365,30 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
return val return val
class VmDiskResizeView(FormOperationMixin, VmOperationView):
op = 'resize_disk'
form_class = VmDiskResizeForm
show_in_toolbar = False
icon = 'arrows-alt'
effect = "success"
def get_form_kwargs(self):
choices = self.get_op().instance.disks
disk_pk = self.request.GET.get('disk')
if disk_pk:
try:
default = choices.get(pk=disk_pk)
except (ValueError, Disk.DoesNotExist):
raise Http404()
else:
default = None
val = super(VmDiskResizeView, self).get_form_kwargs()
val.update({'choices': choices, 'default': default})
return val
class VmCreateDiskView(FormOperationMixin, VmOperationView): class VmCreateDiskView(FormOperationMixin, VmOperationView):
op = 'create_disk' op = 'create_disk'
...@@ -601,6 +625,7 @@ vm_ops = OrderedDict([ ...@@ -601,6 +625,7 @@ vm_ops = OrderedDict([
op='destroy', icon='times', effect='danger')), op='destroy', icon='times', effect='danger')),
('create_disk', VmCreateDiskView), ('create_disk', VmCreateDiskView),
('download_disk', VmDownloadDiskView), ('download_disk', VmDownloadDiskView),
('resize_disk', VmDiskResizeView),
('add_interface', VmAddInterfaceView), ('add_interface', VmAddInterfaceView),
('renew', VmRenewView), ('renew', VmRenewView),
('resources_change', VmResourcesChangeView), ('resources_change', VmResourcesChangeView),
......
...@@ -104,7 +104,9 @@ class Disk(TimeStampedModel): ...@@ -104,7 +104,9 @@ class Disk(TimeStampedModel):
verbose_name_plural = _('disks') verbose_name_plural = _('disks')
permissions = ( permissions = (
('create_empty_disk', _('Can create an empty disk.')), ('create_empty_disk', _('Can create an empty disk.')),
('download_disk', _('Can download a disk.'))) ('download_disk', _('Can download a disk.')),
('resize_disk', _('Can resize a disk.'))
)
class DiskError(HumanReadableException): class DiskError(HumanReadableException):
admin_message = None admin_message = None
......
...@@ -790,6 +790,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -790,6 +790,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
else: else:
raise raise
def resize_disk_live(self, disk, size, timeout=15):
queue_name = self.get_remote_queue_name('vm', 'slow')
return vm_tasks.resize_disk.apply_async(
args=[self.vm_name, disk.path, size],
queue=queue_name).get(timeout=timeout)
disk.size = size
disk.save()
def deploy_disks(self): def deploy_disks(self):
"""Deploy all associated disks. """Deploy all associated disks.
""" """
......
...@@ -205,6 +205,27 @@ class CreateDiskOperation(InstanceOperation): ...@@ -205,6 +205,27 @@ class CreateDiskOperation(InstanceOperation):
@register_operation @register_operation
class ResizeDiskOperation(InstanceOperation):
activity_code_suffix = 'resize_disk'
id = 'resize_disk'
name = _("resize disk")
description = _("Resize the virtual disk image. "
"Size must be greater value than the actual size.")
required_perms = ('storage.resize_disk', )
accept_states = ('RUNNING', )
async_queue = "localhost.man.slow"
def _operation(self, user, disk, size, activity):
self.instance.resize_disk_live(disk, size)
def get_activity_name(self, kwargs):
return create_readable(
ugettext_noop("resize disk %(name)s to %(size)s"),
size=filesizeformat(kwargs['size']), name=kwargs['disk'].name)
@register_operation
class DownloadDiskOperation(InstanceOperation): class DownloadDiskOperation(InstanceOperation):
activity_code_suffix = 'download_disk' activity_code_suffix = 'download_disk'
id = 'download_disk' id = 'download_disk'
......
...@@ -132,6 +132,11 @@ def migrate(params): ...@@ -132,6 +132,11 @@ def migrate(params):
pass pass
@celery.task(name='vmdriver.resize_disk')
def resize_disk(params):
pass
@celery.task(name='vmdriver.domain_info') @celery.task(name='vmdriver.domain_info')
def domain_info(params): def domain_info(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