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
......
...@@ -52,8 +52,8 @@ from django.core.urlresolvers import reverse_lazy ...@@ -52,8 +52,8 @@ from django.core.urlresolvers import reverse_lazy
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from vm.models import ( from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance, Cluster,
) VMwareVMInstance)
from storage.models import DataStore, Disk from storage.models import DataStore, Disk
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
...@@ -77,7 +77,6 @@ priority_choices = ( ...@@ -77,7 +77,6 @@ priority_choices = (
class NoFormTagMixin(object): class NoFormTagMixin(object):
@property @property
def helper(self): def helper(self):
helper = FormHelper(self) helper = FormHelper(self)
...@@ -187,7 +186,7 @@ class VmCustomizeForm(forms.Form): ...@@ -187,7 +186,7 @@ class VmCustomizeForm(forms.Form):
self.initial['ram_size'] = self.template.ram_size self.initial['ram_size'] = self.template.ram_size
else: else:
self.allowed_fields = ("name", "template", "customized", ) self.allowed_fields = ("name", "template", "customized",)
# initial name and template pk # initial name and template pk
self.initial['name'] = self.template.name self.initial['name'] = self.template.name
...@@ -212,7 +211,6 @@ class VmCustomizeForm(forms.Form): ...@@ -212,7 +211,6 @@ class VmCustomizeForm(forms.Form):
class GroupCreateForm(NoFormTagMixin, forms.ModelForm): class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
description = forms.CharField(label=_("Description"), required=False, description = forms.CharField(label=_("Description"), required=False,
widget=forms.Textarea(attrs={'rows': 3})) widget=forms.Textarea(attrs={'rows': 3}))
...@@ -256,11 +254,10 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm): ...@@ -256,11 +254,10 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
class Meta: class Meta:
model = Group model = Group
fields = ('name', ) fields = ('name',)
class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm): class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
new_groups = kwargs.pop('new_groups', None) new_groups = kwargs.pop('new_groups', None)
superuser = kwargs.pop('superuser', False) superuser = kwargs.pop('superuser', False)
...@@ -295,7 +292,6 @@ class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm): ...@@ -295,7 +292,6 @@ class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm):
class HostForm(NoFormTagMixin, forms.ModelForm): class HostForm(NoFormTagMixin, forms.ModelForm):
def setowner(self, user): def setowner(self, user):
self.instance.owner = user self.instance.owner = user
...@@ -368,7 +364,6 @@ class HostForm(NoFormTagMixin, forms.ModelForm): ...@@ -368,7 +364,6 @@ class HostForm(NoFormTagMixin, forms.ModelForm):
class NodeForm(forms.ModelForm): class NodeForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NodeForm, self).__init__(*args, **kwargs) super(NodeForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self) self.helper = FormHelper(self)
...@@ -457,6 +452,317 @@ class NodeForm(forms.ModelForm): ...@@ -457,6 +452,317 @@ class NodeForm(forms.ModelForm):
fields = ['name', 'priority', 'enabled', ] fields = ['name', 'priority', 'enabled', ]
class ClusterCreateForm(forms.ModelForm):
password = forms.CharField(label=_("Password"),
widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
super(ClusterCreateForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Div(
Div(
Div(
AnyTag(
'h3',
HTML(_("Cluster")),
),
css_class="col-sm-3",
),
css_class="row",
),
Div(
Div( # cluster name
HTML('<label for="node-nodename-box">'
'Name'
'</label>'),
css_class="col-sm-3",
),
Div(
'name',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div( # cluster address
HTML('<label for="node-nodename-box">'
'Address'
'</label>'),
css_class="col-sm-3",
),
Div(
'address',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div( # username used for the connection
HTML('<label for="node-nodename-box">'
'Username'
'</label>'),
css_class="col-sm-3",
),
Div(
'username',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div( # password used for the connection
HTML('<label for="node-nodename-box">'
'Password'
'</label>'),
css_class="col-sm-3",
),
Div(
'password',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="fa fa-save"
),
HTML("Save"),
css_id="node-create-submit",
css_class="btn btn-success",
),
css_class="col-sm-12 text-right",
),
css_class="row",
),
css_class="col-sm-11",
),
css_class="row",
),
)
def save(self):
new_cluster = super(ClusterCreateForm, self).save()
return new_cluster
class Meta:
model = Cluster
fields = ['name', 'address', 'username', 'password', ]
class VMwareVMInstanceCreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(VMwareVMInstanceCreateForm, self).__init__(*args)
if "cluster_pk" in kwargs:
self.cluster_pk = kwargs.pop('cluster_pk')
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Div(
Div(
Div(
HTML('<label>'
'Name'
'</label>'),
css_class="col-sm-3",
),
Div(
'name',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div(
HTML('<label>'
'# of CPU cores'
'</label>'),
css_class="col-sm-3",
),
Div(
'cpu_cores',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div(
HTML('<label>'
'Amount of memory'
'</label>'),
css_class="col-sm-3",
),
Div(
'memory_size',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div(
HTML('<label>'
'Time of expiration'
'</label>'),
css_class="col-sm-3",
),
Div(
'time_of_expiration',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div(
HTML('<label>'
'Owner'
'</label>'),
css_class="col-sm-3",
),
Div(
'owner',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="fa fa-save"
),
HTML("Create"),
css_id="node-create-submit",
css_class="btn btn-success",
),
css_class="col-sm-12 text-right",
),
css_class="row",
),
css_class="col-sm-11",
),
css_class="row",
),
)
def save(self, **kwargs):
new_vmware_vm = super(VMwareVMInstanceCreateForm, self).save(commit=False)
own_cluster = Cluster.objects.get(pk=self.cluster_pk)
new_vmware_vm.cluster = own_cluster
new_vmware_vm.uuid = 'placeholder'
new_vmware_vm.operating_system = 'placeholder'
new_vmware_vm.save()
return new_vmware_vm
class Meta:
model = VMwareVMInstance
fields = ['name', 'cpu_cores', 'memory_size', 'time_of_expiration', 'owner', ]
class VMwareVMInstanceForm(forms.ModelForm):
time_of_expiration = forms.DateField(widget=forms.TextInput(attrs=
{
'class': 'datepicker',
}))
def __init__(self, *args, **kwargs):
if "uuid" in kwargs:
self.uuid = kwargs.pop('uuid')
if "cluster_pk" in kwargs:
self.cluster_pk = kwargs.pop('cluster_pk')
super(VMwareVMInstanceForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Div(
Div(
Div( # time of expiration
HTML('<label>'
'Time of expiration'
'</label>'),
css_class="col-sm-3",
),
Div(
'time_of_expiration',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div( # the owner of the vm
HTML('<label>'
'Owner'
'</label>'),
css_class="col-sm-3",
),
Div(
'owner',
css_class="col-sm-9",
),
css_class="row",
),
Div(
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="fa fa-plus"
),
HTML("Add"),
css_id="node-create-submit",
css_class="btn btn-success",
),
css_class="col-sm-12 text-right",
),
css_class="row",
),
css_class="col-sm-11",
),
css_class="row",
),
)
def save(self):
new_virtual_machine = super(VMwareVMInstanceForm, self).save(commit=False)
own_cluster = Cluster.objects.get(pk=self.cluster_pk)
new_virtual_machine.cluster = own_cluster
new_virtual_machine.instanceUUID = self.uuid
vm_details = own_cluster.get_vm_details_by_uuid(self.uuid)
new_virtual_machine.name = vm_details["name"]
new_virtual_machine.cpu_cores = vm_details["cpu"]
new_virtual_machine.memory_size = vm_details["memory"]
new_virtual_machine.operating_system = vm_details["os"]
new_virtual_machine.save()
return new_virtual_machine
class Meta:
model = VMwareVMInstance
fields = ['time_of_expiration', 'owner']
class TemplateForm(forms.ModelForm): class TemplateForm(forms.ModelForm):
networks = forms.ModelMultipleChoiceField( networks = forms.ModelMultipleChoiceField(
queryset=None, required=False, label=_("Networks")) queryset=None, required=False, label=_("Networks"))
...@@ -511,7 +817,7 @@ class TemplateForm(forms.ModelForm): ...@@ -511,7 +817,7 @@ class TemplateForm(forms.ModelForm):
self.allowed_fields += tuple(set(self.fields.keys()) - self.allowed_fields += tuple(set(self.fields.keys()) -
set(['raw_data'])) set(['raw_data']))
if self.user.is_superuser: if self.user.is_superuser:
self.allowed_fields += ('raw_data', ) self.allowed_fields += ('raw_data',)
for name, field in self.fields.items(): for name, field in self.fields.items():
if name not in self.allowed_fields: if name not in self.allowed_fields:
field.widget.attrs['disabled'] = 'disabled' field.widget.attrs['disabled'] = 'disabled'
...@@ -600,7 +906,7 @@ class TemplateForm(forms.ModelForm): ...@@ -600,7 +906,7 @@ class TemplateForm(forms.ModelForm):
class Meta: class Meta:
model = InstanceTemplate model = InstanceTemplate
exclude = ('state', 'disks', ) exclude = ('state', 'disks',)
widgets = { widgets = {
'system': forms.TextInput, 'system': forms.TextInput,
'max_ram_size': forms.HiddenInput, 'max_ram_size': forms.HiddenInput,
...@@ -609,7 +915,6 @@ class TemplateForm(forms.ModelForm): ...@@ -609,7 +915,6 @@ class TemplateForm(forms.ModelForm):
class LeaseForm(forms.ModelForm): class LeaseForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(LeaseForm, self).__init__(*args, **kwargs) super(LeaseForm, self).__init__(*args, **kwargs)
self.generate_fields() self.generate_fields()
...@@ -743,7 +1048,6 @@ class LeaseForm(forms.ModelForm): ...@@ -743,7 +1048,6 @@ class LeaseForm(forms.ModelForm):
class VmRenewForm(OperationForm): class VmRenewForm(OperationForm):
force = forms.BooleanField(required=False, label=_( force = forms.BooleanField(required=False, label=_(
"Set expiration times even if they are shorter than " "Set expiration times even if they are shorter than "
"the current value.")) "the current value."))
...@@ -783,7 +1087,6 @@ class VmMigrateForm(forms.Form): ...@@ -783,7 +1087,6 @@ class VmMigrateForm(forms.Form):
class VmStateChangeForm(OperationForm): class VmStateChangeForm(OperationForm):
interrupt = forms.BooleanField(required=False, label=_( interrupt = forms.BooleanField(required=False, label=_(
"Forcibly interrupt all running activities."), "Forcibly interrupt all running activities."),
help_text=_("Set all activities to finished state, " help_text=_("Set all activities to finished state, "
...@@ -980,7 +1283,6 @@ class DeployChoiceField(forms.ModelChoiceField): ...@@ -980,7 +1283,6 @@ class DeployChoiceField(forms.ModelChoiceField):
class VmDeployForm(OperationForm): class VmDeployForm(OperationForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices', None) choices = kwargs.pop('choices', None)
instance = kwargs.pop('instance', None) instance = kwargs.pop('instance', None)
...@@ -1125,7 +1427,6 @@ class CirclePasswordResetForm(PasswordResetForm): ...@@ -1125,7 +1427,6 @@ class CirclePasswordResetForm(PasswordResetForm):
class CircleSetPasswordForm(SetPasswordForm): class CircleSetPasswordForm(SetPasswordForm):
@property @property
def helper(self): def helper(self):
helper = FormHelper() helper = FormHelper()
...@@ -1136,7 +1437,6 @@ class CircleSetPasswordForm(SetPasswordForm): ...@@ -1136,7 +1437,6 @@ class CircleSetPasswordForm(SetPasswordForm):
class LinkButton(BaseInput): class LinkButton(BaseInput):
""" """
Used to create a link button descriptor for the {% crispy %} template tag:: Used to create a link button descriptor for the {% crispy %} template tag::
...@@ -1183,7 +1483,6 @@ class AnyTag(Div): ...@@ -1183,7 +1483,6 @@ class AnyTag(Div):
class WorkingBaseInput(BaseInput): class WorkingBaseInput(BaseInput):
def __init__(self, name, value, input_type="text", **kwargs): def __init__(self, name, value, input_type="text", **kwargs):
self.input_type = input_type self.input_type = input_type
self.field_classes = "" # we need this for some reason self.field_classes = "" # we need this for some reason
...@@ -1191,7 +1490,6 @@ class WorkingBaseInput(BaseInput): ...@@ -1191,7 +1490,6 @@ class WorkingBaseInput(BaseInput):
class TraitForm(forms.ModelForm): class TraitForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TraitForm, self).__init__(*args, **kwargs) super(TraitForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self) self.helper = FormHelper(self)
...@@ -1223,7 +1521,7 @@ class MyProfileForm(forms.ModelForm): ...@@ -1223,7 +1521,7 @@ class MyProfileForm(forms.ModelForm):
class Meta: class Meta:
fields = ('preferred_language', 'email_notifications', fields = ('preferred_language', 'email_notifications',
'use_gravatar', ) 'use_gravatar',)
model = Profile model = Profile
@property @property
...@@ -1238,9 +1536,8 @@ class MyProfileForm(forms.ModelForm): ...@@ -1238,9 +1536,8 @@ class MyProfileForm(forms.ModelForm):
class UnsubscribeForm(forms.ModelForm): class UnsubscribeForm(forms.ModelForm):
class Meta: class Meta:
fields = ('email_notifications', ) fields = ('email_notifications',)
model = Profile model = Profile
@property @property
...@@ -1251,7 +1548,6 @@ class UnsubscribeForm(forms.ModelForm): ...@@ -1251,7 +1548,6 @@ class UnsubscribeForm(forms.ModelForm):
class CirclePasswordChangeForm(PasswordChangeForm): class CirclePasswordChangeForm(PasswordChangeForm):
@property @property
def helper(self): def helper(self):
helper = FormHelper() helper = FormHelper()
...@@ -1392,10 +1688,9 @@ class ConnectCommandForm(forms.ModelForm): ...@@ -1392,10 +1688,9 @@ class ConnectCommandForm(forms.ModelForm):
class TraitsForm(forms.ModelForm): class TraitsForm(forms.ModelForm):
class Meta: class Meta:
model = Instance model = Instance
fields = ('req_traits', ) fields = ('req_traits',)
@property @property
def helper(self): def helper(self):
...@@ -1415,7 +1710,7 @@ class RawDataForm(forms.ModelForm): ...@@ -1415,7 +1710,7 @@ class RawDataForm(forms.ModelForm):
class Meta: class Meta:
model = Instance model = Instance
fields = ('raw_data', ) fields = ('raw_data',)
@property @property
def helper(self): def helper(self):
...@@ -1477,7 +1772,7 @@ class GroupPermissionForm(forms.ModelForm): ...@@ -1477,7 +1772,7 @@ class GroupPermissionForm(forms.ModelForm):
class Meta: class Meta:
model = Group model = Group
fields = ('permissions', ) fields = ('permissions',)
@property @property
def helper(self): def helper(self):
...@@ -1525,7 +1820,7 @@ class VmResourcesForm(forms.ModelForm): ...@@ -1525,7 +1820,7 @@ class VmResourcesForm(forms.ModelForm):
class Meta: class Meta:
model = Instance model = Instance
fields = ('num_cores', 'priority', 'ram_size', ) fields = ('num_cores', 'priority', 'ram_size',)
vm_search_choices = ( vm_search_choices = (
...@@ -1586,7 +1881,6 @@ class UserListSearchForm(forms.Form): ...@@ -1586,7 +1881,6 @@ class UserListSearchForm(forms.Form):
class DataStoreForm(ModelForm): class DataStoreForm(ModelForm):
@property @property
def helper(self): def helper(self):
helper = FormHelper() helper = FormHelper()
...@@ -1605,7 +1899,7 @@ class DataStoreForm(ModelForm): ...@@ -1605,7 +1899,7 @@ class DataStoreForm(ModelForm):
class Meta: class Meta:
model = DataStore model = DataStore
fields = ("name", "path", "hostname", ) fields = ("name", "path", "hostname",)
class DiskForm(ModelForm): class DiskForm(ModelForm):
...@@ -1623,7 +1917,7 @@ class DiskForm(ModelForm): ...@@ -1623,7 +1917,7 @@ class DiskForm(ModelForm):
class Meta: class Meta:
model = Disk model = Disk
fields = ("name", "filename", "datastore", "type", "bus", "size", fields = ("name", "filename", "datastore", "type", "bus", "size",
"base", "dev_num", "destroyed", "is_ready", ) "base", "dev_num", "destroyed", "is_ready",)
class MessageForm(ModelForm): class MessageForm(ModelForm):
......
$(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.db.models import (
CharField, permalink
)
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
from requests import ConnectionError
from common.operations import OperatedMixin
from pyVim.connect import SmartConnect, Disconnect
import pyVmomi
from pyVmomi import vim, vmodl
logger = getLogger(__name__)
def collect_properties(service_instance, view_ref, obj_type, path_set=None,
include_mors=False):
"""
Collect properties for managed objects from a view ref
Check the vSphere API documentation for example on retrieving
object properties:
- http://goo.gl/erbFDz
Args:
si (ServiceInstance): ServiceInstance connection
view_ref (pyVmomi.vim.view.*): Starting point of inventory navigation
obj_type (pyVmomi.vim.*): Type of managed object
path_set (list): List of properties to retrieve
include_mors (bool): If True include the managed objects
refs in the result
Returns:
A list of properties for the managed objects
"""
collector = service_instance.content.propertyCollector
# Create object specification to define the starting point of
# inventory navigation
obj_spec = pyVmomi.vmodl.query.PropertyCollector.ObjectSpec()
obj_spec.obj = view_ref
obj_spec.skip = True
# Create a traversal specification to identify the path for collection
traversal_spec = pyVmomi.vmodl.query.PropertyCollector.TraversalSpec()
traversal_spec.name = 'traverseEntities'
traversal_spec.path = 'view'
traversal_spec.skip = False
traversal_spec.type = view_ref.__class__
obj_spec.selectSet = [traversal_spec]
# Identify the properties to the retrieved
property_spec = pyVmomi.vmodl.query.PropertyCollector.PropertySpec()
property_spec.type = obj_type
if not path_set:
property_spec.all = True
property_spec.pathSet = path_set
# Add the object and property specification to the
# property filter specification
filter_spec = pyVmomi.vmodl.query.PropertyCollector.FilterSpec()
filter_spec.objectSet = [obj_spec]
filter_spec.propSet = [property_spec]
# Retrieve properties
props = collector.RetrieveContents([filter_spec])
data = []
for obj in props:
properties = {}
for prop in obj.propSet:
properties[prop.name] = prop.val
if include_mors:
properties['obj'] = obj.obj
data.append(properties)
return data
def get_container_view(service_instance, obj_type, container=None):
"""
Get a vSphere Container View reference to all objects of type 'obj_type'
It is up to the caller to take care of destroying the View when no longer
needed.
Args:
obj_type (list): A list of managed object types
Returns:
A container view ref to the discovered managed objects
"""
if not container:
container = service_instance.content.rootFolder
view_ref = service_instance.content.viewManager.CreateContainerView(
container=container,
type=obj_type,
recursive=True
)
return view_ref
class Cluster(OperatedMixin, TimeStampedModel):
"""A VMware cluster.
"""
name = CharField(max_length=50, unique=True,
verbose_name=_('name'),
help_text=_('Human readable name of cluster.'))
address = CharField(max_length=200,
verbose_name=_('address'),
help_text=_('The address of the vCenter.'))
username = CharField(max_length=200,
verbose_name=_('username'),
help_text=_('The username used for the connection.'),
default='')
password = CharField(max_length=200,
verbose_name=_('password'),
help_text=_('The password used for the connection.'),
default='')
class Meta:
app_label = 'vm'
db_table = 'vm_cluster'
@permalink
def get_absolute_url(self):
return 'dashboard.views.cluster-detail', None, {'pk': self.id}
def get_list_of_vms(self):
# return [
# {
# 'uuid': "5020222b-4d02-fe42-c630-af8325b384f9",
# 'name': "unmanaged_vm",
# 'cpu': 2,
# 'memory': 2048,
# 'state': "poweredOn",
# 'os': "linux",
# 'cluster_pk': 1,
# },
# ], [
# {
# 'uuid': "5020222b-4d02-ce42-c630-af8325b384f9",
# 'name': "managed_vm",
# 'cpu': 2,
# 'memory': 2048,
# 'state': "poweredOn",
# 'os': "linux",
# 'time_of_expiration': "12/31/2015",
# 'cluster_pk': 1,
# },
# ], [
# {
# 'uuid': "5020222b-4d02-de42-c630-af8325b384f9",
# 'name': "deleted_vm",
# 'cluster_pk': 1,
# },
# ], None
try:
unmanaged_vm_list = []
managed_vm_list = []
deleted_vm_list = []
si = SmartConnect(host=self.address,
user=self.username,
pwd=self.password,
port=443)
# the info to acquire from each vm
vm_properties = ["name", "config.instanceUuid", "config.hardware.numCPU",
"config.hardware.memoryMB", "summary.runtime.powerState",
"config.guestFullName"]
view = get_container_view(si, obj_type=[vim.VirtualMachine])
vm_data_from_vcenter = collect_properties(si, view_ref=view,
obj_type=vim.VirtualMachine,
path_set=vm_properties,
include_mors=True)
from vm.models import VMwareVMInstance
list_of_vcenter_vm_uuids = []
for curr_vcenter_vm in vm_data_from_vcenter:
list_of_vcenter_vm_uuids.append(curr_vcenter_vm["config.instanceUuid"])
state = {
"poweredOn": "powered on",
"poweredOff": "powered off",
"suspended": "suspended"
}.get(curr_vcenter_vm["summary.runtime.powerState"], "unknown")
vm_info = {
'uuid': curr_vcenter_vm["config.instanceUuid"],
'name': curr_vcenter_vm["name"],
'cpu': curr_vcenter_vm["config.hardware.numCPU"],
'memory': curr_vcenter_vm["config.hardware.memoryMB"],
'state': state,
'os': curr_vcenter_vm["config.guestFullName"],
'cluster_pk': self.pk,
}
if VMwareVMInstance.objects.filter(instanceUUID=curr_vcenter_vm["config.instanceUuid"]).count() == 0:
# vm is not managed
unmanaged_vm_list.extend([vm_info])
else:
# this vm is managed
# we may need to update our info in the database
curr_vm = VMwareVMInstance.objects.get(instanceUUID=curr_vcenter_vm["config.instanceUuid"])
curr_vm.name = vm_info["name"]
curr_vm.cpu_cores = vm_info["cpu"]
curr_vm.memory = vm_info["memory"]
curr_vm.operating_system = vm_info["os"]
curr_vm.save()
vm_info["owner"] = curr_vm.owner.username
managed_vm_list.extend([vm_info])
Disconnect(si)
for curr_managed_vm in VMwareVMInstance.objects.all():
if curr_managed_vm.instanceUUID not in list_of_vcenter_vm_uuids:
vm_info = {
'uuid': curr_managed_vm.instanceUUID,
'name': curr_managed_vm.name,
'cpu': curr_managed_vm.cpu_cores,
'memory': curr_managed_vm.memory_size,
'os': curr_managed_vm.operating_system,
'cluster_pk': self.pk,
'owner': curr_managed_vm.owner.username,
}
deleted_vm_list.extend([vm_info])
return unmanaged_vm_list, managed_vm_list, deleted_vm_list, None
except ConnectionError:
return None, None, None, "Connection to the cluster failed. Please check the connection settings."
except vim.fault.InvalidLogin as e:
return None, None, None, e.msg
def get_vm_details_by_uuid(self, uuid):
try:
si = SmartConnect(host=self.address,
user=self.username,
pwd=self.password,
port=443)
search_index = si.content.searchIndex
vm = search_index.FindByUuid(None, uuid, True, True)
state = {
"poweredOn": "powered on",
"poweredOff": "powered off",
"suspended": "suspended"
}.get(vm.summary.runtime.powerState, "unknown")
vm_info = {
'uuid': vm.summary.config.instanceUuid,
'name': vm.summary.config.name,
'cpu': vm.summary.config.numCpu,
'memory': vm.summary.config.memorySizeMB,
'state': state,
'os': vm.summary.config.guestFullName,
}
Disconnect(si)
return vm_info
except ConnectionError:
return None, "Connection to the cluster failed. Please check the connection settings."
except vim.fault.InvalidLogin as e:
return None, e.msg
def __unicode__(self):
return self.name
# 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