Commit 7725a755 by Balázs H.

Administration of VMware cloud in an educational environment

parent 0282e224
......@@ -201,6 +201,7 @@ PIPELINE_JS = {
"datatables/media/js/jquery.dataTables.js",
"dashboard/dashboard.js",
"dashboard/activity.js",
"dashboard/cluster-details.js",
"dashboard/group-details.js",
"dashboard/group-list.js",
"dashboard/js/stupidtable.min.js", # no bower file
......@@ -343,6 +344,7 @@ THIRD_PARTY_APPS = (
'django_sshkey',
'autocomplete_light',
'pipeline',
'pyVmomi',
)
......@@ -440,6 +442,33 @@ CACHES = {
}
import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
import logging
logger = logging.getLogger('django_auth_ldap')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
# Baseline configuration.
AUTH_LDAP_SERVER_URI = "ldap://sch.bme.hu"
AUTH_LDAP_BIND_DN = "cn=_vmware_reader,ou=VMware,ou=KSZK,ou=Hosts,dc=sch,dc=bme,dc=hu"
AUTH_LDAP_BIND_PASSWORD = "scheu3iSeez"
Please register or sign in to reply
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=Users,ou=SCHAccount,dc=sch,dc=bme,dc=hu",
ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)
if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
try:
from shutil import which # python >3.4
......
$(function() {
/* rename */
$("#cluster-details-h1-name, .cluster-details-rename-button").click(function() {
$("#cluster-details-h1-name span").hide();
$("#cluster-details-rename-form").show().css('display', 'inline-block');
$("#cluster-details-rename-name").select();
});
/* rename ajax */
$('#cluster-details-rename-submit').click(function() {
if(!$("#cluster-details-rename-name")[0].checkValidity()) {
return true;
}
var name = $('#cluster-details-rename-name').val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#cluster-details-h1-name span").text(data.new_name).show();
$('#cluster-details-rename-form').hide();
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming.", "danger");
}
});
return false;
});
});
......@@ -211,7 +211,7 @@ html {
display: none;
}
#group-details-rename-form {
#cluster-details-rename-form, #group-details-rename-form {
display: inline-block;
}
......
......@@ -28,7 +28,7 @@ from django_tables2.columns import (
from django_sshkey.models import UserKey
from storage.models import Disk
from vm.models import Node, InstanceTemplate, Lease
from vm.models import Node, InstanceTemplate, Lease, Cluster
from dashboard.models import ConnectCommand, Message
......@@ -111,6 +111,25 @@ class NodeListTable(Table):
'minion_online', 'overcommit', 'number_of_VMs', )
class ClusterListTable(Table):
pk = Column(
verbose_name="ID",
attrs={'th': {'class': 'cluster-list-table-thin'}},
)
name = TemplateColumn(
template_name="dashboard/cluster-list/column-name.html",
order_by="normalized_name"
)
class Meta:
model = Cluster
attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')}
fields = ('name', )
class GroupListTable(Table):
pk = TemplateColumn(
template_name='dashboard/group-list/column-id.html',
......
......@@ -23,6 +23,7 @@
{% block extra_css %}{% endblock %}
<script src="{% static "jquery/dist/jquery.min.js" %}"></script>
</head>
<body>
......@@ -87,7 +88,6 @@
<span class="pull-right">{{ COMPANY_NAME }}</span>
</footer>
<script src="{% static "jquery/dist/jquery.min.js" %}"></script>
<script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
{% javascript 'all' %}
......
{% load crispy_forms_tags %}
{% load i18n %}
<p class="text-muted">
{% trans "Adding VMware clusters enable managing virtual machines inside them." %}
</p>
<form method="POST" action="{% url "dashboard.views.cluster-create" %}">
{% csrf_token %}
{% crispy form %}
</form>
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title-page %}{{ cluster.name }} | {% trans "Cluster" %}{% endblock %}
{% block content %}
<div class="body-content">
<div class="page-header">
<div class="pull-right" style="padding-top: 15px;">
<a data-group-uuid="{{ record.uuid }}"
class="btn btn-success btn-xs real-link vmwarecluster-edit"
href="{% url "dashboard.views.vmwarevminstance-create" cluster=cluster.pk %}?next={{ request.path }}">
<i class="fa fa-plus"></i> add new vm
</a>
<a data-group-uuid="{{ record.uuid }}"
class="btn btn-info btn-xs real-link vmwarecluster-edit"
href="{% url "dashboard.views.cluster-edit" pk=cluster.pk %}?next={{ request.path }}">
<i class="fa fa-pencil"></i> edit
</a>
<a data-group-uuid="{{ record.uuid }}"
class="btn btn-danger btn-xs real-link vmwarecluster-edit"
href="{% url "dashboard.views.cluster-delete" pk=cluster.pk %}?next={{ request.path }}">
<i class="fa fa-trash-o"></i> delete
</a>
</div>
<h1>
<form action="" method="POST" id="cluster-details-rename-form" class="js-hidden">
{% csrf_token %}
<div class="input-group">
<input id="cluster-details-rename-name" class="form-control" name="new_name"
type="text" value="{{ cluster.name }}" required />
<span class="input-group-btn">
<button type="submit" id="cluster-details-rename-submit" class="btn">
{% trans "Modify" %}
</button>
</span>
</div>
</form>
<div id="cluster-details-h1-name">
<span class="no-js-hidden">{{ cluster.name }}</span>
</div>
</h1>
</div><!-- .page-header -->
{% if unmanaged_vms_table %}
<h3>Unmanaged virtual machines</h3>
<div class="panel-body">
<div class="table-responsive">
{% render_table unmanaged_vms_table %}
</div>
</div>
{% endif %}
{% if managed_vms_table %}
<h3>Managed virtual machines</h3>
<div class="panel-body">
<div class="table-responsive">
{% render_table managed_vms_table %}
</div>
</div>
{% endif %}
{% if deleted_vms_table %}
<h3>Deleted virtual machines</h3>
<div class="panel-body">
<div class="table-responsive">
{% render_table deleted_vms_table %}
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block title-page %}{% trans "Edit cluster " %}{{ cluster.name }}{% endblock %}
{% block content %}
<form method="POST" action="{% url "dashboard.views.cluster-edit" pk=cluster.pk %}">
{% csrf_token %}
{% crispy form %}
</form>
{% endblock %}
\ No newline at end of file
{% load crispy_forms_tags %}
{% load i18n %}
<p class="text-muted">
{% trans "Create a new virtual machine." %}
</p>
<form method="POST" action="{% url "dashboard.views.vmwarevminstance-create" cluster=cluster_pk %}">
{% csrf_token %}
{% crispy form %}
</form>
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right toolbar">
<span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of VMware clusters, which run the virtual machines." %}">
<i class="fa fa-info-circle"></i>
</span>
</div>
<h3 class="no-margin">
<i class="fa fa-cloud"></i> {% trans "VMware clusters" %}
</h3>
</div >
<div class="list-group" id="cluster-list-view">
<div id="dashboard-cluster-list">
{% for i in clusters %}
<a href="{{ i.get_absolute_url }}" class="list-group-item real-link
{% if forloop.last and clusters|length < 5 %} list-group-item-last{% endif %}">
<span class="index-cluster-list-name">
<i class="fa {{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i>
{{ i.name }}
</span>
<div style="clear: both;"></div>
</a>
{% endfor %}
</div>
<div class="list-group-item list-group-footer">
<div class="row">
<div class="col-xs-12 text-right">
{% if request.user.is_superuser %}
<a class="btn btn-success btn-xs cluster-create" href="{% url "dashboard.views.cluster-create" %}">
<i class="fa fa-plus-circle"></i> {% trans "add" %}
</a>
{% endif %}
</div>
</div>
</div>
</div><!-- #cluster-list-view -->
</div>
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right toolbar">
<span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of your assigned VMware clusters." %}">
<i class="fa fa-info-circle"></i>
</span>
</div>
<h3 class="no-margin">
<i class="fa fa-cloud"></i> {% trans "VMware instances" %}
</h3>
</div >
<div class="list-group" id="vmware-instance-list-view">
<div id="dashboard-vmware-instance-list">
{% for i in vmware_instances %}
<a href="{{ i.get_absolute_url }}" class="list-group-item real-link
{% if forloop.last and clusters|length < 5 %} list-group-item-last{% endif %}">
<span class="index-vmware-instance-list-name">
{{ i.name }}
</span>
<div style="clear: both;"></div>
</a>
{% endfor %}
</div>
</div><!-- #vmware-instance-list-view -->
</div>
......@@ -19,6 +19,9 @@
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vm.html" %}
</div>
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vmware-instances.html" %}
</div>
{% else %}
<div class="alert alert-info">
{% trans "You have no permission to start or manage virtual machines." %}
......@@ -43,6 +46,12 @@
</div>
{% endif %}
{% if perms.vm.create_cluster %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vmware-clusters.html" %}
</div>
{% endif %}
{% if perms.vm.view_statistics %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-nodes.html" %}
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load pipeline %}
{% block title-page %}{{ instance.name }} | vm{% endblock %}
{% block content %}
<div class="body-content">
<div class="page-header">
<div class="pull-right" id="ops">
{% include "dashboard/vm-detail/_operations.html" %}
</div>
<h1>
<div id="vm-details-h1-name" class="vm-details-home-edit-name">
{{ instance.name }}
</div>
</h1>
<div style="clear: both;"></div>
</div>
{% if instance.node and not instance.node.online %}
<div class="alert alert-warning">
{% if user.is_superuser %}
{% blocktrans with name=instance.node.name %}
The node <strong>{{ name }}</strong> is missing.
{% endblocktrans %}
{% else %}
{% trans "Currently you cannot execute any operations because the virtual machine's node is missing." %}
{% endif %}
</div>
{% endif %}
<div class="row">
<div class="col-md-4" id="vm-info-pane">
<div class="big">
<span id="vm-details-state" class="label label-success">
<i class="fa {{ status_icon }}"></i>
<span>{{ vm_info.state|upper }}</span>
</span>
</div>
<br />
<dl class="dl-horizontal vm-details-connection">
<dt>{% trans "# of CPU cores" %}</dt>
<dd>{{ vm_info.cpu }}</dd>
<dt>{% trans "Memory" %}</dt>
<dd>{{ vm_info.memory }} MB</dd>
<dt>{% trans "Time of expiration" %}</dt>
<dd>{{ instance.time_of_expiration }}</dd>
</dl>
</div>
</div>
</div>
{% endblock %}
\ No newline at end of file
{% load crispy_forms_tags %}
{% load i18n %}
<p class="text-muted">
{% trans "Add a virtual machine to the managed list, choose an owner and expiration date." %}
</p>
<form method="POST" action="{% url "dashboard.views.vmwarevminstance-add" uuid=instance_uuid cluster=cluster_pk %}">
{% csrf_token %}
{% crispy form %}
</form>
<a data-group-uuid="{{ record.uuid }}"
class="btn btn-success btn-xs real-link vmwarevminstance-add"
href="{% url "dashboard.views.vmwarevminstance-add" cluster=record.cluster_pk uuid=record.uuid %}?next={{ request.path }}">
<i class="fa fa-plus"></i> add
</a>
\ No newline at end of file
<a data-group-uuid="{{ record.uuid }}"
class="btn btn-info btn-xs real-link vmwarevminstance-modify"
href="{% url "dashboard.views.vmwarevminstance-add" cluster=record.cluster_pk uuid=record.uuid %}?next={{ request.path }}">
<i class="fa fa-pencil"></i> modify
</a>
\ No newline at end of file
<a data-group-uuid="{{ record.uuid }}"
class="btn btn-danger btn-xs real-link vmwarevminstance-remove"
href="{% url "dashboard.views.vmwarevminstance-remove" cluster=record.cluster_pk uuid=record.uuid %}?next={{ request.path }}">
<i class="fa fa-minus"></i> remove
</a>
\ No newline at end of file
......@@ -19,6 +19,9 @@ from __future__ import absolute_import
from django.conf.urls import patterns, url, include
import autocomplete_light
from dashboard.views.cluster import ClusterList, ClusterCreate, ClusterDetailView, ClusterDelete, ClusterEdit
from dashboard.views.vmwarevminstance import VMwareVMInstanceDelete, VMwareVMInstanceDetail, \
VMwareVMInstanceCreate, VMwareVMInstanceAdd
from vm.models import Instance
from .views import (
AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete,
......@@ -246,6 +249,27 @@ urlpatterns = patterns(
name="dashboard.views.message-create"),
url(r'^message/delete/(?P<pk>\d+)/$', MessageDelete.as_view(),
name="dashboard.views.message-delete"),
url(r'^cluster/list/$', ClusterList.as_view(), name='dashboard.views.cluster-list'),
url(r'^cluster/create/$', ClusterCreate.as_view(),
name='dashboard.views.cluster-create'),
url(r'^cluster/(?P<pk>\d+)/$', ClusterDetailView.as_view(),
name='dashboard.views.cluster-detail'),
url(r'^cluster/(?P<pk>\d+)/edit/$', ClusterEdit.as_view(),
name='dashboard.views.cluster-edit'),
url(r'^cluster/delete/(?P<pk>\d+)/$', ClusterDelete.as_view(),
name="dashboard.views.cluster-delete"),
url(r'^vmwarevminstance/add/cluster/(?P<cluster>\d+)/uuid/(?P<uuid>[A-Za-z0-9\-]+)/$', VMwareVMInstanceAdd.as_view(),
name='dashboard.views.vmwarevminstance-add'),
url(r'^vmware-vm-instance/create/cluster/(?P<cluster>\d+)/$', VMwareVMInstanceCreate.as_view(),
name='dashboard.views.vmwarevminstance-create'),
url(r'^vmware-vm-instance/(?P<pk>\d+)/$', VMwareVMInstanceDetail.as_view(),
name='dashboard.views.vmwarevminstance-detail'),
url(r'^vmware-vm-instance/remove/cluster/(?P<cluster>\d+)/uuid/(?P<uuid>[A-Za-z0-9\-]+)/$', VMwareVMInstanceDelete.as_view(),
name='dashboard.views.vmwarevminstance-remove'),
url(r'^vmware-vm-instance/remove/cluster/(?P<cluster>\d+)/uuid/(?P<uuid>[A-Za-z0-9\-]+)/$', VMwareVMInstanceDelete.as_view(),
name='dashboard.views.vmwarevminstance-remove'),
)
urlpatterns += patterns(
......
from __future__ import unicode_literals, absolute_import
import json
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.views.generic import DetailView, TemplateView, UpdateView
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from django_tables2 import SingleTableView
from vm.models import Node, Trait
from ..forms import TraitForm, ClusterCreateForm
from ..tables import ClusterListTable
from .util import GraphMixin, DeleteViewBase
from vm.models.cluster import Cluster
import django_tables2 as tables
from django_tables2.columns import TemplateColumn
class UnmanagedVmsTable(tables.Table):
name = tables.Column(orderable=False, verbose_name="Name")
state = tables.Column(orderable=False, verbose_name="Current power state")
os = tables.Column(orderable=False, verbose_name='Operating system')
memory = TemplateColumn("{{ value }} MB", orderable=False, verbose_name='Memory')
cpu = tables.Column(orderable=False, verbose_name='CPU cores')
add_btn = TemplateColumn(orderable=False, template_name="dashboard/vmwarevminstance-list/column-add.html",
verbose_name="Actions")
class Meta:
attrs = {'class': 'table table-bordered table-striped table-hover'}
class ManagedVmsTable(tables.Table):
name = tables.Column(orderable=False, verbose_name="Name")
time_of_expiration = tables.Column(orderable=False, verbose_name="Expiration time")
state = tables.Column(orderable=False, verbose_name="Current power state")
os = tables.Column(orderable=False, verbose_name='Operating system')
owner = tables.Column(orderable=False, verbose_name='Owner')
memory = TemplateColumn("{{ value }} MB", orderable=False, verbose_name='Memory')
cpu = tables.Column(orderable=False, verbose_name='# of CPU cores')
add_btn = TemplateColumn(orderable=False, template_name="dashboard/vmwarevminstance-list/column-modify.html",
verbose_name="Actions")
class Meta:
attrs = {'class': 'table table-bordered table-striped table-hover'}
class DeletedVmsTable(tables.Table):
name = tables.Column(orderable=False, verbose_name="Name")
os = tables.Column(orderable=False, verbose_name='Operating system')
memory = TemplateColumn("{{ value }} MB", orderable=False, verbose_name='Memory')
cpu = tables.Column(orderable=False, verbose_name='# of CPU cores')
owner = tables.Column(orderable=False, verbose_name='Owner')
remove_btn = TemplateColumn(orderable=False, template_name="dashboard/vmwarevminstance-list/column-remove.html",
verbose_name="Actions")
class Meta:
attrs = {'class': 'table table-bordered table-striped table-hover'}
class ClusterDetailView(LoginRequiredMixin, DetailView):
template_name = "dashboard/cluster-detail.html"
model = Cluster
def get(self, *args, **kwargs):
if not self.request.user.has_perm('vm.view_statistics'):
raise PermissionDenied()
return super(ClusterDetailView, self).get(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(ClusterDetailView, self).get_context_data(**kwargs)
unmanaged_vms, managed_vms, deleted_vms, error_msg = self.object.get_list_of_vms()
if error_msg is not None:
messages.error(self.request, error_msg)
else:
unmanaged_vms_table = UnmanagedVmsTable(unmanaged_vms)
managed_vms_table = ManagedVmsTable(managed_vms)
deleted_vms_table = DeletedVmsTable(deleted_vms)
context.update({
'unmanaged_vms_table': unmanaged_vms_table,
'managed_vms_table': managed_vms_table,
'deleted_vms_table': deleted_vms_table,
})
return context
def post(self, request, *args, **kwargs):
if not request.user.is_superuser:
raise PermissionDenied()
if request.POST.get('new_name'):
return self.__set_name(request)
if request.POST.get('to_remove'):
return self.__remove_trait(request)
return redirect(reverse_lazy("dashboard.views.cluster-detail",
kwargs={'pk': self.get_object().pk}))
def __set_name(self, request):
self.object = self.get_object()
new_name = request.POST.get("new_name")
Cluster.objects.filter(pk=self.object.pk).update(
**{'name': new_name})
success_message = _("Cluster successfully renamed.")
if request.is_ajax():
response = {
'message': success_message,
'new_name': new_name,
'node_pk': self.object.pk
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.cluster-detail",
kwargs={'pk': self.object.pk}))
class ClusterList(LoginRequiredMixin, GraphMixin, SingleTableView):
template_name = "dashboard/cluster-list.html"
table_class = ClusterListTable
table_pagination = False
def get(self, *args, **kwargs):
if not self.request.user.has_perm('vm.view_statistics'):
raise PermissionDenied()
if self.request.is_ajax():
nodes = Node.objects.all()
nodes = [{
'name': i.name,
'icon': i.get_status_icon(),
'url': i.get_absolute_url(),
'label': i.get_status_label(),
'status': i.state.lower()} for i in nodes]
return HttpResponse(
json.dumps(list(nodes)),
content_type="application/json",
)
else:
return super(ClusterList, self).get(*args, **kwargs)
def get_queryset(self):
return Cluster.objects.all()
class ClusterCreate(LoginRequiredMixin, TemplateView):
model = Cluster
form_class = ClusterCreateForm
template_name = 'dashboard/cluster-create.html'
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_modal.html']
else:
return ['dashboard/nojs-wrapper.html']
def get(self, request, form=None, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
if form is None:
form = self.form_class()
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/cluster-create.html',
'box_title': _('Add a Cluster'),
'form': form,
'ajax_title': True,
})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
form = self.form_class(request.POST)
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
form.cleaned_data
savedform = form.save()
messages.success(request, _('Cluster successfully created.'))
if request.is_ajax():
return HttpResponse(json.dumps({'redirect':
reverse("dashboard.index")}),
content_type="application/json")
else:
return redirect(reverse("dashboard.index"))
class ClusterEdit(SuperuserRequiredMixin, UpdateView):
model = Cluster
success_message = _("Cluster successfully updated.")
template_name = 'dashboard/cluster-edit.html'
form_class = ClusterCreateForm
def check_auth(self):
# SuperuserRequiredMixin
pass
def get_success_url(self):
return reverse_lazy('dashboard.index')
def get_context_data(self, **kwargs):
context = super(ClusterEdit, self).get_context_data(**kwargs)
context['cluster'] = self.object
return context
class ClusterDelete(SuperuserRequiredMixin, DeleteViewBase):
model = Cluster
success_message = _("Cluster successfully deleted.")
def check_auth(self):
# SuperuserRequiredMixin
pass
def get_success_url(self):
return reverse_lazy('dashboard.index')
......@@ -27,7 +27,7 @@ from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin
from dashboard.models import GroupProfile
from vm.models import Instance, Node, InstanceTemplate
from vm.models import Instance, Node, InstanceTemplate, Cluster, VMwareVMInstance
from dashboard.views.vm import vm_ops
from ..store_api import Store
......@@ -78,6 +78,16 @@ class IndexView(LoginRequiredMixin, TemplateView):
}
})
# VMWare clusters
context.update({
'clusters': Cluster.objects.all()
})
# VMWare instances
context.update({
'vmware_instances': VMwareVMInstance.objects.filter(owner=user)
})