Commit 4c1443f2 by cloud

dashboard, storage, vm: add snapshot limit and minor fixes

parent 895d9a95
Pipeline #626 passed with stage
in 0 seconds
...@@ -1386,6 +1386,9 @@ class UserEditForm(forms.ModelForm): ...@@ -1386,6 +1386,9 @@ class UserEditForm(forms.ModelForm):
label=_('Two-factor authentication secret'), label=_('Two-factor authentication secret'),
help_text=_("Remove the secret key to disable two-factor " help_text=_("Remove the secret key to disable two-factor "
"authentication for this user."), required=False) "authentication for this user."), required=False)
disk_snapshot_limit = forms.IntegerField(
label=_('Snapshot limit per disk'),
min_value=0, widget=NumberInput)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(UserEditForm, self).__init__(*args, **kwargs) super(UserEditForm, self).__init__(*args, **kwargs)
...@@ -1393,11 +1396,13 @@ class UserEditForm(forms.ModelForm): ...@@ -1393,11 +1396,13 @@ class UserEditForm(forms.ModelForm):
self.instance.profile.instance_limit) self.instance.profile.instance_limit)
self.fields["two_factor_secret"].initial = ( self.fields["two_factor_secret"].initial = (
self.instance.profile.two_factor_secret) self.instance.profile.two_factor_secret)
self.fields["disk_snapshot_limit"].initial = (
self.instance.profile.disk_snapshot_limit)
class Meta: class Meta:
model = User model = User
fields = ('email', 'first_name', 'last_name', 'instance_limit', fields = ('email', 'first_name', 'last_name', 'instance_limit',
'is_active', "two_factor_secret", ) 'disk_snapshot_limit', 'is_active', "two_factor_secret", )
def save(self, commit=True): def save(self, commit=True):
user = super(UserEditForm, self).save() user = super(UserEditForm, self).save()
...@@ -1405,6 +1410,8 @@ class UserEditForm(forms.ModelForm): ...@@ -1405,6 +1410,8 @@ class UserEditForm(forms.ModelForm):
self.cleaned_data['instance_limit'] or None) self.cleaned_data['instance_limit'] or None)
user.profile.two_factor_secret = ( user.profile.two_factor_secret = (
self.cleaned_data['two_factor_secret'] or None) self.cleaned_data['two_factor_secret'] or None)
user.profile.disk_snapshot_limit = (
self.cleaned_data['disk_snapshot_limit'] or None)
user.profile.save() user.profile.save()
return user return user
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-12-11 21:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0006_auto_20170707_1909'),
]
operations = [
migrations.AddField(
model_name='profile',
name='disk_snapshot_limit',
field=models.IntegerField(default=3, help_text='Snapshot limit per disk.', verbose_name='disk snapshot limit'),
),
]
...@@ -200,6 +200,10 @@ class Profile(Model): ...@@ -200,6 +200,10 @@ class Profile(Model):
verbose_name=_('disk quota'), verbose_name=_('disk quota'),
default=2048 * 1024 * 1024, default=2048 * 1024 * 1024,
help_text=_('Disk quota in mebibytes.')) help_text=_('Disk quota in mebibytes.'))
disk_snapshot_limit = IntegerField(
verbose_name=_('disk snapshot limit'),
default=3,
help_text=_('Snapshot limit per disk.'))
two_factor_secret = CharField( two_factor_secret = CharField(
verbose_name=_("two factor secret key"), verbose_name=_("two factor secret key"),
max_length=32, null=True, blank=True, max_length=32, null=True, blank=True,
...@@ -332,7 +336,7 @@ def create_profile(user): ...@@ -332,7 +336,7 @@ def create_profile(user):
try: try:
Store(user).create_user(profile.smb_password, None, profile.disk_quota) Store(user).create_user(profile.smb_password, None, profile.disk_quota)
except: except Exception:
logger.exception("Can't create user %s", unicode(user)) logger.exception("Can't create user %s", unicode(user))
return created return created
......
...@@ -1536,16 +1536,10 @@ textarea[name="new_members"] { ...@@ -1536,16 +1536,10 @@ textarea[name="new_members"] {
background: gray; background: gray;
} }
.show-snapshot-btn {
margin-top: 10px;
}
.disk-create_snapshot-btn {
margin-right: 5px;
}
.snapshot-table { .snapshot-table {
background: white; background: white;
}
#two-factor-qr { #two-factor-qr {
text-align: center; text-align: center;
......
...@@ -248,7 +248,7 @@ class VmDetailView(GraphMixin, CheckedDetailView): ...@@ -248,7 +248,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
try: try:
messages.error(request, message) messages.error(request, message)
except: except Exception:
pass pass
return redirect(reverse_lazy("dashboard.views.detail", return redirect(reverse_lazy("dashboard.views.detail",
...@@ -263,7 +263,7 @@ class VmDetailView(GraphMixin, CheckedDetailView): ...@@ -263,7 +263,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
self.object.tags.remove(to_remove) self.object.tags.remove(to_remove)
message = u"Success" message = u"Success"
except: # note this won't really happen except Exception: # note this won't really happen
message = u"Not success" message = u"Not success"
if request.is_ajax(): if request.is_ajax():
...@@ -808,7 +808,8 @@ vm_ops = OrderedDict([ ...@@ -808,7 +808,8 @@ vm_ops = OrderedDict([
)), )),
('create_snapshot', VmDiskModifyView.factory( ('create_snapshot', VmDiskModifyView.factory(
op='create_snapshot', icon='camera', effect='success', op='create_snapshot', icon='camera', effect='success',
form_class=VmSnapshotDiskForm)), form_class=VmSnapshotDiskForm,
)),
('remove_snapshot', VmCommonSnapshotDiskView.factory( ('remove_snapshot', VmCommonSnapshotDiskView.factory(
op='remove_snapshot', icon='times', effect='danger')), op='remove_snapshot', icon='times', effect='danger')),
('revert_snapshot', VmCommonSnapshotDiskView.factory( ('revert_snapshot', VmCommonSnapshotDiskView.factory(
...@@ -1182,7 +1183,7 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -1182,7 +1183,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
else: else:
try: try:
amount = int(request.POST.get("amount", 1)) amount = int(request.POST.get("amount", 1))
except: except Exception:
amount = limit # TODO this should definitely use a Form amount = limit # TODO this should definitely use a Form
current = Instance.active.filter(owner=user).count() current = Instance.active.filter(owner=user).count()
logger.debug('current use: %d, limit: %d', current, limit) logger.debug('current use: %d, limit: %d', current, limit)
...@@ -1207,7 +1208,7 @@ def get_vm_screenshot(request, pk): ...@@ -1207,7 +1208,7 @@ def get_vm_screenshot(request, pk):
instance = get_object_or_404(Instance, pk=pk) instance = get_object_or_404(Instance, pk=pk)
try: try:
image = instance.screenshot(user=request.user).getvalue() image = instance.screenshot(user=request.user).getvalue()
except: except Exception:
# TODO handle this better # TODO handle this better
raise Http404() raise Http404()
......
...@@ -564,7 +564,7 @@ class Disk(TimeStampedModel): ...@@ -564,7 +564,7 @@ class Disk(TimeStampedModel):
disk.destroy() disk.destroy()
raise humanize_exception(ugettext_noop( raise humanize_exception(ugettext_noop(
"Operation aborted by user."), e) "Operation aborted by user."), e)
except: except Exception:
disk.destroy() disk.destroy()
raise raise
disk.is_ready = True disk.is_ready = True
...@@ -581,4 +581,4 @@ class Disk(TimeStampedModel): ...@@ -581,4 +581,4 @@ class Disk(TimeStampedModel):
@property @property
def is_read_only(self): def is_read_only(self):
return not self.type in ('qcow2-norm', 'raw-rw', ) return self.type not in ('qcow2-norm', 'raw-rw', )
...@@ -321,6 +321,16 @@ class CreateSnapshotDiskOperation(RemoteSnapshotDiskOperation): ...@@ -321,6 +321,16 @@ class CreateSnapshotDiskOperation(RemoteSnapshotDiskOperation):
self.disk.did_have_snapshot = True self.disk.did_have_snapshot = True
self.disk.save() self.disk.save()
def _operation(self, disk, **kwargs):
user = kwargs.get('user')
snap_num = len(disk.list_snapshots())
limit = user.profile.disk_snapshot_limit
if snap_num == limit:
raise humanize_exception(ugettext_noop(
"Snapshot limit (%(limit)d) exceeded."),
PermissionDenied(), limit=limit)
super(CreateSnapshotDiskOperation, self)._operation(disk, **kwargs)
def get_activity_name(self, kwargs): def get_activity_name(self, kwargs):
return create_readable( return create_readable(
ugettext_noop('Created snapshot %(snap_name)s' ugettext_noop('Created snapshot %(snap_name)s'
...@@ -478,7 +488,7 @@ class DeployOperation(InstanceOperation): ...@@ -478,7 +488,7 @@ class DeployOperation(InstanceOperation):
# Deploy virtual images # Deploy virtual images
try: try:
self.instance._deploy_disks(parent_activity=activity) self.instance._deploy_disks(parent_activity=activity)
except: except Exception:
self.instance.yield_node() self.instance.yield_node()
self.instance.yield_vnc_port() self.instance.yield_vnc_port()
raise raise
...@@ -495,7 +505,7 @@ class DeployOperation(InstanceOperation): ...@@ -495,7 +505,7 @@ class DeployOperation(InstanceOperation):
try: try:
self.instance.renew(parent_activity=activity) self.instance.renew(parent_activity=activity)
except: except Exception:
pass pass
self.instance._resume_vm(parent_activity=activity) self.instance._resume_vm(parent_activity=activity)
...@@ -580,7 +590,7 @@ class DestroyOperation(InstanceOperation): ...@@ -580,7 +590,7 @@ class DestroyOperation(InstanceOperation):
# Delete mem. dump if exists # Delete mem. dump if exists
try: try:
self.instance._delete_mem_dump(parent_activity=activity) self.instance._delete_mem_dump(parent_activity=activity)
except: except Exception:
pass pass
# Clear node and VNC port association # Clear node and VNC port association
...@@ -827,7 +837,7 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -827,7 +837,7 @@ class SaveAsTemplateOperation(InstanceOperation):
with_shutdown=True, clone=False, task=None, **kwargs): with_shutdown=True, clone=False, task=None, **kwargs):
try: try:
self.instance._cleanup(parent_activity=activity, user=user) self.instance._cleanup(parent_activity=activity, user=user)
except: except Exception:
pass pass
if with_shutdown: if with_shutdown:
...@@ -891,7 +901,7 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -891,7 +901,7 @@ class SaveAsTemplateOperation(InstanceOperation):
# create interface templates # create interface templates
for i in self.instance.interface_set.all(): for i in self.instance.interface_set.all():
i.save_as_template(tmpl) i.save_as_template(tmpl)
except: except Exception:
tmpl.delete() tmpl.delete()
raise raise
else: else:
...@@ -1046,7 +1056,7 @@ class WakeUpOperation(InstanceOperation): ...@@ -1046,7 +1056,7 @@ class WakeUpOperation(InstanceOperation):
try: try:
self.instance.renew(parent_activity=activity) self.instance.renew(parent_activity=activity)
except: except Exception:
pass pass
@register_operation @register_operation
...@@ -1446,7 +1456,7 @@ class RecoverOperation(InstanceOperation): ...@@ -1446,7 +1456,7 @@ class RecoverOperation(InstanceOperation):
try: try:
self.instance.renew(parent_activity=activity) self.instance.renew(parent_activity=activity)
except: except Exception:
pass pass
if self.instance.template: if self.instance.template:
...@@ -1636,7 +1646,7 @@ class AgentStartedOperation(InstanceOperation): ...@@ -1636,7 +1646,7 @@ class AgentStartedOperation(InstanceOperation):
if not self.initialized: if not self.initialized:
try: try:
self.measure_boot_time() self.measure_boot_time()
except: except Exception:
logger.exception('Unhandled error in measure_boot_time()') logger.exception('Unhandled error in measure_boot_time()')
self.instance._cleanup(parent_activity=activity) self.instance._cleanup(parent_activity=activity)
self.instance.password_reset( self.instance.password_reset(
......
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