Commit 11c3e46a by Kálmán Viktor

dashboard: resize request and how-to initial

parent c85651ea
......@@ -25,7 +25,7 @@ from django.shortcuts import redirect
from circle.settings.base import get_env_variable
from dashboard.views import circle_login, HelpView
from dashboard.views import circle_login, HelpView, ResizeHelpView
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item
......@@ -65,6 +65,7 @@ urlpatterns = patterns(
url(r'^info/support/$',
TemplateView.as_view(template_name="info/support.html"),
name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(), name="info.resize"),
)
......
......@@ -4,24 +4,27 @@
<i class="fa fa-file"></i>
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if op.remove_disk %}
<span class="operation-wrapper">
<a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn
{% if op.remove_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %}
</a>
</span>
{% endif %}
{% if op.resize_disk %}
<span class="operation-wrapper">
<span class="operation-wrapper pull-right">
{% if op.resize_disk %}
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
class="btn btn-xs btn-{{ op.resize_disk.effect }} operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %}
</a>
</span>
{% endif %}
{% else %}
<a href="{% url "request.views.request-resize" vm_pk=instance.pk disk_pk=d.pk %}" class="btn btn-xs btn-primary operation">
<i class="fa fa-arrows-alt fa-fw-12"></i> {% trans "Request resize" %}
</a>
{% endif %}
{% if op.remove_disk %}
<a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.remove_disk.effect}} operation disk-remove-btn
{% if op.remove_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %}
</a>
{% endif %}
</span>
<div style="clear: both;"></div>
{% if request.user.is_superuser %}
......
......@@ -136,6 +136,10 @@ class HelpView(TemplateView):
return ctx
class ResizeHelpView(TemplateView):
template_name = "info/resize.html"
class OpenSearchDescriptionView(TemplateView):
template_name = "dashboard/vm-opensearch.xml"
content_type = "application/opensearchdescription+xml"
......
......@@ -22,6 +22,8 @@ from django.utils.translation import ugettext_lazy as _
from django.template import RequestContext
from django.template.loader import render_to_string
from sizefield.widgets import FileSizeWidget
from sizefield.utils import filesizeformat
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
......@@ -70,34 +72,32 @@ class InitialFromFileMixin(object):
RequestContext(request, {}),
)
def clean(self):
cleaned_data = super(InitialFromFileMixin, self).clean()
if cleaned_data['message'].strip() == self.initial['message'].strip():
raise ValidationError(
_("Fill in the message."),
code="invalid")
return cleaned_data
def clean_message(self):
message = self.cleaned_data['message']
if message.strip() == self.initial['message'].strip():
raise ValidationError(_("Fill in the message."), code="invalid")
return message.strip()
class TemplateRequestForm(InitialFromFileMixin, Form):
message = CharField(widget=Textarea, label=_("Message"))
template = ModelChoiceField(TemplateAccessType.objects.all(),
label=_("Template share"))
level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect,
initial=TemplateAccessAction.LEVELS.user)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/template.html"
class LeaseRequestForm(InitialFromFileMixin, Form):
lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease"))
message = CharField(widget=Textarea)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/lease.html"
class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
message = CharField(widget=Textarea)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/resources.html"
......@@ -110,3 +110,28 @@ class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
raise ValidationError(
_("You haven't changed any of the resources."),
code="invalid")
class ResizeRequestForm(InitialFromFileMixin, Form):
message = CharField(widget=Textarea, label=_("Message"))
size = CharField(widget=FileSizeWidget, label=_('Size'),
help_text=_('Size to resize the disk in bytes or with'
' units like MB or GB.'))
initial_template = "request/initials/resize.html"
def __init__(self, *args, **kwargs):
self.disk = kwargs.pop("disk")
super(ResizeRequestForm, self).__init__(*args, **kwargs)
def clean_size(self):
cleaned_data = super(ResizeRequestForm, self).clean()
disk = self.disk
size_in_bytes = cleaned_data.get("size")
if not size_in_bytes.isdigit() and len(size_in_bytes) > 0:
raise ValidationError(_("Invalid format, you can use GB or MB!"))
if int(size_in_bytes) < int(disk.size):
raise ValidationError(_("Disk size must be greater than the actual"
"size (%s).") % filesizeformat(disk.size))
return size_in_bytes
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import sizefield.models
class Migration(migrations.Migration):
dependencies = [
('vm', '0002_interface_model'),
('storage', '0002_disk_bus'),
('request', '0003_auto_20150410_1917'),
]
operations = [
migrations.CreateModel(
name='DiskResizeAction',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('size', sizefield.models.FileSizeField(default=None, null=True)),
('disk', models.ForeignKey(to='storage.Disk')),
('instance', models.ForeignKey(to='vm.Instance')),
],
options={
'abstract': False,
},
),
migrations.AlterField(
model_name='request',
name='type',
field=models.CharField(max_length=10, choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access request'), (b'resize', 'disk resize request')]),
),
]
......@@ -32,10 +32,12 @@ from django.utils.translation import (
from django.core.urlresolvers import reverse
import requests
from sizefield.models import FileSizeField
from model_utils.models import TimeStampedModel
from model_utils import Choices
from vm.models import Instance, InstanceTemplate, Lease
from storage.models import Disk
logger = logging.getLogger(__name__)
......@@ -77,6 +79,7 @@ class Request(TimeStampedModel):
('resource', _('resource request')),
('lease', _("lease request")),
('template', _("template access request")),
('resize', _("disk resize request")),
)
type = CharField(choices=TYPES, max_length=10)
message = TextField(verbose_name=_("Message"))
......@@ -99,7 +102,8 @@ class Request(TimeStampedModel):
return {
'resource': "tasks",
'lease': "clock-o",
'template': "puzzle-piece"
'template': "puzzle-piece",
'resize': "arrows-alt",
}.get(self.type)
def get_effect(self):
......@@ -246,6 +250,26 @@ class TemplateAccessAction(RequestAction):
) % ", ".join([x.name for x in self.template_type.templates.all()])
class DiskResizeAction(RequestAction):
instance = ForeignKey(Instance)
disk = ForeignKey(Disk)
size = FileSizeField(null=True, default=None)
def get_readable_level(self):
return self.LEVELS[self.level]
def accept(self, user):
pass
@property
def accept_msg(self):
return ungettext(
"You got access to the following template: %s",
"You got access to the following templates: %s",
self.template_type.templates.count()
) % ", ".join([x.name for x in self.template_type.templates.all()])
def send_notifications(sender, instance, created, **kwargs):
if not created:
return
......
{% load i18n %}
{% load crispy_forms_tags %}
{% load sizefieldtags %}
<dl>
<dt>{% trans "Virtual machine" %}</dt>
<dd><a href="{{ vm.get_absolute_url }}">{{ vm.name }}</a></dd>
<dt>{% trans "Disk" %}</dt>
<dd>
{% if request.user.is_superuser %}
<a href="{{ disk.get_absolute_url }}">{{ disk.name }} (#{{ disk.id }})</a>
{% else %}
{{ disk.name }} (#{{ disk.id }})
{% endif %}
- {{ disk.size|filesize }}
</dd>
</dl>
<form action="{% url "request.views.request-resize" vm_pk=vm.pk disk_pk=disk.pk %}" method="POST">
{% include "display-form-errors.html" %}
{% csrf_token %}
{{ form.size|as_crispy_field }}
{{ form.message|as_crispy_field }}
<input type="submit" class="btn btn-primary" id="op-form-send"/>
</form>
......@@ -3,6 +3,7 @@
{% load i18n %}
{% load render_table from django_tables2 %}
{% load arrowfilter %}
{% load sizefieldtags %}
{% block title-page %}{% trans "Request" %}{% endblock %}
......@@ -85,8 +86,29 @@
<dt>{% trans "Ram size" %}</dt>
<dd>{{ action.ram_size }} ({{ action.instance.ram_size }}) MiB</dd>
</dl>
{% elif object.type == "resize" %}
<dl>
<dt>{% trans "VM name" %}</dt>
<dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd>
<dt>{% trans "VM description" %}</dt>
<dd>{{ action.instance.description|default:"-" }}</dd>
<dt>{% trans "Disk" %}</dt>
<dd>
{% if request.user.is_superuser %}
<a href="{{ action.disk.get_absolute_url }}">
{{ action.disk.name }} (#{{ action.disk.id}})
</a>
{% else %}
{{ action.disk.name }} (#{{ action.disk.id}})</dd>
{% endif %}
</dd>
<dt>{% trans "Current size" %}</dt>
<dd>{{ action.disk.size|filesize}} ({{ action.disk.size }} bytes)</dd>
<dt>{% trans "Requested size" %}</dt>
<dd>{{ action.size|filesize}} ({{ action.size }} bytes)</dd>
</dl>
{% else %}
hacks!!!
Are you adding a new action type?
{% endif %}
{% if object.status == "PENDING" and request.user.is_superuser %}
......
{% spaceless %}
{% if LANGUAGE_CODE == "en" %}
Why do you need bigger disk?
{% else %} {# place your translations here #}
Why do you need bigger disk?
{% endif %}
{% endspaceless %}
......@@ -23,7 +23,7 @@ from .views import (
LeaseTypeCreate, LeaseTypeDetail,
TemplateAccessTypeCreate, TemplateAccessTypeDetail,
TemplateRequestView, LeaseRequestView, ResourceRequestView,
LeaseTypeDelete, TemplateAccessTypeDelete,
LeaseTypeDelete, TemplateAccessTypeDelete, ResizeRequestView,
)
urlpatterns = patterns(
......@@ -60,4 +60,6 @@ urlpatterns = patterns(
name="request.views.request-lease"),
url(r'resource/(?P<vm_pk>\d+)/$', ResourceRequestView.as_view(),
name="request.views.request-resource"),
url(r'resize/(?P<vm_pk>\d+)/(?P<disk_pk>\d+)/$',
ResizeRequestView.as_view(), name="request.views.request-resize"),
)
......@@ -30,8 +30,9 @@ from django_tables2 import SingleTableView
from request.models import (
Request, TemplateAccessType, LeaseType, TemplateAccessAction,
ExtendLeaseAction, ResourceChangeAction,
ExtendLeaseAction, ResourceChangeAction, DiskResizeAction
)
from storage.models import Disk
from vm.models import Instance
from vm.operations import ResourcesOperation
from request.tables import (
......@@ -39,7 +40,7 @@ from request.tables import (
)
from request.forms import (
LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm,
LeaseRequestForm, ResourceRequestForm,
LeaseRequestForm, ResourceRequestForm, ResizeRequestForm,
)
......@@ -288,3 +289,50 @@ class ResourceRequestView(VmRequestMixin, FormView):
req.save()
return redirect(vm.get_absolute_url())
class ResizeRequestView(VmRequestMixin, FormView):
form_class = ResizeRequestForm
template_name = "request/_request-resize-form.html"
user_level = "owner"
def get_disk(self, *args, **kwargs):
return get_object_or_404(Disk, pk=self.kwargs['disk_pk'])
def get_form_kwargs(self):
kwargs = super(ResizeRequestView, self).get_form_kwargs()
kwargs['disk'] = self.get_disk()
return kwargs
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_modal.html']
else:
return ['dashboard/_base.html']
def get_context_data(self, **kwargs):
context = super(ResizeRequestView, self).get_context_data(**kwargs)
context['disk'] = self.get_disk()
context['template'] = self.template_name
context['box_title'] = context['title'] = _("Disk resize request")
context['ajax_title'] = True
return context
def form_valid(self, form):
vm = self.get_vm()
disk = self.get_disk()
data = form.cleaned_data
user = self.request.user
dra = DiskResizeAction(instance=vm, disk=disk, size=data['size'])
dra.save()
req = Request(
user=user,
message=data['message'],
type=Request.TYPES.resize,
action=dra
)
req.save()
return redirect(vm.get_absolute_url())
......@@ -28,6 +28,7 @@ from celery.contrib.abortable import AbortableAsyncResult
from django.db.models import (Model, BooleanField, CharField, DateTimeField,
ForeignKey)
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from model_utils.models import TimeStampedModel
......@@ -535,3 +536,6 @@ class Disk(TimeStampedModel):
disk.is_ready = True
disk.save()
return disk
def get_absolute_url(self):
return reverse('dashboard.views.disk-detail', kwargs={'pk': self.pk})
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title-page %}{% trans "Resize how-to" %}{% endblock %}
{% block content %}
<div class="row" id="resize-help">
<div class="col-lg-12">
<style>
#resize-help table {
background-color: #f5f5f5;
}
#resize-help .panel {
padding: 2px 20px;
background-color: #f5f5f5;
margin: 20px 0px;
}
#resize-help ol li {
margin-top: 15px;
}
#resize-help img {
display: block;
margin: 15px 0 5px 0;
}
#resize-help pre {
margin-top: 5px;
}
</style>
<div class="page-header">
<h1 id="disk-linux">
<i class="fa fa-linux"></i>
{% trans "Expanding disk on Linux" %}
</h1>
</div>
<p>
{% blocktrans %}
If you don't have enogh space on your virtual machine you can ask for more.
After a request has been made an administrator can extend your HDD.
If the request is granted you have to manually rescan
and extend a logical volume on your machine to acquire the extra space.
To do so you need root access/administrator rights.
{% endblocktrans %}
</p>
<ol>
<li>
{% trans "Ask the administrator for more space. After it has been granted do the following steps." %}
</li>
<li>
{% blocktrans %}
You can check how much free space is left on your machine
(on Debian based distributions like Ubuntu) with the
<strong><code>df -h</code></strong> command.
As you can see below we need more space on
<strong>/</strong> so we will extend
<strong>/dev/mapper/cloud–x–vg-root</strong>.
{% endblocktrans %}
<div class="panel panel-default">
<table class="table">
<thead>
<tr>
<th>Filesystem</th>
<th>Size</th>
<th>Used</th>
<th>Avail</th>
<th>Use%</th>
<th>Mounted on</th>
</tr>
</thead>
<tbody><tr>
<td><strong>/dev/mapper/cloud–x–vg-root</strong></td>
<td>39G</td>
<td>37G</td>
<td>65M</td>
<td>100%</td>
<td><strong>/</strong></td>
</tr>
<tr>
<td>none</td>
<td>4.0K</td>
<td>0</td>
<td>4.0K</td>
<td>0%</td>
<td>/sys/fs/cgroup</td>
</tr>
<tr>
<td>udev</td>
<td>487M</td>
<td>4.0K</td>
<td>487M</td>
<td>1%</td>
<td>/dev</td>
</tr>
<tr>
<td>tmpfs</td>
<td>100M</td>
<td>368K</td>
<td>100M</td>
<td>1%</td>
<td>/run</td>
</tr>
<tr>
<td>none</td>
<td>5.0M</td>
<td>0</td>
<td>5.0M</td>
<td>0%</td>
<td>/run/lock</td>
</tr>
<tr>
<td>none</td>
<td>498M</td>
<td>0</td>
<td>498M</td>
<td>0%</td>
<td>/run/shm</td>
</tr>
<tr>
<td>none</td>
<td>100M</td>
<td>0</td>
<td>100M</td>
<td>0%</td>
<td>/run/user</td>
</tr>
<tr>
<td>/dev/vda1</td>
<td>236M</td>
<td>37M</td>
<td>187M</td>
<td>17%</td>
<td>/boot</td>
</tr>
</tbody>
</table>
</div>
</li>
<li>
{% blocktrans %}
List logical volumes and find the
<strong>VG Name</strong>
(volume group name) of
<strong>/dev/mapper/cloud–x–vg-root</strong>:
<code>lvdisplay</code></p>
{% endblocktrans %}
<pre>
— Logical volume —
<em>LV Path /dev/cloud-x-vg/root</em>
LV Name root
<strong>VG Name cloud-x-vg</strong>
LV UUID xlGizo-eVyj-aqRn-Us7d-BRzj-dsKW-U6kp0F
LV Write Access read/write
LV Creation host, time cloud-x, 2014-07-31 13:17:53 +0200
LV Status available
<code>#</code> open 1
LV Size 38.76 GiB
Current LE 9923
Segments 2
Allocation inherit
Read ahead sectors auto
<code>-</code> currently set to 256
Block device 252:0</pre>
</li>
<li>
{% blocktrans %}
List physical volumes to get the
<strong>PV Name</strong> (partition name) of the
<strong>cloud-x-vg</strong> volume group:
<code>pvdisplay</code>
{% endblocktrans %}
<pre>
— Physical volume —
<strong>PV Name /dev/vda5</strong>
<em>VG Name cloud-x-vg</em>
PV Size 39.76 GiB / not usable 2.00 MiB
Allocatable yes (but full)
PE Size 4.00 MiB
Total PE 10178
Free PE 0
Allocated PE 10178
PV UUID JDp5TP-PHjT-Cgwk-MN4h-iAnk-9dfT-lYoldd</pre>
</li>
<li>
{% blocktrans %}
List the partitions with fdisk:
<strong><code>fdisk /dev/vda</code></strong>
and press <strong>p</strong>.
This will show something similar:
{% endblocktrans %}
<div class="panel panel-default">
<table class="table">
<thead>
<tr>
<th>Device</th>
<th>Boot</th>
<th>Start</th>
<th>End</th>
<th>Blocks</th>
<th>Id</th>
<th>System</th>
</tr>
</thead>
<tbody><tr>
<td>/dev/vda1</td>
<td>*</td>
<td>2048</td>
<td>499711</td>
<td>248832</td>
<td>83</td>
<td>Linux</td>
</tr>
<tr>
<td>/dev/vda2</td>
<td></td>
<td>501758</td>
<td>83884031</td>
<td>41691137</td>
<td>5</td>
<td>Extended</td>
</tr>
<tr>
<td>/dev/vda5</td>
<td></td>
<td>501760</td>
<td>83884031</td>
<td>41691136</td>
<td>8e</td>
<td>Linux LVM</td>
</tr>
</tbody>
</table>
</div>
<p>
{% blocktrans %}
As you can see, the <strong>/dev/vda5</strong> is in the
<strong>/dev/vda2</strong> Extended partition.
To resize it we have to recreate the Extended partition.
{% endblocktrans %}
</p>
</li>
<li>
<p>{% trans "Delete the Extended partition:" %}</p>
<p>
{% blocktrans %}
Press <strong>d</strong> and the number of the partition.
In the example above the extended partition name is
<strong>vda2</strong> so press <strong>2</strong>.
{% endblocktrans %}
</p>
</li>
<li>
<p>{% trans "Create extended partition:" %}</p>
<p>
{% blocktrans %}
Press <strong>n</strong> to create new partition.
Type <strong>e</strong> to choose extended type.
Set partition number - the same as you deleted above:
<strong>2</strong>.
You can use the default starting and ending sector.
{% endblocktrans %}
</p>
</li>
<li>
<p>{% trans "Create logical partition:" %}</p>
<p>
{% blocktrans %}
Press <strong>n</strong> to create new partition.
Type <strong>l</strong> to choose logical type.
Set partition number - the same as the Linux LVM (vda5) has above: <strong>5</strong>.
You can use the default starting and ending sector.
{% endblocktrans %}
</p>
</li>
<li>
<p>{% trans "Change the logical partition’s type:" %}</p>
<p>
{% blocktrans %}
Press <strong>t</strong> to change type.
Set the logical partition’s number: <strong>5</strong> (vda5).
Type <strong>8e</strong> to choose Linux LVM type.
(to show the full list, press L).
{% endblocktrans %}
</p>
</li>
<li>
<p>{% trans "Save and exit: Press <strong>w</strong>." %}</p>
<p>{% trans "If you list the partitions again, you will see the difference:" %}</p>
<div class="panel panel-default">
<table class="table">
<thead>
<tr>
<th>Device</th>
<th>Boot</th>
<th>Start</th>
<th>End</th>
<th>Blocks</th>
<th>Id</th>
<th>System</th>
</tr>
</thead>
<tbody>
<tr>
<td>/dev/vda1</td>
<td>*</td>
<td>2048</td>
<td>499711</td>
<td>248832</td>
<td>83</td>
<td>Linux</td>
</tr>
<tr>