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
......
......@@ -52,8 +52,8 @@ from django.core.urlresolvers import reverse_lazy
from django_sshkey.models import UserKey
from firewall.models import Vlan, Host
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 django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Permission
......@@ -77,7 +77,6 @@ priority_choices = (
class NoFormTagMixin(object):
@property
def helper(self):
helper = FormHelper(self)
......@@ -187,7 +186,7 @@ class VmCustomizeForm(forms.Form):
self.initial['ram_size'] = self.template.ram_size
else:
self.allowed_fields = ("name", "template", "customized", )
self.allowed_fields = ("name", "template", "customized",)
# initial name and template pk
self.initial['name'] = self.template.name
......@@ -212,7 +211,6 @@ class VmCustomizeForm(forms.Form):
class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
description = forms.CharField(label=_("Description"), required=False,
widget=forms.Textarea(attrs={'rows': 3}))
......@@ -256,11 +254,10 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
class Meta:
model = Group
fields = ('name', )
fields = ('name',)
class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
new_groups = kwargs.pop('new_groups', None)
superuser = kwargs.pop('superuser', False)
......@@ -295,7 +292,6 @@ class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm):
class HostForm(NoFormTagMixin, forms.ModelForm):
def setowner(self, user):
self.instance.owner = user
......@@ -316,48 +312,48 @@ class HostForm(NoFormTagMixin, forms.ModelForm):
css_class="row",
),
Div( # host data
Div( # hostname
HTML('<label for="node-hostname-box">'
'Name'
'</label>'),
css_class="col-sm-3",
),
Div( # hostname
'hostname',
css_class="col-sm-9",
),
Div( # mac
HTML('<label for="node-mac-box">'
'MAC'
'</label>'),
css_class="col-sm-3",
),
Div(
'mac',
css_class="col-sm-9",
),
Div( # ip
HTML('<label for="node-ip-box">'
'IP'
'</label>'),
css_class="col-sm-3",
),
Div(
'ipv4',
css_class="col-sm-9",
),
Div( # vlan
HTML('<label for="node-vlan-box">'
'VLAN'
'</label>'),
css_class="col-sm-3",
),
Div(
'vlan',
css_class="col-sm-9",
),
css_class="row",
),
Div( # hostname
HTML('<label for="node-hostname-box">'
'Name'
'</label>'),
css_class="col-sm-3",
),
Div( # hostname
'hostname',
css_class="col-sm-9",
),
Div( # mac
HTML('<label for="node-mac-box">'
'MAC'
'</label>'),
css_class="col-sm-3",
),
Div(
'mac',
css_class="col-sm-9",
),
Div( # ip
HTML('<label for="node-ip-box">'
'IP'
'</label>'),
css_class="col-sm-3",
),
Div(
'ipv4',
css_class="col-sm-9",
),
Div( # vlan
HTML('<label for="node-vlan-box">'
'VLAN'
'</label>'),
css_class="col-sm-3",
),
Div(
'vlan',
css_class="col-sm-9",
),
css_class="row",
),
),
)
return helper
......@@ -368,7 +364,6 @@ class HostForm(NoFormTagMixin, forms.ModelForm):
class NodeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(NodeForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
......@@ -388,11 +383,11 @@ class NodeForm(forms.ModelForm):
),
Div(
Div( # nodename
HTML('<label for="node-nodename-box">'
'Name'
'</label>'),
css_class="col-sm-3",
),
HTML('<label for="node-nodename-box">'
'Name'
'</label>'),
css_class="col-sm-3",
),
Div(
'name',
css_class="col-sm-9",
......@@ -401,11 +396,11 @@ class NodeForm(forms.ModelForm):
),
Div(
Div( # priority
HTML('<label for="node-nodename-box">'
'Priority'
'</label>'),
css_class="col-sm-3",
),
HTML('<label for="node-nodename-box">'
'Priority'
'</label>'),
css_class="col-sm-3",
),
Div(
'priority',
css_class="col-sm-9",
......@@ -414,11 +409,11 @@ class NodeForm(forms.ModelForm):
),
Div(
Div( # enabled
HTML('<label for="node-nodename-box">'
'Enabled'
'</label>'),
css_class="col-sm-3",
),
HTML('<label for="node-nodename-box">'
'Enabled'
'</label>'),
css_class="col-sm-3",
),
Div(
'enabled',
css_class="col-sm-9",
......@@ -426,22 +421,22 @@ class NodeForm(forms.ModelForm):
css_class="row",
),
Div( # nested host
HTML("""{% load crispy_forms_tags %}
HTML("""{% load crispy_forms_tags %}
{% crispy hostform %}
""")
),
),
Div(
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="fa fa-play"
),
HTML("Start"),
css_id="node-create-submit",
css_class="btn btn-success",
),
"button",
AnyTag(
"i",
css_class="fa fa-play"
),
HTML("Start"),
css_id="node-create-submit",
css_class="btn btn-success",
),
css_class="col-sm-12 text-right",
),
css_class="row",
......@@ -457,6 +452,317 @@ class NodeForm(forms.ModelForm):
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):
networks = forms.ModelMultipleChoiceField(
queryset=None, required=False, label=_("Networks"))
......@@ -507,11 +813,11 @@ class TemplateForm(forms.ModelForm):
'name', 'access_method', 'description', 'system', 'tags',
'arch', 'lease', 'has_agent')
if (self.user.has_perm('vm.change_template_resources')
or not self.instance.pk):
or not self.instance.pk):
self.allowed_fields += tuple(set(self.fields.keys()) -
set(['raw_data']))
if self.user.is_superuser:
self.allowed_fields += ('raw_data', )
self.allowed_fields += ('raw_data',)
for name, field in self.fields.items():
if name not in self.allowed_fields:
field.widget.attrs['disabled'] = 'disabled'
......@@ -600,7 +906,7 @@ class TemplateForm(forms.ModelForm):
class Meta:
model = InstanceTemplate
exclude = ('state', 'disks', )
exclude = ('state', 'disks',)
widgets = {
'system': forms.TextInput,
'max_ram_size': forms.HiddenInput,
......@@ -609,7 +915,6 @@ class TemplateForm(forms.ModelForm):
class LeaseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LeaseForm, self).__init__(*args, **kwargs)
self.generate_fields()
......@@ -743,7 +1048,6 @@ class LeaseForm(forms.ModelForm):
class VmRenewForm(OperationForm):
force = forms.BooleanField(required=False, label=_(
"Set expiration times even if they are shorter than "
"the current value."))
......@@ -783,11 +1087,10 @@ class VmMigrateForm(forms.Form):
class VmStateChangeForm(OperationForm):
interrupt = forms.BooleanField(required=False, label=_(
"Forcibly interrupt all running activities."),
help_text=_("Set all activities to finished state, "
"but don't interrupt any tasks."))
help_text=_("Set all activities to finished state, "
"but don't interrupt any tasks."))
new_state = forms.ChoiceField(Instance.STATUS, label=_(
"New status"))
reset_node = forms.BooleanField(required=False, label=_("Reset node"))
......@@ -856,7 +1159,7 @@ class VmDiskResizeForm(OperationForm):
" GB or MB!"))
if int(size_in_bytes) < int(disk.size):
raise forms.ValidationError(_("Disk size must be greater than the "
"actual size."))
"actual size."))
return cleaned_data
@property
......@@ -980,7 +1283,6 @@ class DeployChoiceField(forms.ModelChoiceField):
class VmDeployForm(OperationForm):
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices', None)
instance = kwargs.pop('instance', None)
......@@ -1125,7 +1427,6 @@ class CirclePasswordResetForm(PasswordResetForm):
class CircleSetPasswordForm(SetPasswordForm):
@property
def helper(self):
helper = FormHelper()
......@@ -1136,7 +1437,6 @@ class CircleSetPasswordForm(SetPasswordForm):
class LinkButton(BaseInput):
"""
Used to create a link button descriptor for the {% crispy %} template tag::
......@@ -1183,7 +1483,6 @@ class AnyTag(Div):
class WorkingBaseInput(BaseInput):
def __init__(self, name, value, input_type="text", **kwargs):
self.input_type = input_type
self.field_classes = "" # we need this for some reason
......@@ -1191,7 +1490,6 @@ class WorkingBaseInput(BaseInput):
class TraitForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TraitForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
......@@ -1223,7 +1521,7 @@ class MyProfileForm(forms.ModelForm):
class Meta:
fields = ('preferred_language', 'email_notifications',
'use_gravatar', )
'use_gravatar',)
model = Profile
@property
......@@ -1238,9 +1536,8 @@ class MyProfileForm(forms.ModelForm):
class UnsubscribeForm(forms.ModelForm):
class Meta:
fields = ('email_notifications', )
fields = ('email_notifications',)
model = Profile
@property
......@@ -1251,7 +1548,6 @@ class UnsubscribeForm(forms.ModelForm):
class CirclePasswordChangeForm(PasswordChangeForm):
@property
def helper(self):
helper = FormHelper()
......@@ -1392,10 +1688,9 @@ class ConnectCommandForm(forms.ModelForm):
class TraitsForm(forms.ModelForm):
class Meta:
model = Instance
fields = ('req_traits', )
fields = ('req_traits',)
@property
def helper(self):
......@@ -1415,7 +1710,7 @@ class RawDataForm(forms.ModelForm):
class Meta:
model = Instance
fields = ('raw_data', )
fields = ('raw_data',)
@property
def helper(self):
......@@ -1477,7 +1772,7 @@ class GroupPermissionForm(forms.ModelForm):
class Meta:
model = Group
fields = ('permissions', )
fields = ('permissions',)
@property
def helper(self):
......@@ -1525,7 +1820,7 @@ class VmResourcesForm(forms.ModelForm):
class Meta:
model = Instance
fields = ('num_cores', 'priority', 'ram_size', )
fields = ('num_cores', 'priority', 'ram_size',)
vm_search_choices = (
......@@ -1586,7 +1881,6 @@ class UserListSearchForm(forms.Form):
class DataStoreForm(ModelForm):
@property
def helper(self):
helper = FormHelper()
......@@ -1605,7 +1899,7 @@ class DataStoreForm(ModelForm):
class Meta:
model = DataStore
fields = ("name", "path", "hostname", )
fields = ("name", "path", "hostname",)
class DiskForm(ModelForm):
......@@ -1623,7 +1917,7 @@ class DiskForm(ModelForm):
class Meta:
model = Disk
fields = ("name", "filename", "datastore", "type", "bus", "size",
"base", "dev_num", "destroyed", "is_ready", )
"base", "dev_num", "destroyed", "is_ready",)
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 {
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.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 @@
# 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