Commit 07fb9e65 by Őry Máté

Merge branch 'master' into feature-activity-state

parents 71932249 f17bddde
......@@ -382,3 +382,7 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
'DJANGO_SAML_ATTRIBUTE_MAPPING',
'{"mail": ["email"], "sn": ["last_name"], '
'"uid": ["username"], "cn": ["first_name"]}'))
SAML_CREATE_UNKNOWN_USER = True
if get_env_variable('DJANGO_SAML_ORG_ID_ATTRIBUTE', False) != False:
SAML_ORG_ID_ATTRIBUTE = get_env_variable(
'DJANGO_SAML_ORG_ID_ATTRIBUTE')
......@@ -1467,7 +1467,8 @@
"raw_data": "",
"arch": "x86_64",
"max_ram_size": 1024,
"lease": 1
"lease": 1,
"owner": 1
}
}
]
from datetime import timedelta
import uuid
from django.contrib.auth.models import User
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK
......@@ -430,8 +432,16 @@ class TemplateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
parent = kwargs.pop("parent", None)
self.user = kwargs.pop("user", None)
super(TemplateForm, self).__init__(*args, **kwargs)
self.fields['disks'] = forms.ModelMultipleChoiceField(queryset=DISKS)
self.fields['disks'] = forms.ModelMultipleChoiceField(
queryset=Disk.get_objects_with_level(
'user', self.user).exclude(type="qcow2-snap")
)
data = self.data.copy()
data['owner'] = self.user.pk
self.data = data
if parent is not None:
template = InstanceTemplate.objects.get(pk=parent)
......@@ -458,6 +468,11 @@ class TemplateForm(forms.ModelForm):
self.instance.ram_size = 512
self.instance.num_cores = 2
def clean_owner(self):
if self.instance.pk is not None:
return User.objects.get(pk=self.instance.owner.pk)
return self.user
def save(self, commit=True):
data = self.cleaned_data
self.instance.max_ram_size = data.get('ram_size')
......
from logging import getLogger
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models import Model, ForeignKey
from django.contrib.auth.signals import user_logged_in
from django.db.models import (
Model, ForeignKey, OneToOneField, CharField, IntegerField
)
from django.utils.translation import ugettext_lazy as _
from vm.models import Instance
logger = getLogger(__name__)
class Favourite(Model):
instance = ForeignKey(Instance)
user = ForeignKey(User)
class Profile(Model):
user = OneToOneField(User)
preferred_language = CharField(verbose_name=_('preferred language'),
choices=settings.LANGUAGES,
max_length=32,
default=settings.LANGUAGE_CODE, blank=False)
org_id = CharField( # may be populated from eduPersonOrgId field
unique=True, blank=True, null=True, max_length=64,
help_text=_('Unique identifier of the person, e.g. a student number.'))
instance_limit = IntegerField(default=5)
def create_profile(sender, user, request, **kwargs):
if not user.pk:
return False
profile, created = Profile.objects.get_or_create(user=user)
return created
user_logged_in.connect(create_profile)
if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
logger.debug("Register save_org_id to djangosaml2 pre_user_save")
from djangosaml2.signals import pre_user_save
def save_org_id(sender, **kwargs):
logger.debug("save_org_id called by %s", sender.username)
attributes = kwargs.pop('attributes')
atr = settings.SAML_ORG_ID_ATTRIBUTE
try:
value = attributes[atr][0]
except Exception as e:
value = None
logger.info("save_org_id couldn't find attribute. %s", unicode(e))
if sender.pk is None:
sender.save()
logger.debug("save_org_id saved user %s", unicode(sender))
profile, created = Profile.objects.get_or_create(user=sender)
if created or profile.org_id != value:
logger.info("org_id of %s added to user %s's profile",
value, sender.username)
profile.org_id = value
profile.save()
else:
logger.debug("org_id of %s already added to user %s's profile",
value, sender.username)
return False
pre_user_save.connect(save_org_id)
else:
logger.debug("Do not register save_org_id to djangosaml2 pre_user_save")
$(function() {
if($('.timeline .activity:first i:first').hasClass('icon-spin'))
checkNewActivity();
/* save resources */
$('#vm-details-resources-save').click(function() {
$('i.icon-save', this).removeClass("icon-save").addClass("icon-refresh icon-spin");
$.ajax({
type: 'POST',
url: location.href,
data: $('#vm-details-resources-form').serialize(),
success: function(data, textStatus, xhr) {
addMessage(data['message'], 'success');
$("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save");
},
error: function(xhr, textStatus, error) {
$("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save");
addMessage("Eww, something is wrong", 'danger');
if (xhr.status == 500) {
// alert("uhuhuhuhuhuh");
} else {
// alert("unknown error");
}
}
});
return false;
});
/* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
});
/* rename ajax */
$('#vm-details-rename-submit').click(function() {
var name = $('#vm-details-rename-name').val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#vm-details-h1-name").html(data['new_name']).show();
$('#vm-details-rename').hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
}
});
return false;
});
/* remove tag */
$('.vm-details-remove-tag').click(function() {
var to_remove = $.trim($(this).parent('div').text());
var clicked = $(this);
$.ajax({
type: 'POST',
url: location.href,
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re['message'].toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re['message'], 'danger');
}
});
return false;
});
});
function checkNewActivity() {
var latest = $('.activity:first').data('activity-id');
var latest_sub = $('div[data-activity-id="' + latest + '"] .sub-timeline .sub-activity:first').data('activity-id');
var instance = location.href.split('/'); instance = instance[instance.length - 2];
$.ajax({
type: 'POST',
url: '/dashboard/vm/' + instance + '/activity/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'latest': latest, 'latest_sub': latest_sub},
success: function(data) {
if(data['new_sub_activities'].length > 0) {
d = data['new_sub_activities'];
html = ""
for(var i=0; i<d.length; i++) {
html += '<div data-activity-id="' + d[i].id + '" class="sub-activity">' + d[i].name + ' - ';
if(d[i].finished != null) {
html += d[i].finished
} else {
html += '<i class="icon-refresh icon-spin" class="sub-activity-loading-icon"></i>';
}
html += '</div>';
}
$('div[data-activity-id="' + latest_sub + '"] .sub-activity .sub-activity-loading-icon').remove();
$('div[data-activity-id="' + latest + '"] .sub-timeline').prepend(html);
}
if(data['is_parent_finished']) {
var c = "icon-plus"
$('div[data-activity-id="' + latest + '"] .icon-refresh.icon-spin:first').removeClass('icon-refresh').removeClass('icon-spin').addClass(c);
}
if(data['latest_sub_finished'] != null) {
s = $('div[data-activity-id="' + latest_sub + '"]')
$('.icon-refresh.icon-spin', s).remove();
$(s).append(data['latest_sub_finished']);
}
if(data['is_parent_finished'])
return;
else
setTimeout(checkNewActivity, 1000);
},
error: function() {
}
});
}
......@@ -126,7 +126,7 @@ $(function() {
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
addMessage("Error during renaming!", "danger");
}
});
return false;
......@@ -191,7 +191,7 @@ $(function() {
onsuccess(params);
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
addMessage("Error!", "danger");
}
});
return false;
......
......@@ -202,10 +202,12 @@ function vmCreateLoaded() {
}
},
error: function(xhr, textStatus, error) {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
if (xhr.status == 500) {
alert("uhuhuhuhuhuh");
addMessage("500 Internal Server Error", "danger");
} else {
alert("unknown error");
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
......
......@@ -15,12 +15,11 @@ $(function() {
},
error: function(xhr, textStatus, error) {
$("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save");
addMessage("Eww, something is wrong", 'danger');
if (xhr.status == 500) {
// alert("uhuhuhuhuhuh");
addMessage("500 Internal Server Error", "danger");
} else {
// alert("unknown error");
}
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
return false;
......@@ -47,7 +46,7 @@ $(function() {
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
addMessage("Error during renaming!", "danger");
}
});
return false;
......
......@@ -118,7 +118,7 @@ $(function() {
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
addMessage("Error during renaming!", "danger");
}
});
return false;
......
......@@ -4,11 +4,11 @@
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-heading">
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.views.template-list" %}">Back</a>
<h3 class="no-margin"><i class="icon-desktop"></i> Edit template</h3>
<h3 class="no-margin"><i class="icon-desktop"></i> {% trans "Edit template" %}</h3>
</div>
<div class="panel-body">
{% with form=form %}
......@@ -18,8 +18,56 @@
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="icon-group"></i> {% trans "Manage access" %}</h3>
</div>
<div class="panel-body">
<form action="{% url "dashboard.views.template-acl" pk=object.pk %}" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields">
<thead><tr><th></th><th>{% trans "Who" %}</th><th>{% trans "What" %}</th><th></th></tr></thead>
<tbody>
{% for i in acl.users %}
<tr><td><i class="icon-user"></i></td><td>{{i.user}}</td>
<td><select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select></td>
<td><a href="#" class="btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
{% endfor %}
{% for i in acl.groups %}
<tr><td><i class="icon-group"></i></td><td>{{i.group}}</td>
<td><select class="form-control" name="perm-g-{{i.group.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select></td>
<td><a href="#" class="btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
{% endfor %}
<tr><td><i class="icon-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
<style>
fieldset {
margin-top: 40px;
......
......@@ -13,7 +13,11 @@
<a title="Start" href="#" class="btn btn-default btn-xs"><i class="icon-play"></i></a>
<a title="Wake up" href="#" class="btn btn-default btn-xs"><i class="icon-sun"></i></a>
{% endif %}
<a title="Shut down" href="#" class="btn btn-default btn-xs"><i class="icon-off"></i></a>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
<input type="hidden" name="shut_down" value="dummy"/>
<button title="Shut down" class="btn btn-default btn-xs" type="submit"><i class="icon-off"></i></button>
</form>
<a title="Migrate" href="#" class="btn btn-default btn-xs"><i class="icon-truck"></i></a>
<form style="display: inline;" method="POST" action="{% url "dashboard.views.detail" pk=instance.pk %}">
{% csrf_token %}
......
......@@ -15,7 +15,7 @@
<i class="{% if not a.finished %} icon-refresh icon-spin {% else %}icon-plus{% endif %}"></i>
</span>
<strong>{{ a.get_readable_name }}</strong>
{{ a.started|date:"Y-m-d. H:i" }}, {{ a.user }}
{{ a.started|date:"Y-m-d H:i" }}, {{ a.user }}
{% if a.children.count > 0 %}
<div class="sub-timeline">
{% for s in a.children.all %}
......@@ -32,12 +32,10 @@
{% endif %}
</div>
{% endfor %}
<div><span class="timeline-icon timeline-warning"><i class="icon-remove"></i></span> <strong>Removing</strong> 2013-11-21 15:32</div>
<div><span class="timeline-icon timeline-warning"><i class="icon-pause"></i></span> <strong>Suspending</strong> 2013-09-21 15:32</div>
<div><span class="timeline-icon"><i class="icon-ellipsis-vertical" ></i></span> <strong>(now)</strong></div>
<div><span class="timeline-icon"><i class="icon-truck"></i></span> <strong>Migrated to mega5</strong> 2013-04-21 15:32, ABC123</div>
<div><span class="timeline-icon"><i class="icon-refresh"></i></span> <strong>Forced reboot</strong> 2013-04-21 15:32, ABC123</div>
<div><span class="timeline-icon"><i class="icon-plus"></i></span> <strong>Created</strong> 2013-04-21 15:32, ABC123</div>
<div>
<span class="timeline-icon"><i class="icon-plus"></i></span> <strong>{% trans "Created" %}</strong>
{{ instance.created|date:"Y-m-d H:i" }}, {{ instance.owner }}
</div>
</div>
{% block extra_js %}
......
......@@ -3,9 +3,9 @@
<div class="col-md-4">
<dl>
<dt>System:</dt>
<dd><i class="icon-linux"></i> Uhu Binux Optikai Rendszer</dd>
<dt>Description:</dt>
<dd><small>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc est libero, hendrerit at posuere sed, molestie congue quam. </small></dd>
<dd><i class="icon-{{ os_type_icon }}"></i> {{ instance.system }}</dd>
<dt style="margin-top: 5px;">Description:</dt>
<dd><small>{{ instance.description }}</small></dd>
</dl>
<div style="font-weight: bold;">{% trans "Tags" %}</div>
......
......@@ -7,7 +7,7 @@ from .views import (
TransferOwnershipView, TransferOwnershipConfirmView, NodeDelete,
TemplateList, LeaseDetail, NodeCreate, LeaseCreate, TemplateCreate,
FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete,
VmGraphView,
VmGraphView, TemplateAclUpdateView
)
urlpatterns = patterns(
......@@ -19,14 +19,18 @@ urlpatterns = patterns(
name="dashboard.views.lease-create"),
url(r'^lease/delete/(?P<pk>\d+)/$', LeaseDelete.as_view(),
name="dashboard.views.lease-delete"),
url(r'^template/create/$', TemplateCreate.as_view(),
name="dashboard.views.template-create"),
url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(),
name='dashboard.views.template-acl'),
url(r'^template/(?P<pk>\d+)/$', TemplateDetail.as_view(),
name='dashboard.views.template-detail'),
url(r"^template/list/$", TemplateList.as_view(),
name="dashboard.views.template-list"),
url(r"^template/delete/(?P<pk>\d+)/$", TemplateDelete.as_view(),
name="dashboard.views.template-delete"),
url(r'^vm/(?P<pk>\d+)/remove_port/(?P<rule>\d+)/$', PortDelete.as_view(),
name='dashboard.views.remove-port'),
url(r'^vm/(?P<pk>\d+)/$', VmDetailView.as_view(),
......@@ -43,6 +47,7 @@ urlpatterns = patterns(
url(r'^vm/mass-delete/', VmMassDelete.as_view(),
name='dashboard.view.mass-delete-vm'),
url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity),
url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'),
url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(),
name='dashboard.views.node-detail'),
......@@ -54,8 +59,10 @@ urlpatterns = patterns(
name="dashboard.views.status-node"),
url(r'^node/create/$', NodeCreate.as_view(),
name='dashboard.views.node-create'),
url(r'^favourite/$', FavouriteView.as_view(),
name='dashboard.views.favourite'),
url(r'^group/list/$', GroupList.as_view(),
name='dashboard.views.group-list'),
url((r'^vm/(?P<pk>\d+)/graph/(?P<metric>cpu|memory|network)/'
......
......@@ -157,6 +157,8 @@ class VmDetailView(CheckedDetailView):
context['forms'] = {
'disk_add_form': DiskAddForm(prefix="disk"),
}
context['os_type_icon'] = instance.os_type.replace("unknown",
"question")
return context
def post(self, request, *args, **kwargs):
......@@ -173,6 +175,7 @@ class VmDetailView(CheckedDetailView):
'new_network_vlan': self.__new_network,
'save_as': self.__save_as,
'disk-name': self.__add_disk,
'shut_down': self.__shut_down,
}
for k, v in options.iteritems():
......@@ -359,6 +362,15 @@ class VmDetailView(CheckedDetailView):
return redirect("%s#resources" % reverse_lazy(
"dashboard.views.detail", kwargs={'pk': self.object.pk}))
def __shut_down(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
self.object.shutdown_async(request.user)
return redirect("%s#activity" % reverse_lazy(
"dashboard.views.detail", kwargs={'pk': self.object.pk}))
class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
template_name = "dashboard/node-detail.html"
......@@ -446,7 +458,7 @@ class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
if m:
typ, id = m.groups()
entity = {'u': User, 'g': Group}[typ].objects.get(id=id)
if instance.owner == entity:
if getattr(instance, "owner", None) == entity:
logger.info("Tried to set owner's acl level for %s by %s.",
unicode(instance), unicode(request.user))
continue
......@@ -476,6 +488,29 @@ class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
value, unicode(request.user))
class TemplateAclUpdateView(AclUpdateView):
model = InstanceTemplate
def post(self, request, *args, **kwargs):
template = self.get_object()
if not (template.has_level(request.user, "owner") or
getattr(template, 'owner', None) == request.user):
logger.warning('Tried to set permissions of %s by non-owner %s.',
unicode(template), unicode(request.user))
raise PermissionDenied()
self.set_levels(request, template)
self.add_levels(request, template)
post_for_disk = request.POST.copy()
post_for_disk['perm-new'] = 'user'
request.POST = post_for_disk
for d in template.disks.all():
self.add_levels(request, d)
return redirect(reverse("dashboard.views.template-detail",
kwargs=self.kwargs))
class TemplateCreate(SuccessMessageMixin, CreateView):
model = InstanceTemplate
form_class = TemplateForm
......@@ -485,27 +520,28 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
def get(self, *args, **kwargs):
if not self.request.user.has_perm('vm.create_template'):
raise PermissionDenied()
form = self.form_class()
form.fields['disks'].queryset = Disk.get_objects_with_level(
'user', self.request.user).exclude(type="qcow2-snap")
self.parent = self.request.GET.get("parent")
return super(TemplateCreate, self).get(*args, **kwargs)
def get_form_kwargs(self):
kwargs = super(TemplateCreate, self).get_form_kwargs()
kwargs['parent'] = getattr(self, "parent", None)
kwargs['user'] = self.request.user
return kwargs
def post(self, request, *args, **kwargs):
if not self.request.user.has_perm('vm.create_template'):
raise PermissionDenied()
form = self.form_class(request.POST)
form = self.form_class(request.POST, user=request.user)
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
post = form.cleaned_data
for disk in post['disks']:
if not disk.has_level(request.user, 'user'):
raise PermissionDenied()
return super(TemplateCreate, self).post(self, request, args, kwargs)
def get_success_url(self):
......@@ -545,6 +581,11 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
else:
return super(TemplateDetail, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(TemplateDetail, self).get_context_data(**kwargs)
context['acl'] = get_acl_data(self.get_object())
return context
def get_success_url(self):
return reverse_lazy("dashboard.views.template-detail",
kwargs=self.kwargs)
......@@ -558,6 +599,11 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
raise PermissionDenied()
return super(TemplateDetail, self).post(self, request, args, kwargs)
def get_form_kwargs(self):
kwargs = super(TemplateDetail, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
class TemplateList(LoginRequiredMixin, SingleTableView):
template_name = "dashboard/template-list.html"
......@@ -684,7 +730,6 @@ class VmCreate(LoginRequiredMixin, TemplateView):
})
return self.render_to_response(context)
# TODO handle not ajax posts
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if not form.is_valid():
......@@ -692,6 +737,22 @@ class VmCreate(LoginRequiredMixin, TemplateView):
post = form.cleaned_data
user = request.user
try:
limit = user.profile.instance_limit
except Exception as e:
logger.debug('No profile or instance limit: %s', e)
else:
current = Instance.active.filter(owner=user).count()
logger.debug('current use: %d, limit: %d', current, limit)
if limit < current:
messages.error(request,
_('Instance limit (%d) exceeded.') % limit)
if request.is_ajax():
return HttpResponse(json.dumps({'redirect': '/'}),
content_type="application/json")
else:
return redirect('/')
template = post['template']
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
......@@ -1241,19 +1302,21 @@ class VmGraphView(LoginRequiredMixin, View):
if not instance.has_level(request.user, 'user'):
raise PermissionDenied()
prefix = 'vm.%s' % instance.vm_name
if metric == 'cpu':
target = ('cactiStyle(alias(derivative(%s.cpu.usage),'
'"cpu usage (%%)"))') % prefix
elif metric == 'memory':
target = ('cactiStyle(alias(%s.memory.usage,'
'"memory usage (%%)"))') % prefix
elif metric == 'network':
target = ('cactiStyle(aliasByMetric('
'derivative(%s.network.bytes_*)))') % prefix
else:
targets = {
'cpu': ('cactiStyle(alias(derivative(%s.cpu.usage),'
'"cpu usage (%%)"))'),
'memory': ('cactiStyle(alias(%s.memory.usage,'
'"memory usage (%%)"))'),
'network': ('cactiStyle(aliasByMetric('
'derivative(%s.network.bytes_*)))'),
}
if metric not in targets.keys():
raise SuspiciousOperation()
prefix = 'vm.%s' % instance.vm_name
target = targets[metric] % prefix
title = '%s (%s) - %s' % (instance.name, instance.vm_name, metric)
params = urlencode({'target': target,
......
......@@ -119,6 +119,7 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
disks = ManyToManyField(Disk, verbose_name=_('disks'),
related_name='template_set',
help_text=_('Disks which are to be mounted.'))
owner = ForeignKey(User)
class Meta:
app_label = 'vm'
......@@ -148,6 +149,12 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
else:
return 'linux'
def save(self, *args, **kwargs):
is_new = getattr(self, "pk", None) is None
super(InstanceTemplate, self).save(*args, **kwargs)
if is_new:
self.set_level(self.owner, 'owner')
class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
......@@ -419,6 +426,24 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
else:
return timedelta() # zero
@property
def os_type(self):
"""Get the type of the instance's operating system.
"""
if self.template is None: