Commit c9fed898 by Őry Máté

merge siliconbrain/tmp

parents 6c98c553 6afddec8
...@@ -157,3 +157,40 @@ body { ...@@ -157,3 +157,40 @@ body {
#vm-list-rename-name { #vm-list-rename-name {
max-width: 100px; max-width: 100px;
} }
.label-tag {
/* pass */
}
#vm-details-tags-form {
margin-top: 15px;
max-width: 250px;
}
.vm-details-remove-tag {
color: white;
padding-left: 5px;
}
.vm-details-remove-tag:hover {
cursor: pointer;
color: black;
text-decoration: none;
}
/* small buttons for tags, copied from Bootstraps input-sm, bnt-sm */
.btn-tags {
padding: 3px 6px;
font-size: 11px;
line-height: 1.5;
border-radius: 3px;
}
.input-tags {
height: 22px;
padding: 2px 8px;
font-size: 11px;
line-height: 1.5;
border-radius: 3px;
}
/* --- */
...@@ -41,8 +41,15 @@ $(function () { ...@@ -41,8 +41,15 @@ $(function () {
window.location.hash = $(this).attr('href'); window.location.hash = $(this).attr('href');
}); });
if (window.location.hash) if (window.location.hash) {
if(window.location.hash.substring(1,4) == "ipv")
$("a[href=#network]").tab('show');
$("a[href=" + window.location.hash +"]").tab('show'); $("a[href=" + window.location.hash +"]").tab('show');
}
/* scroll to top if there is a message */
if($(".messagelist").children(".alert").length > 0)
$('body').animate({scrollTop: 0});
addSliderMiscs(); addSliderMiscs();
......
var ctrlDown, shiftDown = false;
var ctrlKey = 17;
var shiftKey = 16;
var selected = [];
$(function() {
$(document).keydown(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = true;
if (e.keyCode == shiftKey) shiftDown = true;
}).keyup(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = false;
if (e.keyCode == shiftKey) shiftDown = false;
});
$('.node-list-table tbody').find('tr').mousedown(function() {
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('node-list-selected')) {
selected.splice(selected.indexOf($(this).index()), 1);
} else {
selected.push($(this).index());
}
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1] + 1;
end = $(this).index();
if(start > end) {
var tmp = start - 1; start = end; end = tmp - 1;
}
for(var i = start; i <= end; i++) {
if(selected.indexOf(i) < 0) {
selected.push(i);
setRowColor($('.node-list-table tbody tr').eq(i));
}
}
}
} else {
$('.node-list-selected').removeClass('node-list-selected');
$(this).addClass('node-list-selected');
selected = [$(this).index()];
}
// reset btn disables
$('.node-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls
if(selected.length > 1) {
$('.node-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) {
$('.node-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
}
} else {
$('.node-list-group-control a').attr('disabled', true);
}
return false;
});
$('#node-list-group-migrate').click(function() {
console.log(collectIds(selected));
});
$('.node-list-details').popover({
'placement': 'auto',
'html': true,
'trigger': 'hover'
});
$('.node-list-connect').popover({
'placement': 'left',
'html': true,
'trigger': 'click'
});
$('tbody a').mousedown(function(e) {
// parent tr doesn't get selected when clicked
e.stopPropagation();
});
$('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) {
return false;
}
});
/* group actions */
/* select all */
$('#node-list-group-select-all').click(function() {
$('.node-list-table tbody tr').each(function() {
var index = $(this).index();
if(selected.indexOf(index) < 0) {
selected.push(index);
$(this).addClass('node-list-selected');
}
});
if(selected.length > 0)
$('.node-list-group-control a').attr('disabled', false);
return false;
});
/* mass vm delete */
$('#node-list-group-delete').click(function() {
text = "Are you sure you want to delete the selected VMs?";
random_vm_pk = $('.vm-delete').eq(0).data('vm-pk');
addModalConfirmation(text, random_vm_pk, massDeleteVm, false);
return false;
});
});
function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.node-list-table tbody tr').eq(rows[i]));
ids.push(div.prop('id').replace('vm-', ''));
}
return ids;
}
function setRowColor(row) {
if(!row.hasClass('vm-list-selected')) {
row.addClass('node-list-selected');
} else {
row.removeClass('node-list-selected');
}
}
...@@ -51,6 +51,30 @@ $(function() { ...@@ -51,6 +51,30 @@ $(function() {
}); });
return false; return false;
}); });
/* remove tag */
$('.vm-details-remove-tag').click(function() {
var to_remove = $.trim($(this).parent('div').text());
var clicked = $(this);
$.ajax({
type: 'POST',
url: location.href,
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re['message'].toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re['message'], 'danger');
}
});
return false;
});
}); });
function checkNewActivity() { function checkNewActivity() {
......
from django_tables2 import Table # A from django_tables2 import Table, A
from django_tables2.columns import TemplateColumn # LinkColumn from django_tables2.columns import LinkColumn, TemplateColumn, Column
from vm.models import Instance from vm.models import Instance, Node
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -40,3 +40,23 @@ class VmListTable(Table): ...@@ -40,3 +40,23 @@ class VmListTable(Table):
attrs = {'class': ('table table-bordered table-striped table-hover ' attrs = {'class': ('table table-bordered table-striped table-hover '
'vm-list-table')} 'vm-list-table')}
fields = ('pk', 'name', 'state', 'time_of_suspend', 'time_of_delete', ) fields = ('pk', 'name', 'state', 'time_of_suspend', 'time_of_delete', )
class NodeListTable(Table):
pk = Column(
verbose_name="ID",
)
name = LinkColumn(
'dashboard.views.node-detail',
args=[A('pk')],
attrs={'a': {'class': 'real-link'}}
)
class Meta:
model = Node
attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')}
fields = ('pk', 'name', 'host', 'enabled', 'created', 'modified',
'priority', 'overcommit', )
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
<a class="navbar-brand" href="/dashboard/">{% block header-site %}CIRCLE{% endblock %}</a> <a class="navbar-brand" href="/dashboard/">{% block header-site %}CIRCLE{% endblock %}</a>
<!-- temporarily --> <!-- temporarily -->
<a class="navbar-brand pull-right" href="/network/" style="color: white; font-size: 10px;">Network</a> <a class="navbar-brand pull-right" href="/network/" style="color: white; font-size: 10px;">Network</a>
<a class="navbar-brand pull-right" href="/admin/" style="color: white; font-size: 10px;">Admin</a>
<a class="navbar-brand pull-right" href="/login/?next={% url "dashboard.index" %}" style="color: white; font-size: 10px;">Login</a> <a class="navbar-brand pull-right" href="/login/?next={% url "dashboard.index" %}" style="color: white; font-size: 10px;">Login</a>
</div> </div>
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right toolbar"> <div class="pull-right toolbar">
<div class="btn-group"> <div class="btn-group">
<a href="#vm-graph-view" class="btn btn-default btn-xs"><i class="icon-dashboard"></i></a> <a href="#node-graph-view" class="btn btn-default btn-xs"><i class="icon-dashboard"></i></a>
<a href="#vm-list-view" class="btn btn-default btn-xs"><i class="icon-list"></i></a> <a href="#node-list-view" class="btn btn-default btn-xs"><i class="icon-list"></i></a>
</div> </div>
<span class="btn btn-default btn-xs infobtn" title="Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. "><i class="icon-info-sign"></i></span> <span class="btn btn-default btn-xs infobtn" title="Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. "><i class="icon-info-sign"></i></span>
</div> </div>
...@@ -12,35 +12,32 @@ ...@@ -12,35 +12,32 @@
<i class="icon-sitemap"></i> {% trans "Compute nodes" %} <i class="icon-sitemap"></i> {% trans "Compute nodes" %}
</h3> </h3>
</div> </div>
<div class="panel-body" id="vm-graph-view" style=""> <div class="panel-body" id="node-graph-view" style="">
<p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-width="60" data-height="60" data-readOnly="true" value="68"></p> <p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-width="60" data-height="60" data-readOnly="true" value="68"></p>
<p><span class="big"><big>13</big> running </span> <p><span class="big"><big>13</big> running </span>
+ <big>3</big> off + <big>3</big> missing</p> + <big>3</big> off + <big>3</big> missing</p>
<ul class="list-inline"> <ul class="list-inline">
<li class="label label-success"><i class="icon-play-sign"></i> szilva</li> {% for i in nodes %}
<li class="label label-success"><i class="icon-play-sign"></i> korte</li> <li class="label label-success"><i class="icon-{% if i.enabled == True %}play-sign{% else %}pause{% endif %}"></i>{{ i.name }}</li>
<li class="label label-success"><i class="icon-play-sign"></i> korte</li> {% endfor %}
<li class="label label-success"><i class="icon-play-sign"></i> cseresznye</li>
<li class="label label-success"><i class="icon-play-sign"></i> szilva</li>
<li class="label label-success"><i class="icon-play-sign"></i> szilva</li>
<li class="label label-success"><i class="icon-play-sign"></i> szilva</li>
<li class="label label-success"><i class="icon-play-sign"></i> cseresznye</li>
<li class="label label-success"><i class="icon-play-sign"></i> korte</li>
<li class="label label-success"><i class="icon-play-sign"></i> korte</li>
<li class="label label-success"><i class="icon-play-sign"></i> cseresznye</li>
<li class="label label-success"><i class="icon-play-sign"></i> cseresznye</li>
<li class="label label-success"><i class="icon-play-sign"></i> szilva</li>
<li class="label label-warning"><i class="icon-off"></i> szilva</li>
<li class="label label-warning"><i class="icon-off"></i> korte</li>
<li class="label label-warning"><i class="icon-off"></i> korte</li>
<li class="label label-danger"><i class="icon-question"></i> cseresznye</li>
<li class="label label-danger"><i class="icon-question"></i> cseresznye</li>
<li class="label label-danger"><i class="icon-question"></i> szilva</li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="text-right"> <div class="row">
<a class="btn btn-primary btn-xs"><i class="icon-chevron-sign-right"></i> <strong>13</strong> more </a> <div class="col-sm-6 col-xs-6 input-group input-group-sm">
<input type="text" class="form-control" placeholder="Search..." />
<div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary" title="search"><i class="icon-search"></i></button>
</div> </div>
</div> </div>
<div class="col-sm-6 text-right">
{% if more_nodes > 0 %}
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.node-list" %}">
<i class="icon-chevron-sign-right"></i> <strong>{{ more_nodes }}</strong> more
</a>
{% endif %}
<a class="btn btn-success btn-xs vm-create" href="{% url "dashboard.views.vm-create" %}"><i class="icon-plus-sign"></i> new </a>
</div>
</div>
</div>
</div> </div>
<style>
.row {
margin-bottom: 15px;
}
</style>
<form method="POST" action="/dashboard/vm/create/">
{% csrf_token %}
<div class="row">
<div class="col-sm-10">
<select class="select form-control" id="vm-create-template-select" name="template-pk">
<option value="-1">Choose a VM template</option>
{% for template in templates %}
<option value="{{ template.pk }}">{{ template.name}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<div class="col-sm-5">
<a class="btn btn-info vm-create-advanced-btn">Advanced <i class="vm-create-advanced-icon icon-caret-down"></i></a>
</div>
<div class="col-sm-5 text-right">
<button id="vm-create-submit" type="submit" class="btn btn-success "><i class="icon-play"></i> Start</button>
</div>
</div>
<div class="vm-create-advanced">
<div class="row">
<div class="col-sm-12">
<h2>Resources</h2>
</div>
<p class="row">
<div class="col-sm-3">
<label for="vm-cpu-priority-slider"><i class="icon-trophy"></i> CPU priority</label>
</div>
<div class="col-sm-9">
<input name="cpu-priority" type="text" id="vm-cpu-priority-slider" class="vm-slider" value="20" data-slider-min="0" data-slider-max="100" data-slider-step="1" data-slider-value="20" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> CPU count</label>
</div>
<div class="col-sm-9">
<input name="cpu-count" type="text" id="vm-cpu-count-slider" class="vm-slider" value="2" data-slider-min="0" data-slider-max="8" data-slider-step="1" data-slider-value="2" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="ram-slider"><i class="icon-ticket"></i> RAM amount</label>
</div>
<div class="col-sm-9">
<input name="ram-size" type="text" id="vm-ram-size-slider" class="vm-slider" value="512" data-slider-min="128" data-slider-max="4096" data-slider-step="128" data-slider-value="512" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/> MiB
</div>
</p>
</div>
<!-- disk -->
<div class="row">
<div class="col-sm-4">
<h2>Disks</h2>
</div>
<div class="col-sm-8" style="padding-top: 3px;">
<div class="js-hidden" style="padding-top: 15px; max-width: 450px;">
<select class="form-control" id="vm-create-disk-add-form" multiple name="disks">
{% for d in disks %}
<option value="{{ d.pk }}">{{ d.name }}</option>
{% endfor %}
</select>
</div>
<div class="no-js-hidden">
<h3 id="vm-create-disk-list">
No disks are added!
</h3>
<h3 id="vm-create-disk-add">
<div class="input-group" style="max-width: 330px;">
<select class="form-control" id="vm-create-disk-add-select">
<!-- options should be copied via js from above -->
</select>
<div class="input-group-btn">
<!--<input type="submit" value="Add to network" class="btn btn-success"/>-->
<a href="#" id="vm-create-disk-add-button" class="btn btn-success"><i class="icon-plus-sign"></i></a>
</div>
</div>
</h3>
</div>
</div>
</div>
<!-- network -->
<div class="row">
<div class="col-sm-4">
<h2>Network</h2>
</div>
<style>
/* temporary inline css for dev */
a.hover-black {
color:white;
}
.hover-black:hover {
color: black /*#d9534f*/;
text-decoration: none;
}
.no-js-hidden {
display: none;
}
</style>
<div class="col-sm-8" style="padding-top: 3px;">
<div class="js-hidden" style="padding-top: 15px; max-width: 450px;">
<h4>Managed networks</h4>
<select class="form-control" id="vm-create-network-add-managed" multiple name="managed-vlans">
{% for v in vlans %}
<option value="{{ v.pk }}">{{ v.name }}</option>
{% endfor %}
</select>
<h4>Unmanaged networks</h4>
<select class="form-control" id="vm-create-network-add-unmanaged" multiple name="unmanaged-vlans">
{% for v in vlans %}
<option value="{{ v.pk }}">{{ v.name }}</option>
{% endfor %}
</select>
</div>
<div class="no-js-hidden">
<h3 id="vm-create-network-list">
Not added to any network!
</h3>
<h3 id="vm-create-network-add">
<div class="input-group" style="max-width: 330px;">
<select class="form-control" id="vm-create-network-add-select">
<!-- options should be copied via js from above -->
</select>
<span class="input-group-addon">
<input id="vm-create-network-add-checkbox-managed" type="checkbox" title data-original-title="Managed network?" style="-webkit-transform: scale(1.4, 1.4); margin-top: 4px;" checked/>
</span>
<div class="input-group-btn">
<!--<input type="submit" value="Add to network" class="btn btn-success"/>-->
<a href="#" id="vm-create-network-add-button" class="btn btn-success"><i class="icon-plus-sign"></i></a>
</div>
</div>
</h3>
</div>
</div>
</div>
</div>
</form>
{% load i18n %}
<h3>{% trans "Activity" %}</h3>
<style>
.sub-timeline {
border-left: 3px solid green;
margin-left: 30px;
padding-left: 10px;
}
</style>
<div class="timeline">
{% for a in activity %}
<div class="activity" data-activity-id="{{ a.pk }}">
<span class="timeline-icon">
<i class="{% if not a.finished %} icon-refresh icon-spin {% else %}icon-plus{% endif %}"></i>
</span>
<strong>{{ a.get_readable_name }}</strong>
{{ a.started|date:"Y-m-d. H:i" }}, {{ a.user }}
{% if a.instanceactivity_set.count > 0 %}
<div class="sub-timeline">
{% for s in a.instanceactivity_set.all %}
<div data-activity-id="{{ s.pk }}" class="sub-activity">
{{ s.get_readable_name }} -
{% if s.finished %}
{{ s.finished|time:"H:i:s" }}
{% else %}
<i class="icon-refresh icon-spin" class="sub-activity-loading-icon"></i>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
<div><span class="timeline-icon timeline-warning"><i class="icon-remove"></i></span> <strong>Removing</strong> 2013-11-21 15:32</div>
<div><span class="timeline-icon timeline-warning"><i class="icon-pause"></i></span> <strong>Suspending</strong> 2013-09-21 15:32</div>
<div><span class="timeline-icon"><i class="icon-ellipsis-vertical" ></i></span> <strong>(now)</strong></div>
<div><span class="timeline-icon"><i class="icon-truck"></i></span> <strong>Migrated to mega5</strong> 2013-04-21 15:32, ABC123</div>
<div><span class="timeline-icon"><i class="icon-refresh"></i></span> <strong>Forced reboot</strong> 2013-04-21 15:32, ABC123</div>
<div><span class="timeline-icon"><i class="icon-plus"></i></span> <strong>Created</strong> 2013-04-21 15:32, ABC123</div>
</div>
{% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/vm-details.js"></script>
{% endblock %}
<div class="row">
<div class="col-md-4">
<dl>
<dt>System:</dt>
<dd><i class="icon-linux"></i> Uhu Binux Optikai Rendszer</dd>
<dt>Description:</dt>
<dd><small>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc est libero, hendrerit at posuere sed, molestie congue quam. </small></dd>
</dl>
</div>
<div class="col-md-8">
<img src="/static/grafikon.png" style="width:45%"/>
<img src="/static/grafikon.png" style="width:45%"/>
<img src="/static/grafikon.png" style="width:45%"/>
<img src="/static/grafikon.png" style="width:45%"/>
</div>
</div>
{% load i18n %}
<form id="vm-details-resources-form" method="POST" action="">
{% csrf_token %}
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.name }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Enabled" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.enabled }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Priority" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.priority }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "CPU cores" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.num_cores }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "RAM size" %}</label>
</div>
<div class="col-sm-9">
<label > {% widthratio node.ram_size 1024000 1 %} MB </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Traits" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.traits.all }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Tags" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.tags.all }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host owner" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.owner }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host IP" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.ipv4 }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Shared IP" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.shared_ip }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host vlan" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.vlan }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.groups.all }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.hostname }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.hostname }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host online" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.online }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="ram-slider"><i class="icon-ticket"></i> {% trans "RAM amount" %}</label>
</div>
<div class="col-sm-9">
<input name="ram-size" type="text" id="vm-ram-size-slider" class="vm-slider" value="{{ instance.ram_size }}" data-slider-min="128" data-slider-max="4096" data-slider-step="128" data-slider-value="{{ instance.ram_size }}" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/> MiB
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "CPU count" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host. }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-12">
<button type="submit" class="btn btn-success btn-sm" id="vm-details-resources-save"><i class="icon-save"></i> {% trans "Save resources" %}</button>
</div>
</p>
</form>
{% block extra_js %}
<style>
label {padding-top: 6px;}
</style>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="page-header">
<h1>{{ node.name }} <small>{{ node.get_connect_host }}</small></h1>
</div>
<div class="row">
<div class="col-md-4" id="vm-info-pane">
<div class="big">
<span class="label label-success">{{ node.enabled }}</span>
<div class="btn-group">
<button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="#"><i class="icon-refresh"></i> Flash</a></li>
<li><a href="#"><i class="icon-refresh"></i> Reboot</a></li>
<li><a href="#"><i class="icon-off"></i> Shutdown</a></li>
<li><a data-vm-pk="{{ node.pk }}" class="vm-delete" href="{% url "dashboard.views.delete-vm" pk=node.pk %}"><i class="icon-remove"></i> Discard</a></li>
</ul>
</div>
</div>
</div>
<div class="col-md-8" id="vm-detail-pane">
<div class="panel panel-default" id="vm-detail-panel">
<ul class="nav nav-pills panel-heading">
<li class="active">
<a href="#home" data-toggle="pill" class="text-center">
<i class="icon-compass icon-2x"></i><br>
{% trans "Home" %}</a></li>
<li>
<a href="#resources" data-toggle="pill" class="text-center">
<i class="icon-tasks icon-2x"></i><br>
{% trans "Resources" %}</a></li>
<li>
<a href="#activity" data-toggle="pill" class="text-center">
<i class="icon-time icon-2x"></i><br>
{% trans "Activity" %}</a></li>
</ul>
<div class="tab-content panel-body">
<div class="tab-pane active" id="home">{% include "dashboard/node-detail-home.html" %}</div>
<div class="tab-pane" id="resources">{% include "dashboard/node-detail-resources.html" %}</div>
<div class="tab-pane" id="activity">{% include "dashboard/node-detail-activity.html" %}</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<div class="alert alert-info">
Tip #1: you can select multiple vm instances while holding down the <strong>CTRL</strong> key!
</div>
<div class="alert alert-info">
Tip #2: if you want to select multiple instances by one click select an instance then hold down <strong>SHIFT</strong> key and select another one!
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="icon-desktop"></i> Your nodes</h3>
</div>
<div class="panel-body node-list-group-control">
<p>
<strong>Group actions</strong>
<button id="node-list-group-select-all" class="btn btn-info btn-xs">Select all</button>
<a class="btn btn-default btn-xs" id="node-list-group-migrate" disabled><i class="icon-truck"></i> Migrate</a>
<a disabled href="#" class="btn btn-default btn-xs"><i class="icon-refresh"></i> Reboot</a>
<a disabled href="#" class="btn btn-default btn-xs"><i class="icon-off"></i> Shutdown</a>
<a id="node-list-group-delete" disabled href="#" class="btn btn-danger btn-xs"><i class="icon-remove"></i> Discard</a>
</p>
</div>
<div class="panel-body">
{% render_table table %}
</div>
</div>
</div>
</div>
<style>
.popover {
max-width: 600px;
}
.node-list-selected, .node-list-selected td {
background-color: #e8e8e8 !important;
}
.node-list-selected:hover, .node-list-selected:hover td {
background-color: #d0d0d0 !important;
}
.node-list-selected td:first-child {
font-weight: bold;
}
.node-list-table-thin {
width: 10px;
}
.node-list-table-admin {
width: 130px;
}
</style>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/node-list.js"></script>
{% endblock %}
<div class="btn-group">
<button type="button" class="btn btn-xs btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="#"><i class="icon-refresh"></i> Reboot</a></li>
<li><a href="#"><i class="icon-off"></i> Shutdown</a></li>
<li><a data-node-pk="{{ record.pk }}" class="real-link node-delete" href="{% url "dashboard.views.delete-node" pk=record.pk %}?next={{ request.path }}"><i class="icon-remove"></i> Discard</a></li>
</ul>
</div>
<a class="btn btn-default btn-xs" title data-original-title="Migrate">
<i class="icon-truck"></i>
</a>
<a class="btn btn-default btn-xs" title data-original-title="Rename">
<i class="icon-pencil"></i>
</a>
<a href="#" class="btn btn-default btn-xs node-list-connect" data-toggle="popover"
data-content='
Belépés: <input style="width: 300px;" type="text" class="form-control" value="ssh xyz@vm.ik.bme.hu -p22312"/>
Jelszó: <input style="width: 300px;" type="text" class="form-control" value="asdfkicsiasdfkocsi"/>
'>Connect</a>
<a class="btn btn-info btn-xs node-list-details" href="#" data-toggle="popover"
data-content='
<h4>Quick details</h4>
<dl class="dl-horizontal">
<dt>Number of cores:</dt><dd>{{ record.num_cores }}</dd>
<dt>Memory:</dt> <dd>{{ record.ram_size }} Mebibytes</dd>
<dt>Architecture:</td><dd>{{ record.arch }}</dd>
</dl>
<dl>
<dt>IPv4 address:</dt><dd>{{ record.ipv4 }}10.9.8.7</dd>
<dt>IPv6 address:</dt><dd> 2001:2001:2001:2001:2001:2001::</dd>
<dt>DNS name:</dt><dd>1825.vm.ik.bme.hu</dd>
</ul>
'>Details</a>
<div id="node-{{ record.pk }}">{{ record.pk }}</div>
<tr>
<!--<td><input type="checkbox"/ class="vm-checkbox" id="vm-1825{{ c }}"></td>-->
<td>
<div id="vm-1{{ c }}">1{{ c }}</div>
</td>
<td><a href="" class="real-link">network-devenv</a></td>
<td>running</td>
<td>10 days</td>
<td>1 month</td>
<td>
<a class="btn btn-default btn-xs" title data-original-title="Migrate">
<i class="icon-truck"></i>
</a>
<a class="btn btn-default btn-xs" title data-original-title="Rename">
<i class="icon-pencil"></i>
</a>
<a href="#" class="btn btn-default btn-xs vm-list-connect" data-toggle="popover"
data-content='
Belépés: <input style="width: 300px;" type="text" class="form-control" value="ssh cloud@vm.ik.bme.hu -p22312"/>
Jelszó: <input style="width: 300px;" type="text" class="form-control" value="asdfkicsiasdfkocsi"/>
'>Connect</a>
</td>
<td>
<a class="btn btn-info btn-xs vm-list-details" href="#" data-toggle="popover"
data-content='
<h4>Quick details</h4>
<dl class="dl-horizontal">
<dt>Number of cores:</dt><dd>4</dd>
<dt>Memory:</dt> <dd>512 MB</dd>
<dt>Architecture:</td><dd>x86-64</dd>
</dl>
<dl>
<dt>IPv4 address:</dt><dd>10.9.8.7</dd>
<dt>IPv6 address:</dt><dd> 2001:2001:2001:2001:2001:2001::</dd>
<dt>DNS name:</dt><dd>1825.vm.ik.bme.hu</dd>
</ul>
'>Details</a>
</td>
<td>
<div class="btn-group">
<button type="button" class="btn btn-xs btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="#"><i class="icon-refresh"></i> Reboot</a></li>
<li><a href="#"><i class="icon-off"></i> Shutdown</a></li>
<li><a href="#"><i class="icon-remove"></i> Discard</a></li>
</ul>
</div>
</td>
</tr>
{% load i18n %}
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<dl> <dl>
...@@ -6,6 +7,34 @@ ...@@ -6,6 +7,34 @@
<dt>Description:</dt> <dt>Description:</dt>
<dd><small>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc est libero, hendrerit at posuere sed, molestie congue quam. </small></dd> <dd><small>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc est libero, hendrerit at posuere sed, molestie congue quam. </small></dd>
</dl> </dl>
<div style="font-weight: bold;">{% trans "Tags" %}</div>
<div id="vm-details-tags" style="margin-bottom: 20px;">
<div id="vm-details-tags-list">
{% if instance.tags.all %}
{% for t in instance.tags.all %}
<div class="label label-primary label-tag" style="display: inline-block">
{{ t }}
<a href="#" class="vm-details-remove-tag"><i class="icon-remove"></i></a>
</div>
{% endfor %}
{% else %}
<small>No tag added!</small>
{% endif %}
</div>
<form action="" method="POST">
{% csrf_token %}
<div class="input-group" id="vm-details-tags-form">
<input type="text" class="form-control input-sm input-tags" name="new_tag" id="vm-details-tags-input"/>
<!--<div class="input-group-addon">
<i class="icon-question"></i>
</div>-->
<div class="input-group-btn">
<input type="submit" class="btn btn-default btn-sm input-tags" value="{% trans "Add tag" %}"/>
</div>
</div>
</form>
</div><!-- id:vm-details-tags -->
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<img src="/static/grafikon.png" style="width:45%"/> <img src="/static/grafikon.png" style="width:45%"/>
......
<tfoot>
<tr>
<td style="vertical-align: middle;">
<i class="icon-plus"></i> <i class="icon-long-arrow-right"></i>
</td>
<td colspan="2">
<div class="input-group input-group-sm">
<input type="text" class="form-control" size="5" />
<span class="input-group-addon">/</span>
<select class="form-control"><option>tcp</option><option>udp</option></select>
</div>
</td>
<td>
<button type="submit" class="btn btn-success btn-sm">Add</button>
</td>
</tr>
</tfoot>
...@@ -12,9 +12,15 @@ Interfaces</h2> ...@@ -12,9 +12,15 @@ Interfaces</h2>
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
<dl> <dl>
<dt>IPv4 address:</dt> <dd>{{ i.host.ipv4 }}</dd> <dt>{% trans "IPv4 address" %}:</dt> <dd>{{ i.host.ipv4 }}</dd>
<dt>IPv6 address:</dt> <dd>{{ i.host.ipv6 }}</dd> <dt>{% trans "IPv6 address" %}:</dt> <dd>{{ i.host.ipv6 }}</dd>
<dt>DNS name:</dt> <dd>{{ i.host.get_fqdn }}</dd> <dt>{% trans "DNS name" %}:</dt> <dd>{{ i.host.get_fqdn }}</dd>
<dt>{% trans "Groups" %}:</dt>
<dd>
{% for g in i.host.groups.all %}
{{ g }}{% if not forloop.last %},{% endif %}
{% endfor %}
</dd>
</dl> </dl>
</div> </div>
<div class="col-md-7"> <div class="col-md-7">
...@@ -23,7 +29,7 @@ Interfaces</h2> ...@@ -23,7 +29,7 @@ Interfaces</h2>
<li><a href="#ipv6" data-toggle="pill" class="text-center">IPv6</a></li> <li><a href="#ipv6" data-toggle="pill" class="text-center">IPv6</a></li>
</ul> </ul>
<h4>Port access</h4> <h4>Port access</h4>
<div class="tab-content"> <div class="tab-content" style="padding-top: 10px;">
<div class="tab-pane active" id="ipv4"> <div class="tab-pane active" id="ipv4">
<table class="table table-striped rule-table"> <table class="table table-striped rule-table">
<thead> <thead>
...@@ -38,19 +44,28 @@ Interfaces</h2> ...@@ -38,19 +44,28 @@ Interfaces</h2>
</th></tr> </th></tr>
</thead> </thead>
<tbody> <tbody>
<!-- inline td width shall be replaced -->
{% for l in i.host.list_ports %}
{% if l.ipv4 %}
<tr>
<td>
{{ l.ipv4.host }}:{{ l.ipv4.port }}
</td>
<td style="width: 61px;"><i class="icon-long-arrow-right"></i></td>
<td style="width: 111px;">
{{ l.private }}/{{ l.proto }}
</td>
<td>
<a href="#" class="btn btn-link btn-xs" title="{% trans "Remove" %}"><i class="icon-remove"><span class="sr-only">{% trans "Remove" %}</span></i></a>
</td>
</tr>
{% endif %}
{% endfor %}
<tr><td>vm.ik.bme.hu:22620</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="ssh">22</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr> <tr><td>vm.ik.bme.hu:22620</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="ssh">22</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
<tr><td>vm.ik.bme.hu:22620</td><td><i class="icon-long-arrow-right"></i></td><td>12344/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr> <tr><td>vm.ik.bme.hu:22620</td><td><i class="icon-long-arrow-right"></i></td><td>12344/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
<tr><td>vm.ik.bme.hu:22620</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="http-alt">8080</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr> <tr><td>vm.ik.bme.hu:22620</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="http-alt">8080</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
</tbody> </tbody>
<tfoot> {% include "dashboard/vm-detail-network-port-add.html" %}
<tr><td><i class="icon-plus"></i> <i class="icon-long-arrow-right"></i></td><td colspan="2">
<div class="input-group input-group-sm">
<input type="text" class="form-control" size="5" />
<span class="input-group-addon">/</span>
<select class="form-control"><option>tcp</option><option>udp</option></select>
</div>
</td><td><button type="submit" class="btn btn-success btn-sm">Add</button></td></tr>
</tfoot>
</table> </table>
</div> <!-- /ipv4 --> </div> <!-- /ipv4 -->
<div class="tab-pane" id="ipv6"> <div class="tab-pane" id="ipv6">
...@@ -65,19 +80,28 @@ Interfaces</h2> ...@@ -65,19 +80,28 @@ Interfaces</h2>
</th></tr> </th></tr>
</thead> </thead>
<tbody> <tbody>
<!-- inline td width TODO not do it -->
{% for l in i.host.list_ports %}
{% if l.ipv6 %}
<tr>
<td>
{{ l.ipv6.host }}:{{ l.ipv6.port }}
</td>
<td style="width: 61px;"><i class="icon-long-arrow-right"></i></td>
<td style="width: 111px;">
{{ l.private }}/{{ l.proto }}
</td>
<td>
<a href="#" class="btn btn-link btn-xs" title="{% trans "Remove" %}"><i class="icon-remove"><span class="sr-only">{% trans "Remove" %}</span></i></a>
</td>
</tr>
{% endif %}
{% endfor %}
<tr><td>550.vm.ik.bme.hu:22</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="ssh">22</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr> <tr><td>550.vm.ik.bme.hu:22</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="ssh">22</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
<tr><td>550.vm.ik.bme.hu:12344</td><td><i class="icon-long-arrow-right"></i></td><td>12344/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr> <tr><td>550.vm.ik.bme.hu:12344</td><td><i class="icon-long-arrow-right"></i></td><td>12344/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
<tr><td>550.vm.ik.bme.hu:8080</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="http-alt">8080</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr> <tr><td>550.vm.ik.bme.hu:8080</td><td><i class="icon-long-arrow-right"></i></td><td><abbr title="http-alt">8080</abbr>/tcp</td><td><a href="#" class="btn btn-link btn-xs" title="remove"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
</tbody> </tbody>
<tfoot> {% include "dashboard/vm-detail-network-port-add.html" %}
<tr><td><i class="icon-plus"></i> <i class="icon-long-arrow-right"></i></td><td colspan="2">
<div class="input-group input-group-sm">
<input type="text" class="form-control" size="5" />
<span class="input-group-addon">/</span>
<select class="form-control"><option>tcp</option><option>udp</option></select>
</div>
</td><td><button type="submit" class="btn btn-success btn-sm">Add</button></td></tr>
</tfoot>
</table> </table>
</div> </div>
</div> </div>
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button> <button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="#" class="vm-details-rename-button" class="icon-pencil"></i> Rename</a></li> <li><a href="#" class="vm-details-rename-button"><i class="icon-pencil"></i> Rename</a></li>
<li><a href="#"><i class="icon-refresh"></i> Reboot</a></li> <li><a href="#"><i class="icon-refresh"></i> Reboot</a></li>
<li><a href="#"><i class="icon-off"></i> Shutdown</a></li> <li><a href="#"><i class="icon-off"></i> Shutdown</a></li>
<li><a data-vm-pk="{{ instance.pk }}" class="vm-delete" href="{% url "dashboard.views.delete-vm" pk=instance.pk %}"><i class="icon-remove"></i> Discard</a></li> <li><a data-vm-pk="{{ instance.pk }}" class="vm-delete" href="{% url "dashboard.views.delete-vm" pk=instance.pk %}"><i class="icon-remove"></i> Discard</a></li>
......
...@@ -3,7 +3,7 @@ from django.conf.urls import patterns, url ...@@ -3,7 +3,7 @@ from django.conf.urls import patterns, url
from vm.models import Instance from vm.models import Instance
from .views import ( from .views import (
IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView, IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView,
VmDelete, VmMassDelete, vm_activity) VmDelete, VmMassDelete, vm_activity, NodeList, NodeDetailView)
urlpatterns = patterns( urlpatterns = patterns(
'', '',
...@@ -21,5 +21,9 @@ urlpatterns = patterns( ...@@ -21,5 +21,9 @@ urlpatterns = patterns(
name="dashboard.views.delete-vm"), name="dashboard.views.delete-vm"),
url(r'^vm/mass-delete/', VmMassDelete.as_view(), url(r'^vm/mass-delete/', VmMassDelete.as_view(),
name='dashboard.view.mass-delete-vm'), name='dashboard.view.mass-delete-vm'),
url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity) url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity),
url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'),
url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(),
name='dashboard.views.node-detail'),
) )
...@@ -18,9 +18,9 @@ from django.utils.translation import ugettext as _ ...@@ -18,9 +18,9 @@ from django.utils.translation import ugettext as _
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from .tables import VmListTable from .tables import (VmListTable, NodeListTable)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate, from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
InstanceActivity) InstanceActivity, Node)
from firewall.models import Vlan from firewall.models import Vlan
from storage.models import Disk from storage.models import Disk
...@@ -43,6 +43,12 @@ class IndexView(TemplateView): ...@@ -43,6 +43,12 @@ class IndexView(TemplateView):
'more_instances': instances.count() - len(instances[:5]) 'more_instances': instances.count() - len(instances[:5])
}) })
nodes = Node.objects.all()
context.update({
'nodes': nodes[:1],
'more_nodes': nodes.count() - len(nodes[:1])
})
context.update({ context.update({
'running_vms': instances.filter(state='RUNNING'), 'running_vms': instances.filter(state='RUNNING'),
'running_vm_num': instances.filter(state='RUNNING').count(), 'running_vm_num': instances.filter(state='RUNNING').count(),
...@@ -104,10 +110,15 @@ class VmDetailView(CheckedDetailView): ...@@ -104,10 +110,15 @@ class VmDetailView(CheckedDetailView):
and request.POST.get('cpu-priority')): and request.POST.get('cpu-priority')):
return self.__set_resources(request) return self.__set_resources(request)
# this is usually not None so it should be the last
if request.POST.get('new_name'): if request.POST.get('new_name'):
return self.__set_name(request) return self.__set_name(request)
if request.POST.get('new_tag') is not None:
return self.__add_tag(request)
if request.POST.get("to_remove") is not None:
return self.__remove_tag(request)
def __set_resources(self, request): def __set_resources(self, request):
self.object = self.get_object() self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'): if not self.object.has_level(request.user, 'owner'):
...@@ -154,6 +165,50 @@ class VmDetailView(CheckedDetailView): ...@@ -154,6 +165,50 @@ class VmDetailView(CheckedDetailView):
return redirect(reverse_lazy("dashboard.views.detail", return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
def __add_tag(self, request):
new_tag = request.POST.get('new_tag')
self.object = self.get_object()
if len(new_tag) < 1:
message = u"Please input something!"
elif len(new_tag) > 20:
message = u"Tag name is too long!"
else:
self.object.tags.add(new_tag)
try:
messages.error(request, message)
except:
pass
return redirect(reverse_lazy("dashboard.views.detail",
kwargs={'pk': self.object.pk}))
def __remove_tag(self, request):
try:
to_remove = request.POST.get('to_remove')
self.object = self.get_object()
self.object.tags.remove(to_remove)
message = u"Success"
except: # note this won't really happen
message = u"Not success"
if request.is_ajax():
return HttpResponse(
json.dumps({'message': message}),
content_type="application=json"
)
class NodeDetailView(DetailView):
template_name = "dashboard/node-detail.html"
model = Node
def get_context_data(self, **kwargs):
context = super(NodeDetailView, self).get_context_data(**kwargs)
return context
class AclUpdateView(View, SingleObjectMixin): class AclUpdateView(View, SingleObjectMixin):
...@@ -237,6 +292,13 @@ class VmList(SingleTableView): ...@@ -237,6 +292,13 @@ class VmList(SingleTableView):
table_pagination = False table_pagination = False
class NodeList(SingleTableView):
template_name = "dashboard/node-list.html"
model = Node
table_class = NodeListTable
table_pagination = False
class VmCreate(TemplateView): class VmCreate(TemplateView):
def get_template_names(self): def get_template_names(self):
......
...@@ -9,6 +9,7 @@ celery = Celery('manager', backend='amqp', ...@@ -9,6 +9,7 @@ celery = Celery('manager', backend='amqp',
broker=getenv("AMQP_URI"), broker=getenv("AMQP_URI"),
include=['vm.tasks.local_tasks', include=['vm.tasks.local_tasks',
'vm.tasks.local_periodic_tasks', 'vm.tasks.local_periodic_tasks',
'vm.tasks.local_agent_tasks',
'storage.tasks.local_tasks', 'storage.tasks.local_tasks',
'firewall.tasks.local_tasks']) 'firewall.tasks.local_tasks'])
......
# flake8: noqa
from .activity import NodeActivity
from .activity import InstanceActivity
from .instance import InstanceActiveManager
from .instance import BaseResourceConfigModel
from .instance import NamedBaseResourceConfig
from .instance import VirtualMachineDescModel
from .instance import InstanceTemplate
from .instance import Instance
from .instance import post_state_changed
from .instance import pre_state_changed
from .network import InterfaceTemplate
from .network import Interface
from .node import Trait
from .node import Node
from .node import Lease
__all__ = [
'InstanceActivity', 'InstanceActiveManager', 'BaseResourceConfigModel',
'NamedBaseResourceConfig', 'VirtualMachineDescModel', 'InstanceTemplate',
'Instance', 'post_state_changed', 'pre_state_changed', 'InterfaceTemplate',
'Interface', 'Trait', 'Node', 'NodeActivity', 'Lease', ]
from __future__ import unicode_literals
from contextlib import contextmanager
from logging import getLogger
from django.db.models import ForeignKey
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from common.models import ActivityModel, activitycontextimpl
logger = getLogger(__name__)
class InstanceActivity(ActivityModel):
instance = ForeignKey('Instance', related_name='activity_log',
help_text=_('Instance this activity works on.'),
verbose_name=_('instance'))
class Meta:
ordering = ['-started', 'instance', '-id']
app_label = 'vm'
db_table = 'vm_instanceactivity'
def __unicode__(self):
if self.parent:
return '{}({})->{}'.format(self.parent.activity_code,
self.instance,
self.activity_code)
else:
return '{}({})'.format(self.activity_code,
self.instance)
def get_readable_name(self):
return self.activity_code.split('.')[-1].replace('_', ' ').capitalize()
@classmethod
def create(cls, code_suffix, instance, task_uuid=None, user=None):
act = cls(activity_code='vm.Instance.' + code_suffix,
instance=instance, parent=None, started=timezone.now(),
task_uuid=task_uuid, user=user)
act.save()
return act
def create_sub(self, code_suffix, task_uuid=None):
act = InstanceActivity(
activity_code=self.activity_code + '.' + code_suffix,
instance=self.instance, parent=self, started=timezone.now(),
task_uuid=task_uuid, user=self.user)
act.save()
return act
@contextmanager
def sub_activity(self, code_suffix, task_uuid=None):
act = self.create_sub(code_suffix, task_uuid)
return activitycontextimpl(act)
@contextmanager
def instance_activity(code_suffix, instance, task_uuid=None, user=None):
act = InstanceActivity.create(code_suffix, instance, task_uuid, user)
return activitycontextimpl(act)
class NodeActivity(ActivityModel):
node = ForeignKey('Node', related_name='activity_log',
help_text=_('Node this activity works on.'),
verbose_name=_('node'))
class Meta:
app_label = 'vm'
db_table = 'vm_nodeactivity'
@classmethod
def create(cls, code_suffix, node, task_uuid=None, user=None):
act = cls(activity_code='vm.Node.' + code_suffix,
node=node, parent=None, started=timezone.now(),
task_uuid=task_uuid, user=user)
act.save()
return act
def create_sub(self, code_suffix, task_uuid=None):
act = NodeActivity(
activity_code=self.activity_code + '.' + code_suffix,
node=self.node, parent=self, started=timezone.now(),
task_uuid=task_uuid, user=self.user)
act.save()
return act
@contextmanager
def sub_activity(self, code_suffix, task_uuid=None):
act = self.create_sub(code_suffix, task_uuid)
return activitycontextimpl(act)
@contextmanager
def node_activity(code_suffix, node, task_uuid=None, user=None):
act = InstanceActivity.create(code_suffix, node, task_uuid, user)
return activitycontextimpl(act)
from __future__ import unicode_literals from __future__ import unicode_literals
from contextlib import contextmanager
from datetime import timedelta from datetime import timedelta
from importlib import import_module
from logging import getLogger from logging import getLogger
from netaddr import EUI, mac_unix from importlib import import_module
import django.conf import django.conf
from django.contrib.auth.models import User from django.db.models import (Model, ForeignKey, ManyToManyField,
from django.core import signing
from django.db.models import (Model, FloatField, ForeignKey, ManyToManyField,
IntegerField, DateTimeField, BooleanField, IntegerField, DateTimeField, BooleanField,
TextField, CharField, permalink, Manager) TextField, CharField, permalink, Manager)
from django.contrib.auth.models import User
from django.core import signing
from django.dispatch import Signal from django.dispatch import Signal
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from celery.exceptions import TimeoutError
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from acl.models import AclBase from acl.models import AclBase
from common.models import ActivityModel, activitycontextimpl, method_cache
from firewall.models import Vlan, Host
from storage.models import Disk from storage.models import Disk
from .tasks import local_tasks, vm_tasks, net_tasks from ..tasks import local_tasks, vm_tasks
from .node import Node, Trait
from .network import Interface
from .activity import instance_activity
logger = getLogger(__name__) logger = getLogger(__name__)
pre_state_changed = Signal(providing_args=["new_state"])
post_state_changed = Signal(providing_args=["new_state"])
pwgen = User.objects.make_random_password pwgen = User.objects.make_random_password
scheduler = import_module(name=django.conf.settings.VM_SCHEDULER) scheduler = import_module(name=django.conf.settings.VM_SCHEDULER)
ARCHITECTURES = (('x86_64', 'x86-64 (64 bit)'),
('i686', 'x86 (32 bit)'))
ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS
ACCESS_METHODS = [(key, name) for key, (name, port, transport) ACCESS_METHODS = [(key, name) for key, (name, port, transport)
in ACCESS_PROTOCOLS.iteritems()] in ACCESS_PROTOCOLS.iteritems()]
ARCHITECTURES = (('x86_64', 'x86-64 (64 bit)'),
('i686', 'x86 (32 bit)'))
VNC_PORT_RANGE = (2000, 65536) # inclusive start, exclusive end VNC_PORT_RANGE = (2000, 65536) # inclusive start, exclusive end
pre_state_changed = Signal(providing_args=["new_state"])
post_state_changed = Signal(providing_args=["new_state"])
def find_unused_vnc_port(): def find_unused_vnc_port():
used = Instance.objects.values_list('vnc_port', flat=True) used = Instance.objects.values_list('vnc_port', flat=True)
...@@ -88,13 +86,6 @@ class NamedBaseResourceConfig(BaseResourceConfigModel, TimeStampedModel): ...@@ -88,13 +86,6 @@ class NamedBaseResourceConfig(BaseResourceConfigModel, TimeStampedModel):
return self.name return self.name
class Trait(Model):
name = CharField(max_length=50, verbose_name=_('name'))
def __unicode__(self):
return self.name
class VirtualMachineDescModel(BaseResourceConfigModel): class VirtualMachineDescModel(BaseResourceConfigModel):
"""Abstract base for virtual machine describing models. """Abstract base for virtual machine describing models.
...@@ -118,176 +109,6 @@ class VirtualMachineDescModel(BaseResourceConfigModel): ...@@ -118,176 +109,6 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
abstract = True abstract = True
class Node(TimeStampedModel):
"""A VM host machine, a hypervisor.
"""
name = CharField(max_length=50, unique=True,
verbose_name=_('name'),
help_text=_('Human readable name of node.'))
priority = IntegerField(verbose_name=_('priority'),
help_text=_('Node usage priority.'))
host = ForeignKey(Host, verbose_name=_('host'),
help_text=_('Host in firewall.'))
enabled = BooleanField(verbose_name=_('enabled'), default=False,
help_text=_('Indicates whether the node can '
'be used for hosting.'))
traits = ManyToManyField(Trait, blank=True,
help_text=_("Declared traits."),
verbose_name=_('traits'))
tags = TaggableManager(blank=True, verbose_name=_("tags"))
overcommit = FloatField(default=1.0, verbose_name=_("overcommit ratio"),
help_text=_("The ratio of total memory with "
"to without overcommit."))
class Meta:
permissions = ()
def __unicode__(self):
return self.name
@property
@method_cache(10, 5)
def online(self):
return self.remote_query(vm_tasks.ping, timeout=1, default=False)
@property
@method_cache(300)
def num_cores(self):
"""Number of CPU threads available to the virtual machines.
"""
return self.remote_query(vm_tasks.get_core_num)
@property
@method_cache(300)
def ram_size(self):
"""Bytes of total memory in the node.
"""
return self.remote_query(vm_tasks.get_ram_size)
@property
def ram_size_with_overcommit(self):
"""Bytes of total memory including overcommit margin.
"""
return self.ram_size * self.overcommit
def get_remote_queue_name(self, queue_id):
return self.host.hostname + "." + queue_id
def remote_query(self, task, timeout=30, raise_=False, default=None):
"""Query the given task, and get the result.
If the result is not ready in timeout secs, return default value or
raise a TimeoutError."""
r = task.apply_async(
queue=self.get_remote_queue_name('vm'), expires=timeout + 60)
try:
return r.get(timeout=timeout)
except TimeoutError:
if raise_:
raise
else:
return default
def update_vm_states(self):
domains = {}
for i in self.remote_query(vm_tasks.list_domains_info, timeout=5):
# [{'name': 'cloud-1234', 'state': 'RUNNING', ...}, ...]
try:
id = int(i['name'].split('-')[1])
except:
pass # name format doesn't match
else:
domains[id] = i['state']
instances = self.instance_set.order_by('id').values('id', 'state')
for i in instances:
try:
d = domains[i['id']]
except KeyError:
logger.info('Node %s update: instance %s missing from '
'libvirt', self, i['id'])
else:
if d != i['state']:
logger.info('Node %s update: instance %s state changed '
'(libvirt: %s, db: %s)',
self, i['id'], d, i['state'])
Instance.objects.get(id=i['id']).state_changed(d)
del domains[i['id']]
for i in domains.keys():
logger.info('Node %s update: domain %s in libvirt but not in db.',
self, i)
class NodeActivity(ActivityModel):
node = ForeignKey(Node, related_name='activity_log',
help_text=_('Node this activity works on.'),
verbose_name=_('node'))
@classmethod
def create(cls, code_suffix, node, task_uuid=None, user=None):
act = cls(activity_code='vm.Node.' + code_suffix,
node=node, parent=None, started=timezone.now(),
task_uuid=task_uuid, user=user)
act.save()
return act
def create_sub(self, code_suffix, task_uuid=None):
act = NodeActivity(
activity_code=self.activity_code + '.' + code_suffix,
node=self.node, parent=self, started=timezone.now(),
task_uuid=task_uuid, user=self.user)
act.save()
return act
@contextmanager
def sub_activity(self, code_suffix, task_uuid=None):
act = self.create_sub(code_suffix, task_uuid)
return activitycontextimpl(act)
@contextmanager
def node_activity(code_suffix, node, task_uuid=None, user=None):
act = InstanceActivity.create(code_suffix, node, task_uuid, user)
return activitycontextimpl(act)
class Lease(Model):
"""Lease times for VM instances.
Specifies a time duration until suspension and deletion of a VM
instance.
"""
name = CharField(max_length=100, unique=True,
verbose_name=_('name'))
suspend_interval_seconds = IntegerField(verbose_name=_('suspend interval'))
delete_interval_seconds = IntegerField(verbose_name=_('delete interval'))
class Meta:
ordering = ['name', ]
@property
def suspend_interval(self):
return timedelta(seconds=self.suspend_interval_seconds)
@suspend_interval.setter
def suspend_interval(self, value):
self.suspend_interval_seconds = value.seconds
@property
def delete_interval(self):
return timedelta(seconds=self.delete_interval_seconds)
@delete_interval.setter
def delete_interval(self, value):
self.delete_interval_seconds = value.seconds
def __unicode__(self):
return self.name
class InstanceTemplate(VirtualMachineDescModel, TimeStampedModel): class InstanceTemplate(VirtualMachineDescModel, TimeStampedModel):
"""Virtual machine template. """Virtual machine template.
...@@ -323,11 +144,13 @@ class InstanceTemplate(VirtualMachineDescModel, TimeStampedModel): ...@@ -323,11 +144,13 @@ class InstanceTemplate(VirtualMachineDescModel, TimeStampedModel):
disks = ManyToManyField(Disk, verbose_name=_('disks'), disks = ManyToManyField(Disk, verbose_name=_('disks'),
related_name='template_set', related_name='template_set',
help_text=_('Disks which are to be mounted.')) help_text=_('Disks which are to be mounted.'))
lease = ForeignKey(Lease, related_name='template_set', lease = ForeignKey('Lease', related_name='template_set',
verbose_name=_('lease'), verbose_name=_('lease'),
help_text=_('Expiration times.')) help_text=_('Expiration times.'))
class Meta: class Meta:
app_label = 'vm'
db_table = 'vm_instancetemplate'
ordering = ['name', ] ordering = ['name', ]
permissions = () permissions = ()
verbose_name = _('template') verbose_name = _('template')
...@@ -351,28 +174,6 @@ class InstanceTemplate(VirtualMachineDescModel, TimeStampedModel): ...@@ -351,28 +174,6 @@ class InstanceTemplate(VirtualMachineDescModel, TimeStampedModel):
return 'linux' return 'linux'
class InterfaceTemplate(Model):
"""Network interface template for an instance template.
If the interface is managed, a host will be created for it.
"""
vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
help_text=_('Network the interface belongs to.'))
managed = BooleanField(verbose_name=_('managed'), default=True,
help_text=_('If a firewall host (i.e. IP address '
'association) should be generated.'))
template = ForeignKey(InstanceTemplate, verbose_name=_('template'),
related_name='interface_set',
help_text=_('Template the interface '
'template belongs to.'))
class Meta:
permissions = ()
verbose_name = _('interface template')
verbose_name_plural = _('interface templates')
class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel): class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
"""Virtual machine instance. """Virtual machine instance.
...@@ -433,7 +234,7 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel): ...@@ -433,7 +234,7 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
disks = ManyToManyField(Disk, related_name='instance_set', disks = ManyToManyField(Disk, related_name='instance_set',
help_text=_("Set of mounted disks."), help_text=_("Set of mounted disks."),
verbose_name=_('disks')) verbose_name=_('disks'))
lease = ForeignKey(Lease, help_text=_("Preferred expiration periods.")) lease = ForeignKey('Lease', help_text=_("Preferred expiration periods."))
vnc_port = IntegerField(blank=True, default=None, null=True, vnc_port = IntegerField(blank=True, default=None, null=True,
help_text=_("TCP port where VNC console listens."), help_text=_("TCP port where VNC console listens."),
unique=True, verbose_name=_('vnc_port')) unique=True, verbose_name=_('vnc_port'))
...@@ -445,6 +246,8 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel): ...@@ -445,6 +246,8 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
active = InstanceActiveManager() active = InstanceActiveManager()
class Meta: class Meta:
app_label = 'vm'
db_table = 'vm_instance'
ordering = ['pk', ] ordering = ['pk', ]
verbose_name = _('instance') verbose_name = _('instance')
verbose_name_plural = _('instances') verbose_name_plural = _('instances')
...@@ -918,136 +721,3 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel): ...@@ -918,136 +721,3 @@ class Instance(AclBase, VirtualMachineDescModel, TimeStampedModel):
self.state = new_state self.state = new_state
self.save() self.save()
post_state_changed.send(sender=self, new_state=new_state) post_state_changed.send(sender=self, new_state=new_state)
class InstanceActivity(ActivityModel):
instance = ForeignKey(Instance, related_name='activity_log',
help_text=_('Instance this activity works on.'),
verbose_name=_('instance'))
class Meta:
ordering = ['-started', 'instance', '-id']
def __unicode__(self):
if self.parent:
return '{}({})->{}'.format(self.parent.activity_code,
self.instance,
self.activity_code)
else:
return '{}({})'.format(self.activity_code,
self.instance)
def get_readable_name(self):
return self.activity_code.split('.')[-1].replace('_', ' ').capitalize()
@classmethod
def create(cls, code_suffix, instance, task_uuid=None, user=None):
act = cls(activity_code='vm.Instance.' + code_suffix,
instance=instance, parent=None, started=timezone.now(),
task_uuid=task_uuid, user=user)
act.save()
return act
def create_sub(self, code_suffix, task_uuid=None):
act = InstanceActivity(
activity_code=self.activity_code + '.' + code_suffix,
instance=self.instance, parent=self, started=timezone.now(),
task_uuid=task_uuid, user=self.user)
act.save()
return act
@contextmanager
def sub_activity(self, code_suffix, task_uuid=None):
act = self.create_sub(code_suffix, task_uuid)
return activitycontextimpl(act)
@contextmanager
def instance_activity(code_suffix, instance, task_uuid=None, user=None):
act = InstanceActivity.create(code_suffix, instance, task_uuid, user)
return activitycontextimpl(act)
class Interface(Model):
"""Network interface for an instance.
"""
vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
related_name="vm_interface")
host = ForeignKey(Host, verbose_name=_('host'), blank=True, null=True)
instance = ForeignKey(Instance, verbose_name=_('instance'),
related_name='interface_set')
def __unicode__(self):
return 'cloud-' + str(self.instance.id) + '-' + str(self.vlan.vid)
@property
def mac(self):
try:
return self.host.mac
except:
return Interface.generate_mac(self.instance, self.vlan)
@classmethod
def generate_mac(cls, instance, vlan):
"""Generate MAC address for a VM instance on a VLAN.
"""
# MAC 02:XX:XX:XX:XX:XX
# \________/\__/
# VM ID VLAN ID
i = instance.id & 0xfffffff
v = vlan.vid & 0xfff
m = (0x02 << 40) | (i << 12) | v
return EUI(m, dialect=mac_unix)
def get_vmnetwork_desc(self):
return {
'name': self.__unicode__(),
'bridge': 'cloud',
'mac': str(self.mac),
'ipv4': str(self.host.ipv4) if self.host is not None else None,
'ipv6': str(self.host.ipv6) if self.host is not None else None,
'vlan': self.vlan.vid,
'managed': self.host is not None
}
def deploy(self, user=None, task_uuid=None):
net_tasks.create.apply_async(
args=[self.get_vmnetwork_desc()],
queue=self.instance.get_remote_queue_name('net'))
def destroy(self, user=None, task_uuid=None):
net_tasks.destroy.apply_async(
args=[self.get_vmnetwork_desc()],
queue=self.instance.get_remote_queue_name('net'))
@classmethod
def create(cls, instance, vlan, managed, owner=None):
"""Create a new interface for a VM instance to the specified VLAN.
"""
if managed:
host = Host()
host.vlan = vlan
# TODO change Host's mac field's type to EUI in firewall
host.mac = str(cls.generate_mac(instance, vlan))
host.hostname = instance.vm_name
# Get adresses from firewall
addresses = vlan.get_new_address()
host.ipv4 = addresses['ipv4']
host.ipv6 = addresses['ipv6']
host.owner = owner
host.save()
else:
host = None
iface = cls(vlan=vlan, host=host, instance=instance)
iface.save()
return iface
def save_as_template(self, instance_template):
"""Create a template based on this interface.
"""
i = InterfaceTemplate(vlan=self.vlan, managed=self.host is not None,
template=instance_template)
i.save()
return i
from __future__ import unicode_literals
from logging import getLogger
from netaddr import EUI, mac_unix
from django.db.models import Model, ForeignKey, BooleanField
from django.utils.translation import ugettext_lazy as _
from firewall.models import Vlan, Host
from ..tasks import net_tasks
logger = getLogger(__name__)
class InterfaceTemplate(Model):
"""Network interface template for an instance template.
If the interface is managed, a host will be created for it.
"""
vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
help_text=_('Network the interface belongs to.'))
managed = BooleanField(verbose_name=_('managed'), default=True,
help_text=_('If a firewall host (i.e. IP address '
'association) should be generated.'))
template = ForeignKey('InstanceTemplate', verbose_name=_('template'),
related_name='interface_set',
help_text=_('Template the interface '
'template belongs to.'))
class Meta:
app_label = 'vm'
db_table = 'vm_interfacetemplate'
permissions = ()
verbose_name = _('interface template')
verbose_name_plural = _('interface templates')
class Interface(Model):
"""Network interface for an instance.
"""
vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
related_name="vm_interface")
host = ForeignKey(Host, verbose_name=_('host'), blank=True, null=True)
instance = ForeignKey('Instance', verbose_name=_('instance'),
related_name='interface_set')
class Meta:
app_label = 'vm'
db_table = 'vm_interface'
def __unicode__(self):
return 'cloud-' + str(self.instance.id) + '-' + str(self.vlan.vid)
@property
def mac(self):
try:
return self.host.mac
except:
return Interface.generate_mac(self.instance, self.vlan)
@classmethod
def generate_mac(cls, instance, vlan):
"""Generate MAC address for a VM instance on a VLAN.
"""
# MAC 02:XX:XX:XX:XX:XX
# \________/\__/
# VM ID VLAN ID
i = instance.id & 0xfffffff
v = vlan.vid & 0xfff
m = (0x02 << 40) | (i << 12) | v
return EUI(m, dialect=mac_unix)
def get_vmnetwork_desc(self):
return {
'name': self.__unicode__(),
'bridge': 'cloud',
'mac': str(self.mac),
'ipv4': str(self.host.ipv4) if self.host is not None else None,
'ipv6': str(self.host.ipv6) if self.host is not None else None,
'vlan': self.vlan.vid,
'managed': self.host is not None
}
def deploy(self, user=None, task_uuid=None):
net_tasks.create.apply_async(
args=[self.get_vmnetwork_desc()],
queue=self.instance.get_remote_queue_name('net'))
def destroy(self, user=None, task_uuid=None):
net_tasks.destroy.apply_async(
args=[self.get_vmnetwork_desc()],
queue=self.instance.get_remote_queue_name('net'))
@classmethod
def create(cls, instance, vlan, managed, owner=None):
"""Create a new interface for a VM instance to the specified VLAN.
"""
if managed:
host = Host()
host.vlan = vlan
# TODO change Host's mac field's type to EUI in firewall
host.mac = str(cls.generate_mac(instance, vlan))
host.hostname = instance.vm_name
# Get adresses from firewall
addresses = vlan.get_new_address()
host.ipv4 = addresses['ipv4']
host.ipv6 = addresses['ipv6']
host.owner = owner
host.save()
else:
host = None
iface = cls(vlan=vlan, host=host, instance=instance)
iface.save()
return iface
def save_as_template(self, instance_template):
"""Create a template based on this interface.
"""
i = InterfaceTemplate(vlan=self.vlan, managed=self.host is not None,
template=instance_template)
i.save()
return i
from __future__ import unicode_literals
from datetime import timedelta
from logging import getLogger
from django.db.models import (
Model, CharField, IntegerField, ForeignKey, BooleanField, ManyToManyField,
FloatField,
)
from django.utils.translation import ugettext_lazy as _
from celery.exceptions import TimeoutError
from model_utils.models import TimeStampedModel
from taggit.managers import TaggableManager
from common.models import method_cache
from firewall.models import Host
from ..tasks import vm_tasks
logger = getLogger(__name__)
class Trait(Model):
name = CharField(max_length=50, verbose_name=_('name'))
class Meta:
app_label = 'vm'
db_table = 'vm_trait'
def __unicode__(self):
return self.name
class Node(TimeStampedModel):
"""A VM host machine, a hypervisor.
"""
name = CharField(max_length=50, unique=True,
verbose_name=_('name'),
help_text=_('Human readable name of node.'))
priority = IntegerField(verbose_name=_('priority'),
help_text=_('Node usage priority.'))
host = ForeignKey(Host, verbose_name=_('host'),
help_text=_('Host in firewall.'))
enabled = BooleanField(verbose_name=_('enabled'), default=False,
help_text=_('Indicates whether the node can '
'be used for hosting.'))
traits = ManyToManyField(Trait, blank=True,
help_text=_("Declared traits."),
verbose_name=_('traits'))
tags = TaggableManager(blank=True, verbose_name=_("tags"))
overcommit = FloatField(default=1.0, verbose_name=_("overcommit ratio"),
help_text=_("The ratio of total memory with "
"to without overcommit."))
class Meta:
app_label = 'vm'
db_table = 'vm_node'
permissions = ()
def __unicode__(self):
return self.name
@property
@method_cache(10, 5)
def online(self):
return self.remote_query(vm_tasks.ping, timeout=1, default=False)
@property
@method_cache(300)
def num_cores(self):
"""Number of CPU threads available to the virtual machines.
"""
return self.remote_query(vm_tasks.get_core_num)
@property
@method_cache(300)
def ram_size(self):
"""Bytes of total memory in the node.
"""
return self.remote_query(vm_tasks.get_ram_size)
@property
def ram_size_with_overcommit(self):
"""Bytes of total memory including overcommit margin.
"""
return self.ram_size * self.overcommit
def get_remote_queue_name(self, queue_id):
return self.host.hostname + "." + queue_id
def remote_query(self, task, timeout=30, raise_=False, default=None):
"""Query the given task, and get the result.
If the result is not ready in timeout secs, return default value or
raise a TimeoutError."""
r = task.apply_async(
queue=self.get_remote_queue_name('vm'), expires=timeout + 60)
try:
return r.get(timeout=timeout)
except TimeoutError:
if raise_:
raise
else:
return default
def update_vm_states(self):
domains = {}
for i in self.remote_query(vm_tasks.list_domains_info, timeout=5):
# [{'name': 'cloud-1234', 'state': 'RUNNING', ...}, ...]
try:
id = int(i['name'].split('-')[1])
except:
pass # name format doesn't match
else:
domains[id] = i['state']
instances = self.instance_set.order_by('id').values('id', 'state')
for i in instances:
try:
d = domains[i['id']]
except KeyError:
logger.info('Node %s update: instance %s missing from '
'libvirt', self, i['id'])
else:
if d != i['state']:
logger.info('Node %s update: instance %s state changed '
'(libvirt: %s, db: %s)',
self, i['id'], d, i['state'])
self.instance_set.get(id=i['id']).state_changed(d)
del domains[i['id']]
for i in domains.keys():
logger.info('Node %s update: domain %s in libvirt but not in db.',
self, i)
class Lease(Model):
"""Lease times for VM instances.
Specifies a time duration until suspension and deletion of a VM
instance.
"""
name = CharField(max_length=100, unique=True,
verbose_name=_('name'))
suspend_interval_seconds = IntegerField(verbose_name=_('suspend interval'))
delete_interval_seconds = IntegerField(verbose_name=_('delete interval'))
class Meta:
app_label = 'vm'
db_table = 'vm_lease'
ordering = ['name', ]
@property
def suspend_interval(self):
return timedelta(seconds=self.suspend_interval_seconds)
@suspend_interval.setter
def suspend_interval(self, value):
self.suspend_interval_seconds = value.seconds
@property
def delete_interval(self):
return timedelta(seconds=self.delete_interval_seconds)
@delete_interval.setter
def delete_interval(self, value):
self.delete_interval_seconds = value.seconds
def __unicode__(self):
return self.name
from manager.mancelery import celery
@celery.task(name='agent.change_password')
def change_password(vm, password):
pass
@celery.task(name='agent.restart_networking')
def restart_networking(vm):
pass
@celery.task(name='agent.set_time')
def set_time(vm, time):
pass
@celery.task(name='agent.set_hostname')
def set_hostname(vm, time):
pass
from manager.mancelery import celery
from vm.tasks.agent_tasks import (restart_networking, change_password,
set_time, set_hostname)
import time
@celery.task
def agent_started(vm):
from vm.models import Instance, instance_activity
instance = Instance.objects.get(id=int(vm.split('-')[-1]))
with instance_activity(code_suffix='agent', instance=instance) as act:
with act.sub_activity('starting'):
queue = "%s.agent" % instance.node.host.hostname
print queue
restart_networking.apply_async(queue=queue,
args=(vm, ))
change_password.apply_async(queue=queue,
args=(vm, instance.pw))
set_time.apply_async(queue=queue,
args=(vm, time.time()))
set_hostname.apply_async(queue=queue,
args=(vm, instance.primary_host.hostname))
@celery.task
def agent_stopped(vm):
from vm.models import Instance, InstanceActivity
instance = Instance.objects.get(id=int(vm.split('-')[-1]))
qs = InstanceActivity.objects.filter(instance=instance,
activity_code='vm.Instance.agent')
act = qs.latest('id')
with act.sub_activity('stopping'):
pass
@celery.task
def agent_ok(vm):
from vm.models import Instance, InstanceActivity
instance = Instance.objects.get(id=int(vm.split('-')[-1]))
qs = InstanceActivity.objects.filter(instance=instance,
activity_code='vm.Instance.agent')
act = qs.latest('id')
with act.sub_activity('ok'):
pass
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