Commit de54bc3b by Őry Máté

Merge branch 'issue-sliders' into 'master'

VM resources sliders

 new sliders from VM detail
 new sliders from VM customized create
  configurable max node ram
 server side max node ram check
 new sliders for template edit/create

https://miskolc.cloud.bme.hu:18525/dashboard/

Closes #183
Closes #137
parents 1ee2b455 7bdefb89
......@@ -458,3 +458,5 @@ STORE_URL = get_env_variable("STORE_URL", "")
SESSION_COOKIE_NAME = "csessid%x" % (((getnode() // 139) ^
(getnode() % 983)) & 0xffff)
MAX_NODE_RAM = get_env_variable("MAX_NODE_RAM", 1024)
......@@ -420,6 +420,7 @@ create_readable = HumanReadableObject.create
class HumanReadableException(HumanReadableObject, Exception):
"""HumanReadableObject that is an Exception so can used in except clause.
"""
def __init__(self, level=None, *args, **kwargs):
super(HumanReadableException, self).__init__(*args, **kwargs)
if level is not None:
......@@ -450,6 +451,7 @@ def humanize_exception(message, exception=None, level=None, **params):
...
Welcome!
"""
Ex = type("HumanReadable" + type(exception).__name__,
(HumanReadableException, type(exception)),
exception.__dict__)
......
......@@ -1382,7 +1382,7 @@
"pw": "ads",
"time_of_suspend": null,
"ram_size": 200,
"priority": 4,
"priority": 10,
"active_since": null,
"template": null,
"access_method": "nx",
......@@ -1412,7 +1412,7 @@
"pw": "ads",
"time_of_suspend": null,
"ram_size": 200,
"priority": 4,
"priority": 10,
"active_since": null,
"template": null,
"access_method": "nx",
......@@ -1518,7 +1518,7 @@
"ram_size": 1024,
"modified": "2014-01-24T00:58:19.654Z",
"system": "bubuntu",
"priority": 20,
"priority": 10,
"access_method": "ssh",
"raw_data": "",
"arch": "x86_64",
......
......@@ -30,7 +30,7 @@ from django.core.exceptions import PermissionDenied, ValidationError
import autocomplete_light
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK,
Layout, Div, BaseInput, Field, HTML, Submit, TEMPLATE_PACK,
)
from crispy_forms.utils import render_field
......@@ -51,7 +51,7 @@ from vm.models import (
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Permission
from .models import Profile, GroupProfile
from circle.settings.base import LANGUAGES
from circle.settings.base import LANGUAGES, MAX_NODE_RAM
from django.utils.translation import string_concat
from .virtvalidator import domain_validator
......@@ -59,6 +59,13 @@ from .virtvalidator import domain_validator
LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")"))
for l in LANGUAGES)
priority_choices = (
(10, _("idle")),
(30, _("normal")),
(80, _("server")),
(100, _("realtime")),
)
class VmSaveForm(forms.Form):
name = forms.CharField(max_length=100, label=_('Name'),
......@@ -72,19 +79,62 @@ class VmSaveForm(forms.Form):
class VmCustomizeForm(forms.Form):
name = forms.CharField()
cpu_priority = forms.IntegerField()
cpu_count = forms.IntegerField()
ram_size = forms.IntegerField()
amount = forms.IntegerField(min_value=0, initial=1)
name = forms.CharField(widget=forms.TextInput(attrs={
'class': "form-control",
'style': "max-width: 350px",
'required': "",
}))
cpu_count = forms.IntegerField(widget=forms.NumberInput(attrs={
'class': "form-control input-tags cpu-count-input",
'min': 1,
'max': 10,
'required': "",
}),
min_value=1, max_value=10,
)
ram_size = forms.IntegerField(widget=forms.TextInput(attrs={
'class': "form-control input-tags ram-input",
'min': 128,
'pattern': "\d+",
'max': MAX_NODE_RAM,
'step': 128,
'required': "",
}),
min_value=128, max_value=MAX_NODE_RAM,
)
cpu_priority = forms.ChoiceField(
priority_choices, widget=forms.Select(attrs={
'class': "form-control input-tags cpu-priority-input",
})
)
amount = forms.IntegerField(widget=forms.NumberInput(attrs={
'class': "form-control",
'min': "1",
'style': "max-width: 60px",
'required': "",
}), initial=1, min_value=1)
disks = forms.ModelMultipleChoiceField(
queryset=None, required=False)
queryset=None, required=False,
widget=forms.SelectMultiple(attrs={
'class': "form-control",
'id': "vm-create-disk-add-form",
})
)
networks = forms.ModelMultipleChoiceField(
queryset=None, required=False)
queryset=None, required=False,
widget=forms.SelectMultiple(attrs={
'class': "form-control",
'id': "vm-create-network-add-vlan",
})
)
template = forms.CharField()
customized = forms.CharField() # dummy flag field
template = forms.CharField(widget=forms.HiddenInput())
customized = forms.CharField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
......@@ -111,230 +161,6 @@ class VmCustomizeForm(forms.Form):
self.initial['template'] = self.template.pk
self.initial['customized'] = self.template.pk
# set widget for amount
self.fields['amount'].widget = NumberInput()
self.helper = FormHelper(self)
# don't show labels for the sliders
self.helper.form_show_labels = True
self.fields['cpu_count'].label = ""
self.fields['ram_size'].label = ""
self.fields['cpu_priority'].label = ""
self.helper.layout = Layout(
Field("template", type="hidden"),
Field("customized", type="hidden"),
Div(
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="fa fa-play"
),
HTML(" Start"),
css_id="vm-create-customized-start",
css_class="btn btn-success",
style="float: right; margin-top: 24px;",
),
Field("name", style="max-width: 350px;"),
css_class="col-sm-12",
),
css_class="row",
),
Div(
Div(
Field("amount", min="1", style="max-width: 60px;"),
css_class="col-sm-10",
),
css_class="row",
),
Div(
Div(
AnyTag(
'h2',
HTML(_("Resources")),
),
css_class="col-sm-12",
),
css_class="row",
),
Div( # cpu priority
Div(
HTML('<label for="vm-cpu-priority-slider">'
'<i class="fa fa-trophy"></i> CPU priority'
'</label>'),
css_class="col-sm-3"
),
Div(
Field('cpu_priority', id="vm-cpu-priority-slider",
css_class="vm-slider",
data_slider_min="0", data_slider_max="100",
data_slider_step="1",
data_slider_value=self.template.priority,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
),
css_class="row"
),
Div( # cpu count
Div(
HTML('<label for="cpu-count-slider">'
'<i class="fa fa-cogs"></i> CPU count'
'</label>'),
css_class="col-sm-3"
),
Div(
Field('cpu_count', id="vm-cpu-count-slider",
css_class="vm-slider",
data_slider_min="1", data_slider_max="8",
data_slider_step="1",
data_slider_value=self.template.num_cores,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
),
css_class="row"
),
Div( # ram size
Div(
HTML('<label for="ram-slider">'
'<i class="fa fa-ticket"></i> RAM amount'
'</label>'),
css_class="col-sm-3"
),
Div(
Field('ram_size', id="vm-ram-size-slider",
css_class="vm-slider",
data_slider_min="128", data_slider_max="4096",
data_slider_step="128",
data_slider_value=self.template.ram_size,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
),
css_class="row"
),
Div( # disks
Div(
AnyTag(
"h2",
HTML("Disks")
),
css_class="col-sm-4",
),
Div(
Div(
Field("disks", css_class="form-control",
id="vm-create-disk-add-form"),
css_class="js-hidden",
style="padding-top: 15px; max-width: 450px;",
),
Div(
AnyTag(
"h3",
HTML(_("No disks are added!")),
css_id="vm-create-disk-list",
),
Div(
HTML(""),
style="clear: both;",
),
# AnyTag(
# "h3",
# Div(
# AnyTag(
# "select",
# css_class="form-control",
# css_id="vm-create-disk-add-select",
# ),
# Div(
# AnyTag(
# "a",
# AnyTag(
# "i",
# css_class="icon-plus-sign",
# ),
# href="#",
# css_id="vm-create-disk-add-button",
# css_class="btn btn-success",
# ),
# css_class="input-group-btn"
# ),
# css_class="input-group",
# style="max-width: 330px;",
# ),
# css_id="vm-create-disk-add",
# ),
css_class="no-js-hidden",
),
css_class="col-sm-8",
style="padding-top: 3px;",
),
css_class="row",
), # end of disks
Div( # network
Div(
AnyTag(
"h2",
HTML(_("Network")),
),
css_class="col-sm-4",
),
Div(
Div( # js-hidden
Field(
"networks",
css_class="form-control",
id="vm-create-network-add-vlan",
),
css_class="js-hidden",
style="padding-top: 15px; max-width: 450px;",
),
Div( # no-js-hidden
AnyTag(
"h3",
HTML(_("Not added to any network!")),
css_id="vm-create-network-list",
),
AnyTag(
"h3",
Div(
AnyTag(
"select",
css_class=("form-control "
"font-awesome-font"),
css_id="vm-create-network-add-select",
),
Div(
AnyTag(
"a",
AnyTag(
"i",
css_class="fa fa-plus-circle",
),
css_id=("vm-create-network-add"
"-button"),
css_class="btn btn-success",
),
css_class="input-group-btn",
),
css_class="input-group",
style="max-width: 330px;",
),
css_class="vm-create-network-add"
),
css_class="no-js-hidden",
),
css_class="col-sm-8",
style="padding-top: 3px;",
),
css_class="row"
), # end of network
)
class GroupCreateForm(forms.ModelForm):
......@@ -581,6 +407,29 @@ class TemplateForm(forms.ModelForm):
networks = forms.ModelMultipleChoiceField(
queryset=None, required=False, label=_("Networks"))
num_cores = forms.IntegerField(widget=forms.NumberInput(attrs={
'class': "form-control input-tags cpu-count-input",
'min': 1,
'max': 10,
'required': "",
}),
min_value=1, max_value=10,
)
ram_size = forms.IntegerField(widget=forms.NumberInput(attrs={
'class': "form-control input-tags ram-input",
'min': 128,
'max': MAX_NODE_RAM,
'step': 128,
'required': "",
}),
min_value=128, max_value=MAX_NODE_RAM,
)
priority = forms.ChoiceField(priority_choices, widget=forms.Select(attrs={
'class': "form-control input-tags cpu-priority-input",
}))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
super(TemplateForm, self).__init__(*args, **kwargs)
......@@ -612,9 +461,10 @@ class TemplateForm(forms.ModelForm):
field.widget.attrs['disabled'] = 'disabled'
if not self.instance.pk and len(self.errors) < 1:
self.instance.priority = 20
self.instance.ram_size = 512
self.instance.num_cores = 2
self.initial['num_cores'] = 1
self.initial['priority'] = 10
self.initial['ram_size'] = 512
self.initial['max_ram_size'] = 512
lease_queryset = (
Lease.get_objects_with_level("operator", self.user).distinct()
......@@ -622,11 +472,16 @@ class TemplateForm(forms.ModelForm):
self.fields["lease"].queryset = lease_queryset
self.fields['raw_data'].validators.append(domain_validator)
def clean_owner(self):
if self.instance.pk is not None:
return User.objects.get(pk=self.instance.owner.pk)
return self.user
def clean_max_ram_size(self):
return self.cleaned_data.get("ram_size", 0)
def _clean_fields(self):
try:
old = InstanceTemplate.objects.get(pk=self.instance.pk)
......@@ -688,77 +543,14 @@ class TemplateForm(forms.ModelForm):
submit_kwargs['disabled'] = None
helper = FormHelper()
helper.layout = Layout(
Field("name"),
Fieldset(
_("Resource configuration"),
Div( # cpu count
Div(
Field('num_cores', id="vm-cpu-count-slider",
css_class="vm-slider",
data_slider_min="1", data_slider_max="8",
data_slider_step="1",
data_slider_value=self.instance.num_cores,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
),
css_class="row"
),
Div( # cpu priority
Div(
Field('priority', id="vm-cpu-priority-slider",
css_class="vm-slider",
data_slider_min="0", data_slider_max="100",
data_slider_step="1",
data_slider_value=self.instance.priority,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
),
css_class="row"
),
Div(
Div(
Field('ram_size', id="vm-ram-size-slider",
css_class="vm-slider",
data_slider_min="128", data_slider_max="4096",
data_slider_step="128",
data_slider_value=self.instance.ram_size,
data_slider_handle="square",
data_slider_tooltip="hide"),
css_class="col-sm-9"
),
css_class="row",
),
Field('max_ram_size', type="hidden", value="0"),
Field('arch'),
),
Fieldset(
_("Virtual machine settings"),
Field('access_method'),
Field('boot_menu'),
Field('raw_data'),
Field('req_traits'),
Field('description'),
Field("parent", type="hidden"),
Field("system"),
),
Fieldset(
_("External resources"),
Field("networks"),
Field("lease"),
Field("tags"),
),
)
helper.add_input(Submit('submit', 'Save changes', **submit_kwargs))
return helper
class Meta:
model = InstanceTemplate
exclude = ('state', 'disks', )
widgets = {
'system': forms.TextInput
'system': forms.TextInput,
'max_ram_size': forms.HiddenInput
}
......@@ -1306,3 +1098,32 @@ class GroupPermissionForm(forms.ModelForm):
helper.add_input(Submit("submit", _("Save"),
css_class="btn btn-success", ))
return helper
class VmResourcesForm(forms.ModelForm):
num_cores = forms.IntegerField(widget=forms.NumberInput(attrs={
'class': "form-control input-tags cpu-count-input",
'min': 1,
'max': 10,
'required': "",
}),
min_value=1, max_value=10,
)
ram_size = forms.IntegerField(widget=forms.NumberInput(attrs={
'class': "form-control input-tags ram-input",
'min': 128,
'max': MAX_NODE_RAM,
'step': 128,
'required': "",
}),
min_value=128, max_value=MAX_NODE_RAM,
)
priority = forms.ChoiceField(priority_choices, widget=forms.Select(attrs={
'class': "form-control input-tags cpu-priority-input",
}))
class Meta:
model = Instance
fields = ('num_cores', 'priority', 'ram_size', )
......@@ -186,42 +186,6 @@ html {
text-decoration: none !important;
}
.slider {
display: inline-block;
}
.slider .track {
height: 20px;
top: 50%;
}
.slider > .dragger, .slider > .dragger:hover {
border-radius: 0px;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
width: 8px;
height: 24px;
margin-top: -12px!important;
text-shadow: 0 1px 0 #fff;
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
background-repeat: repeat-x;
border-color: #2d6ca2;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
}
.slider > .dragger:hover {
background-color: #3071a9;
background-image: none;
border-color: #2d6ca2;
}
.slider > .highlight-track {
height: 20px;
top: 50%;
}
.slider + .output {
}
.rule-table tr >:nth-child(1) {
text-align: right;
}
......@@ -848,3 +812,54 @@ textarea[name="list-new-namelist"] {
#show-all-activities-container {
margin: 20px 0 0 10px;
}
#vm-details-resources-form {
margin-top: 15px;
}
#vm-details-resources-form .row, .resources-sliders .row {
margin-bottom: 15px;
}
#vm-create-disk-add-form {
max-width: 450px;
margin-top: 15px;
}
.vm-create-template {
max-width: 800px;
border: 1px solid black;
border-bottom: none;
}
.vm-create-template-list .vm-create-template:last-child {
border-bottom: 1px solid black;
}
.vm-create-template-summary {
padding: 15px;
cursor: pointer;
}
.vm-create-template:nth-child(odd) .vm-create-template-summary {
background: #F5F5F5;
}
.vm-create-template-list .vm-create-template-summary:hover {
background: #D2D2D2;
}
.vm-create-template-details {
border-top: 1px dashed #D3D3D3;
padding: 15px;
}
.vm-create-template-details ul {
list-style: none;
padding: 0 15px;
}
.vm-create-template-details li {
border-bottom: 1px dotted #aaa;
padding: 5px 0px;
}
......@@ -435,28 +435,63 @@ function compareVmByFav(a, b) {
return a.pk < b.pk ? -1 : 1;
}
$(document).on('shown.bs.tab', 'a[href="#resources"]', function (e) {
$(".cpu-priority-input").trigger("change");
$(".cpu-count-input, .ram-input").trigger("input");
})
function addSliderMiscs() {
$('.vm-slider').each(function() {
$("<span>").addClass("output").html($(this).val()).insertAfter($(this));
});
$('.vm-slider').slider()
.on('slide', function(e) {
$(this).val(e.value);
$(this).parent('div').nextAll("span").html(e.value)
// set max values based on inputs
var cpu_count_range = "0, " + $(".cpu-count-input").prop("max");
var ram_range = "0, " + $(".ram-input").prop("max");
$(".cpu-count-slider").data("slider-range", cpu_count_range);
$(".ram-slider").data("slider-range", ram_range);
$(".vm-slider").simpleSlider();
$(".cpu-priority-slider").bind("slider:changed", function (event, data) {
var value = data.value + 0;
$('.cpu-priority-input option[value="' + value + '"]').attr("selected", "selected");
});
refreshSliders();
}
$(".cpu-priority-input").change(function() {
var val = $(":selected", $(this)).val();
$(".cpu-priority-slider").simpleSlider("setValue", val);
});
$(".cpu-count-slider").bind("slider:changed", function (event, data) {
var value = data.value + 0;
$(".cpu-count-input").val(parseInt(value));
});
// ehhh
function refreshSliders() {
$('.vm-slider').each(function() {
$(this).val($(this).slider().data('slider').getValue());
$(this).parent('div').nextAll("span").html($(this).val());
$(".cpu-count-input").bind("input", function() {
var val = parseInt($(this).val());
if(!val) return;
$(".cpu-count-slider").simpleSlider("setValue", val);
});
var ram_fire = false;
$(".ram-slider").bind("slider:changed", function (event, data) {
if(ram_fire) {
ram_fire = false;
return;
}
var value = data.value + 0;
$(".ram-input").val(value);
});
$(".ram-input").bind("input", function() {
var val = $(this).val();
ram_fire = true;
$(".ram-slider").simpleSlider("setValue", parseInt(val));
});
$(".cpu-priority-input").trigger("change");
$(".cpu-count-input, .ram-input").trigger("input");
}
/* deletes the VM with the pk
* if dir is true, then redirect to the dashboard landing page
* else it adds a success message */
......