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"
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)
})
# groups
if user.has_module_perms('auth'):
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
from .models import (Instance, InstanceActivity, InstanceTemplate, Interface,
InterfaceTemplate, Lease, NamedBaseResourceConfig, Node,
NodeActivity, Trait)
NodeActivity, Trait, Cluster, VMwareVMInstance)
class InstanceActivityAdmin(admin.ModelAdmin):
exclude = ('parent', )
admin.site.register(Cluster)
admin.site.register(VMwareVMInstance)
admin.site.register(Instance)
admin.site.register(InstanceActivity, InstanceActivityAdmin)
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
from .network import InterfaceTemplate
from .network import Interface
from .node import Node
from .cluster import Cluster
from .vmwarevminstance import VMwareVMInstance
__all__ = [
'InstanceActivity', 'BaseResourceConfigModel',
'NamedBaseResourceConfig', 'VirtualMachineDescModel', 'InstanceTemplate',
'Instance', 'post_state_changed', 'pre_state_changed', 'InterfaceTemplate',
'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 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import logging
import datetime
from django.utils import timezone
from django.utils.translation import ugettext_noop
from manager.mancelery import celery
from vm.models import Node, Instance
from vm.models import Node, Instance, Cluster
logger = logging.getLogger(__name__)
......@@ -76,3 +78,14 @@ def garbage_collector(timeout=15):
i.notify_owners_about_expiration()
else:
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