Commit 7725a755 by Balázs H.

Administration of VMware cloud in an educational environment

parent 0282e224
...@@ -201,6 +201,7 @@ PIPELINE_JS = { ...@@ -201,6 +201,7 @@ PIPELINE_JS = {
"datatables/media/js/jquery.dataTables.js", "datatables/media/js/jquery.dataTables.js",
"dashboard/dashboard.js", "dashboard/dashboard.js",
"dashboard/activity.js", "dashboard/activity.js",
"dashboard/cluster-details.js",
"dashboard/group-details.js", "dashboard/group-details.js",
"dashboard/group-list.js", "dashboard/group-list.js",
"dashboard/js/stupidtable.min.js", # no bower file "dashboard/js/stupidtable.min.js", # no bower file
...@@ -343,6 +344,7 @@ THIRD_PARTY_APPS = ( ...@@ -343,6 +344,7 @@ THIRD_PARTY_APPS = (
'django_sshkey', 'django_sshkey',
'autocomplete_light', 'autocomplete_light',
'pipeline', 'pipeline',
'pyVmomi',
) )
...@@ -440,6 +442,33 @@ CACHES = { ...@@ -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"
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': if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
try: try:
from shutil import which # python >3.4 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 { ...@@ -211,7 +211,7 @@ html {
display: none; display: none;
} }
#group-details-rename-form { #cluster-details-rename-form, #group-details-rename-form {
display: inline-block; display: inline-block;
} }
......
...@@ -28,7 +28,7 @@ from django_tables2.columns import ( ...@@ -28,7 +28,7 @@ from django_tables2.columns import (
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
from storage.models import Disk 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 from dashboard.models import ConnectCommand, Message
...@@ -111,6 +111,25 @@ class NodeListTable(Table): ...@@ -111,6 +111,25 @@ class NodeListTable(Table):
'minion_online', 'overcommit', 'number_of_VMs', ) '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): class GroupListTable(Table):
pk = TemplateColumn( pk = TemplateColumn(
template_name='dashboard/group-list/column-id.html', template_name='dashboard/group-list/column-id.html',
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
<script src="{% static "jquery/dist/jquery.min.js" %}"></script>
</head> </head>
<body> <body>
...@@ -87,7 +88,6 @@ ...@@ -87,7 +88,6 @@
<span class="pull-right">{{ COMPANY_NAME }}</span> <span class="pull-right">{{ COMPANY_NAME }}</span>
</footer> </footer>
<script src="{% static "jquery/dist/jquery.min.js" %}"></script>
<script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script> <script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
{% javascript 'all' %} {% 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 @@ ...@@ -19,6 +19,9 @@
<div class="col-lg-4 col-sm-6"> <div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vm.html" %} {% include "dashboard/index-vm.html" %}
</div> </div>
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vmware-instances.html" %}
</div>
{% else %} {% else %}
<div class="alert alert-info"> <div class="alert alert-info">
{% trans "You have no permission to start or manage virtual machines." %} {% trans "You have no permission to start or manage virtual machines." %}
...@@ -43,6 +46,12 @@ ...@@ -43,6 +46,12 @@
</div> </div>
{% endif %} {% 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 %} {% if perms.vm.view_statistics %}
<div class="col-lg-4 col-sm-6"> <div class="col-lg-4 col-sm-6">
{% include "dashboard/index-nodes.html" %} {% 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 ...@@ -19,6 +19,9 @@ from __future__ import absolute_import
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
import autocomplete_light 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 vm.models import Instance
from .views import ( from .views import (
AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete, AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete,
...@@ -246,6 +249,27 @@ urlpatterns = patterns( ...@@ -246,6 +249,27 @@ urlpatterns = patterns(
name="dashboard.views.message-create"), name="dashboard.views.message-create"),
url(r'^message/delete/(?P<pk>\d+)/$', MessageDelete.as_view(), url(r'^message/delete/(?P<pk>\d+)/$', MessageDelete.as_view(),
name="dashboard.views.message-delete"), 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( 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 ...@@ -27,7 +27,7 @@ from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
from dashboard.models import GroupProfile 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 dashboard.views.vm import vm_ops
from ..store_api import Store from ..store_api import Store
...@@ -78,6 +78,16 @@ class IndexView(LoginRequiredMixin, TemplateView): ...@@ -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)
})
# groups # groups
if user.has_module_perms('auth'): if user.has_module_perms('auth'):
profiles = GroupProfile.get_objects_with_level('operator', user) profiles = GroupProfile.get_objects_with_level('operator', user)
......
from __future__ import unicode_literals, absolute_import
import json
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.views.generic import TemplateView, DetailView
from dashboard.views import DeleteViewBase
from vm.models import VMwareVMInstance
from vm.models.cluster import Cluster
from ..forms import VMwareVMInstanceForm, VMwareVMInstanceCreateForm
class VMwareVMInstanceCreate(LoginRequiredMixin, TemplateView):
model = VMwareVMInstance
form_class = VMwareVMInstanceCreateForm
template_name = 'dashboard/create-vmware-vm.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):
cluster = None
if 'cluster' in kwargs:
cluster = kwargs.pop("cluster")
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/create-vmware-vm.html',
'box_title': _('Create a VMware virtual machine'),
'form': form,
'ajax_title': True,
'cluster_pk': cluster,
})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
cluster = None
if 'cluster' in kwargs:
cluster = kwargs.pop("cluster")
form = self.form_class(request.POST, cluster_pk=int(cluster))
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
savedform = form.save()
messages.success(request, _('Virtual machine 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 VMwareVMInstanceAdd(LoginRequiredMixin, TemplateView):
model = VMwareVMInstance
form_class = VMwareVMInstanceForm
template_name = 'dashboard/vmwarevminstance-add.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()
uuid = None
cluster = None
if 'uuid' in kwargs:
uuid = kwargs.pop("uuid")
if 'cluster' in kwargs:
cluster = kwargs.pop("cluster")
cluster_instance = Cluster.objects.get(pk=cluster)
vm_info = cluster_instance.get_vm_details_by_uuid(uuid)
if form is None:
form = self.form_class(uuid=uuid)
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/vmwarevminstance-add.html',
'box_title': _('Add a virtual machine: '+vm_info["name"]),
'form': form,
'ajax_title': True,
'instance_uuid': uuid,
'cluster_pk': cluster,
})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
if 'cluster' in kwargs:
cluster = kwargs.pop("cluster")
if 'uuid' in kwargs:
uuid = kwargs.pop("uuid")
form = self.form_class(request.POST, cluster_pk=int(cluster), uuid=uuid)
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
form.cleaned_data
savedform = form.save()
messages.success(request, _('Virtual machine successfully added.'))
if request.is_ajax():
return HttpResponse(json.dumps({'redirect':
reverse("dashboard.index")}),
content_type="application/json")
else:
return redirect(reverse("dashboard.index"))
class VMwareVMInstanceDelete(SuperuserRequiredMixin, DeleteViewBase):
model = VMwareVMInstance
success_message = _("Instance has been successfully deleted.")
def check_auth(self):
# SuperuserRequiredMixin
pass
def get_success_url(self):
return reverse_lazy('dashboard.index')
class VMwareVMInstanceDetail(LoginRequiredMixin, DetailView):
template_name = "dashboard/vmware-vm-instance-detail.html"
model = VMwareVMInstance
def get(self, *args, **kwargs):
if not self.request.user.has_perm('vm.view_statistics'):
raise PermissionDenied()
return super(VMwareVMInstanceDetail, self).get(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(VMwareVMInstanceDetail, self).get_context_data(**kwargs)
context['instance'] = self.object
vm_info = self.object.get_vm_info()
context['vm_info'] = vm_info
context['status_icon'] = self.object.get_status_icon(vm_info['state'])
return context
\ No newline at end of file
...@@ -19,13 +19,15 @@ from django.contrib import admin ...@@ -19,13 +19,15 @@ from django.contrib import admin
from .models import (Instance, InstanceActivity, InstanceTemplate, Interface, from .models import (Instance, InstanceActivity, InstanceTemplate, Interface,
InterfaceTemplate, Lease, NamedBaseResourceConfig, Node, InterfaceTemplate, Lease, NamedBaseResourceConfig, Node,
NodeActivity, Trait) NodeActivity, Trait, Cluster, VMwareVMInstance)
class InstanceActivityAdmin(admin.ModelAdmin): class InstanceActivityAdmin(admin.ModelAdmin):
exclude = ('parent', ) exclude = ('parent', )
admin.site.register(Cluster)
admin.site.register(VMwareVMInstance)
admin.site.register(Instance) admin.site.register(Instance)
admin.site.register(InstanceActivity, InstanceActivityAdmin) admin.site.register(InstanceActivity, InstanceActivityAdmin)
admin.site.register(InstanceTemplate) admin.site.register(InstanceTemplate)
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import model_utils.fields
import common.operations
import django.utils.timezone
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('vm', '0002_interface_model'),
]
operations = [
migrations.CreateModel(
name='Cluster',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('name', models.CharField(help_text='Human readable name of cluster.', unique=True, max_length=50, verbose_name='name')),
('address', models.CharField(help_text='The address of the vCenter.', max_length=200, verbose_name='address')),
('username', models.CharField(default='', help_text='The username used for the connection.', max_length=200, verbose_name='username')),
('password', models.CharField(default='', help_text='The password used for the connection.', max_length=200, verbose_name='password')),
],
options={
'db_table': 'vm_cluster',
},
bases=(common.operations.OperatedMixin, models.Model),
),
migrations.CreateModel(
name='VMwareVMInstance',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('name', models.CharField(help_text='The name of the virtual machine.', unique=True, max_length=50, verbose_name='name')),
('instanceUUID', models.CharField(help_text='A unique identifier of the VM.', unique=True, max_length=200, verbose_name='instanceUUID')),
('time_of_expiration', models.DateTimeField(default=None, help_text='The time, when the virtual machine will expire.', null=True, verbose_name='time of expiration', blank=True)),
('operating_system', models.CharField(help_text='The OS of the VM.', unique=True, max_length=200, verbose_name='operating system')),
('cpu_cores', models.IntegerField(help_text='The number of CPU cores in the VM.')),
('memory_size', models.IntegerField(help_text='The amount of memory (MB) in the VM.')),
('cluster', models.ForeignKey(to='vm.Cluster')),
('owner', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'vm_vmware_vminstance',
'verbose_name': 'VMware virtual machine instance',
},
bases=(common.operations.OperatedMixin, models.Model),
),
]
...@@ -15,11 +15,13 @@ from .instance import pwgen ...@@ -15,11 +15,13 @@ from .instance import pwgen
from .network import InterfaceTemplate from .network import InterfaceTemplate
from .network import Interface from .network import Interface
from .node import Node from .node import Node
from .cluster import Cluster
from .vmwarevminstance import VMwareVMInstance
__all__ = [ __all__ = [
'InstanceActivity', 'BaseResourceConfigModel', 'InstanceActivity', 'BaseResourceConfigModel',
'NamedBaseResourceConfig', 'VirtualMachineDescModel', 'InstanceTemplate', 'NamedBaseResourceConfig', 'VirtualMachineDescModel', 'InstanceTemplate',
'Instance', 'post_state_changed', 'pre_state_changed', 'InterfaceTemplate', 'Instance', 'post_state_changed', 'pre_state_changed', 'InterfaceTemplate',
'Interface', 'Trait', 'Node', 'NodeActivity', 'Lease', 'node_activity', 'Interface', 'Trait', 'Node', 'NodeActivity', 'Lease', 'node_activity',
'pwgen' 'pwgen', 'Cluster', 'VMwareVMInstance',
] ]
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, unicode_literals
from logging import getLogger
from django.contrib.auth.models import User
from django.db.models import (
CharField, ForeignKey, permalink, DateTimeField, IntegerField)
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
from requests import ConnectionError
from common.operations import OperatedMixin
from vm.models import Cluster
logger = getLogger(__name__)
class VMwareVMInstance(OperatedMixin, TimeStampedModel):
"""A VMware virtual machine instance.
"""
name = CharField(max_length=50, unique=True,
verbose_name=_('name'),
help_text=_('The name of the virtual machine.'))
instanceUUID = CharField(max_length=200,
verbose_name=_('instanceUUID'),
help_text=_('A unique identifier of the VM.'),
unique=True)
time_of_expiration = DateTimeField(blank=True, default=None, null=True,
verbose_name=_('time of expiration'),
help_text=_("The time, when the virtual machine"
" will expire."))
cluster = ForeignKey(Cluster, null=False)
owner = ForeignKey(User, null=False)
operating_system = CharField(max_length=200,
verbose_name=_('operating system'),
help_text=_('The OS of the VM.'),
unique=True)
cpu_cores = IntegerField(help_text=_('The number of CPU cores in the VM.'))
memory_size = IntegerField(help_text=_('The amount of memory (MB) in the VM.'))
class Meta:
app_label = 'vm'
db_table = 'vm_vmware_vminstance'
verbose_name = 'VMware virtual machine instance'
@permalink
def get_absolute_url(self):
return 'dashboard.views.vmwarevminstance-detail', None, {'pk': self.id}
def __unicode__(self):
return self.name
def shutdown_vm(self):
try:
si = SmartConnect(host=self.cluster.address,
user=self.cluster.username,
pwd=self.cluster.password,
port=443)
search_index = si.content.searchIndex
vm = search_index.FindByUuid(None, self.instanceUUID, True, True)
vm.ShutdownGuest()
Disconnect(si)
except ConnectionError:
pass
except vim.fault.InvalidLogin as e:
pass
except vim.fault.ToolsUnavailable:
pass
def start_vm(self):
try:
si = SmartConnect(host=self.cluster.address,
user=self.cluster.username,
pwd=self.cluster.password,
port=443)
search_index = si.content.searchIndex
vm = search_index.FindByUuid(None, self.instanceUUID, True, True)
vm.PowerOnVM_Task()
Disconnect(si)
except ConnectionError:
pass
except vim.fault.InvalidLogin as e:
pass
def restart_vm(self):
try:
si = SmartConnect(host=self.cluster.address,
user=self.cluster.username,
pwd=self.cluster.password,
port=443)
search_index = si.content.searchIndex
vm = search_index.FindByUuid(None, self.instanceUUID, True, True)
vm.RebootGuest()
Disconnect(si)
except ConnectionError:
pass
except vim.fault.InvalidLogin as e:
pass
def suspend_vm(self):
try:
si = SmartConnect(host=self.cluster.address,
user=self.cluster.username,
pwd=self.cluster.password,
port=443)
search_index = si.content.searchIndex
vm = search_index.FindByUuid(None, self.instanceUUID, True, True)
vm.StandbyGuest()
Disconnect(si)
except ConnectionError:
pass
except vim.fault.InvalidLogin as e:
pass
def get_vm_info(self):
return self.cluster.get_vm_details_by_uuid(self.instanceUUID)
def get_status_icon(self, state):
return {
'powered on': 'fa-play',
'powered off': 'fa-stop',
'suspended': 'fa-pause',
}.get(state, 'fa-question')
\ No newline at end of file
...@@ -16,11 +16,13 @@ ...@@ -16,11 +16,13 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import logging import logging
import datetime
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
from manager.mancelery import celery from manager.mancelery import celery
from vm.models import Node, Instance from vm.models import Node, Instance, Cluster
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -76,3 +78,14 @@ def garbage_collector(timeout=15): ...@@ -76,3 +78,14 @@ def garbage_collector(timeout=15):
i.notify_owners_about_expiration() i.notify_owners_about_expiration()
else: else:
logger.debug("Instance %d didn't expire." % i.pk) logger.debug("Instance %d didn't expire." % i.pk)
for c in Cluster.objects.all():
for i in c.vmwarevminstance_set.all():
if datetime.now > i.time_of_expiration:
i.suspend_vm()
i.owner.profile.notify(
ugettext_noop('%(instance)s suspended'),
ugettext_noop(
'Your instance <a href="%(url)s">%(instance)s</a> '
'has been suspended due to expiration.'),
instance=i.name, url=i.get_absolute_url())
\ No newline at end of file
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