Commit f2a517de by Őry Máté

Merge branch 'master' into acl

Conflicts:
	circle/dashboard/urls.py
	circle/dashboard/views.py
	circle/vm/models.py
parents 6b1e9548 34b69cf4
from django.contrib.auth.models import User
from django.db.models import (CharField, DateTimeField, ForeignKey, TextField)
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
def activitycontextimpl(act):
try:
yield act
except Exception as e:
act.finish(str(e))
raise e
else:
act.finish()
class ActivityModel(TimeStampedModel):
activity_code = CharField(max_length=100, verbose_name=_('activity code'))
parent = ForeignKey('self', blank=True, null=True)
task_uuid = CharField(blank=True, max_length=50, null=True, unique=True,
help_text=_('Celery task unique identifier.'),
verbose_name=_('task_uuid'))
user = ForeignKey(User, blank=True, null=True, verbose_name=_('user'),
help_text=_('The person who started this activity.'))
started = DateTimeField(verbose_name=_('started at'),
blank=True, null=True,
help_text=_('Time of activity initiation.'))
finished = DateTimeField(verbose_name=_('finished at'),
blank=True, null=True,
help_text=_('Time of activity finalization.'))
result = TextField(verbose_name=_('result'), blank=True, null=True,
help_text=_('Human readable result of activity.'))
class Meta:
abstract = True
def finish(self, result=None):
if not self.finished:
self.finished = timezone.now()
self.result = result
self.save()
/*!
* Slider for Bootstrap
*
* Copyright 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.slider {
display: inline-block;
vertical-align: middle;
position: relative;
&.slider-horizontal {
width: 210px;
height: @baseLineHeight;
.slider-track {
height: @baseLineHeight/2;
width: 100%;
margin-top: -@baseLineHeight/4;
top: 50%;
left: 0;
}
.slider-selection {
height: 100%;
top: 0;
bottom: 0;
}
.slider-handle {
margin-left: -@baseLineHeight/2;
margin-top: -@baseLineHeight/4;
&.triangle {
border-width: 0 @baseLineHeight/2 @baseLineHeight/2 @baseLineHeight/2;
width: 0;
height: 0;
border-bottom-color: #0480be;
margin-top: 0;
}
}
}
&.slider-vertical {
height: 210px;
width: @baseLineHeight;
.slider-track {
width: @baseLineHeight/2;
height: 100%;
margin-left: -@baseLineHeight/4;
left: 50%;
top: 0;
}
.slider-selection {
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
.slider-handle {
margin-left: -@baseLineHeight/4;
margin-top: -@baseLineHeight/2;
&.triangle {
border-width: @baseLineHeight/2 0 @baseLineHeight/2 @baseLineHeight/2;
width: 1px;
height: 1px;
border-left-color: #0480be;
margin-left: 0;
}
}
}
input {
display: none;
}
.tooltip-inner {
white-space: nowrap;
}
}
.slider-track {
position: absolute;
cursor: pointer;
#gradient > .vertical(#f5f5f5, #f9f9f9);
.box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
.border-radius(@baseBorderRadius);
}
.slider-selection {
position: absolute;
#gradient > .vertical(#f9f9f9, #f5f5f5);
.box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
.box-sizing(border-box);
.border-radius(@baseBorderRadius);
}
.slider-handle {
position: absolute;
width: @baseLineHeight;
height: @baseLineHeight;
#gradient > .vertical(#149bdf, #0480be);
.box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)");
opacity: 0.8;
border: 0px solid transparent;
&.round {
.border-radius(@baseLineHeight);
}
&.triangle {
background: transparent none;
}
}
\ No newline at end of file
/*!
* Slider for Bootstrap
*
* Copyright 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.slider {
display: inline-block;
vertical-align: middle;
position: relative;
}
.slider.slider-horizontal {
width: 210px;
height: 20px;
}
.slider.slider-horizontal .slider-track {
height: 10px;
width: 100%;
margin-top: -5px;
top: 50%;
left: 0;
}
.slider.slider-horizontal .slider-selection {
height: 100%;
top: 0;
bottom: 0;
}
.slider.slider-horizontal .slider-handle {
margin-left: -10px;
margin-top: -5px;
}
.slider.slider-horizontal .slider-handle.triangle {
border-width: 0 10px 10px 10px;
width: 0;
height: 0;
border-bottom-color: #0480be;
margin-top: 0;
}
.slider.slider-vertical {
height: 210px;
width: 20px;
}
.slider.slider-vertical .slider-track {
width: 10px;
height: 100%;
margin-left: -5px;
left: 50%;
top: 0;
}
.slider.slider-vertical .slider-selection {
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
.slider.slider-vertical .slider-handle {
margin-left: -5px;
margin-top: -10px;
}
.slider.slider-vertical .slider-handle.triangle {
border-width: 10px 0 10px 10px;
width: 1px;
height: 1px;
border-left-color: #0480be;
margin-left: 0;
}
.slider input {
display: none;
}
.slider .tooltip-inner {
white-space: nowrap;
}
.slider-track {
position: absolute;
cursor: pointer;
background-color: #f7f7f7;
background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.slider-selection {
position: absolute;
background-color: #f7f7f7;
background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5));
background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5);
background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5);
background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.slider-handle {
position: absolute;
width: 20px;
height: 20px;
background-color: #0e90d2;
background-image: -moz-linear-gradient(top, #149bdf, #0480be);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
background-image: -o-linear-gradient(top, #149bdf, #0480be);
background-image: linear-gradient(to bottom, #149bdf, #0480be);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
opacity: 0.8;
border: 0px solid transparent;
}
.slider-handle.round {
-webkit-border-radius: 20px;
-moz-border-radius: 20px;
border-radius: 20px;
}
.slider-handle.triangle {
background: transparent none;
}
/* custom */
.slider-handle, .slider-handle:hover {
background-color: #3071a9;
opacity: 1;
width: 8px;
height: 26px;
margin-top: -4px!important;
margin-left: -6px !important;
border-radius: 0px;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
text-shadow: 0 1px 0 #fff;
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
background-repeat: repeat-x;
border-color: #2d6ca2;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
}
.slider-handle:hover {
background: #428bca;
background-image: none;
border-color: #2d6ca2;
}
.slider-track, .slider-selection {
height: 20px !important;
background: #ccc;
background: -webkit-linear-gradient(top, #bbb, #ddd);
background: -moz-linear-gradient(top, #bbb, #ddd);
background: linear-gradient(top, #bbb, #ddd);
-webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
border: 1px solid #aaa;
}
.slider-selection {
background-color: #8DCA09;
background: -webkit-linear-gradient(top, #8DCA09, #72A307);
background: -moz-linear-gradient(top, #8DCA09, #72A307);
background: linear-gradient(top, #8DCA09, #72A307);
border-color: #496805;
}
.vm-slider {
width: 300px;
}
.output {
padding-left: 10px;
font-weight: bold;
}
$(function () {
$('.vm-create').click(function(e) {
$.ajax({
type: 'GET',
url: '/dashboard/vm/create/',
success: function(data) {
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#vm-create-modal').modal('show');
$('#vm-create-modal').on('hidden.bs.modal', function() {
$('#vm-create-modal').remove();
});
}
});
return false;
});
$('[href=#vm-graph-view]').click(function (e) {
$('#vm-list-view').hide();
$('#vm-graph-view').show();
......@@ -18,26 +34,31 @@ $(function () {
$('[title]').tooltip();
$(':input[title]').tooltip({trigger: 'focus', placement: 'auto right'});
$(".knob").knob();
$("[data-slider]")
.each(function () {
var input = $(this);
$("<span>")
.addClass("output")
.html($(this).val())
.insertAfter(input);
})
.bind("slider:ready slider:changed", function (event, data) {
$(this)
.nextAll(".output:first")
.html(data.value.toFixed(3));
});
$("[data-mark]")
.each(function () {
var value=$(this).attr('data-mark').parseFloat();
if (window.location.hash)
$("a[href=" + window.location.hash +"]").tab('show');
});
if (window.location.hash)
$("a[href=" + window.location.hash +"]").tab('show');
addSliderMiscs();
});
function addSliderMiscs() {
$('.vm-slider').each(function() {
$("<span>").addClass("output").html($(this).val()).insertAfter($(this));
});
$('.vm-slider').slider()
.on('slide', function(e) {
$(this).val(e.value);
$(this).parent('div').nextAll("span").html(e.value)
});
refreshSliders();
}
// ehhh
function refreshSliders() {
$('.vm-slider').each(function() {
$(this).val($(this).slider().data('slider').getValue());
$(this).parent('div').nextAll("span").html($(this).val());
});
}
......@@ -15,9 +15,8 @@
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
<link href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet">
<script src="{{ STATIC_URL }}dashboard/js/jquery.knob.js"></script>
<link href="{{ STATIC_URL }}dashboard/loopj-jquery-simple-slider-fa64f59/css/simple-slider.css" rel="stylesheet">
<script src="{{ STATIC_URL }}dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js"></script>
<script src="{{ STATIC_URL}}dashboard/bootstrap-slider/bootstrap-slider.js"></script>
<link rel="stylesheet" href="{{ STATIC_URL }}dashboard/bootstrap-slider/slider.css"/>
<link href="{{ STATIC_URL }}dashboard/dashboard.css" rel="stylesheet">
<script src="{{ STATIC_URL }}dashboard/dashboard.js"></script>
</head>
......@@ -39,9 +38,6 @@
</div> <!-- /container -->
</body>
<script>
{% block extra_js %}
{% endblock %}
</script>
</html>
......@@ -15,51 +15,43 @@
<div class="list-group" id="vm-list-view">
{% for i in instances %}
<a href="{{ i.get_absolute_url }}" class="list-group-item">
<i class="icon-play-sign"></i> {{ i.name }} <div class="pull-right"><i class="icon-star text-primary" title="Mark as favorite."></i></div>
<i class="icon-{% if i.state == "RUNNING" %}play-sign{% else %}pause{% endif %}"></i> {{ i.name }} <div class="pull-right"><i class="icon-star text-primary" title="Mark as favorite."></i></div>
</a>
{% endfor %}
<a href="vm/443" class="list-group-item">
<i class="icon-play-sign"></i> ALMA <div class="pull-right"><i class="icon-star text-primary" title="Mark as favorite."></i></div>
</a>
<a href="#" class="list-group-item">
<i class="icon-play-sign"></i> ALMA <div class="pull-right"><i class="icon-star text-primary" title="Mark as favorite."></i></div>
</a>
<a href="#" class="list-group-item">
<i class="icon-play-sign"></i> ALMA <div class="pull-right"><i class="icon-star text-primary" title="Mark as favorite."></i></div>
</a>
<a href="#" class="list-group-item">
<i class="icon-off"></i> ALMA <div class="pull-right"><i class="icon-star-empty text-muted"></i></div>
</a>
<div href="#" class="list-group-item list-group-footer">
<div class="row">
<div class="col-sm-6 input-group input-group-sm">
<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 class="col-sm-6 text-right">
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.vm-list" %}"><i class="icon-chevron-sign-right"></i> <strong>13</strong> more </a>
<a class="btn btn-success btn-xs"><i class="icon-plus-sign"></i> new </a>
{% if more_instances > 0 %}
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.vm-list" %}">
<i class="icon-chevron-sign-right"></i> <strong>{{ more_instances }}</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 class="panel-body" id="vm-graph-view" style="display: none">
<p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-width="100" data-height="100" data-readOnly="true" value="22"></p>
<p><span class="bigbig"><big>13</big> running </span>
<p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-max="10" data-width="100" data-height="100" data-readOnly="true" value="{{ instances|length|add:more_instances }}"></p>
<p><span class="bigbig"><big>{{ running_vm_num }}</big> running </span>
<ul class="list-inline">
<li class="label label-success"><i class="icon-play-sign"></i> szilva</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>
{% for vm in running_vms %}
<li class="label label-success"><i class="icon-play-sign"></i> {{ vm.name}}</li>
{% endfor %}
</ul>
</p>
<p class="big text-warning"><big>10</big> stopped</p>
<p class="big text-warning"><big>{{ stopped_vm_num }}</big> stopped</p>
<p>Total: $3000 </p>
<div class="clearfix"></div>
<div class="text-right">
<a class="btn btn-primary btn-xs"><i class="icon-chevron-sign-right"></i> <strong>13</strong> more </a>
<a href="{% url "dashboard.views.vm-list" %}" class="btn btn-primary btn-xs"><i class="icon-chevron-sign-right"></i> <strong>{{ instances|length|add:more_instances }}</strong> machines total</a>
</div>
</div>
</div>
......@@ -28,6 +28,8 @@
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/vm-create.js"></script>
{% endblock %}
<div class="modal fade" id="vm-create-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{{ box_title }}</h4>
</div>
<div class="modal-body">
{% include template %}
</div>
<!--<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>-->
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% extends "dashboard/base.html" %}
{% block content %}
<div class="body-content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
{{ box_title }}
</h3>
</div>
<div class="panel-body">
{% include template %}
</div>
</div>
{% endblock %}
{% block extra_js %}
{% if template == "dashboard/vm-create.html" %}
<script src="{{ STATIC_URL }}dashboard/vm-create.js"></script>
{% endif %}
{% endblock %}
<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>
{% include "dashboard/vm-detail-resources.html" %}
</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>
<p class="row">
<div class="col-sm-3">
<label for="cpu-priority-slider"><i class="icon-trophy"></i> CPU priority</label>
<label for="vm-cpu-priority-slider"><i class="icon-trophy"></i> CPU priority</label>
</div>
<div class="col-sm-9">
<input type="text" data-slider="true" data-slider-highlight="true"
data-slider-snap="true" data-slider-range="0,100" data-slider-step="1" id="cpu-priority-slider" value="{{ instance.priority }}">
<input name="cpu-priority" type="text" id="vm-cpu-priority-slider" class="vm-slider" value="{{ instance.priority }}" data-slider-min="0" data-slider-max="100" data-slider-step="1" data-slider-value="{{ instance.priority }}" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/>
</div>
</p>
......@@ -14,7 +13,7 @@
<label for="cpu-count-slider"><i class="icon-cogs"></i> CPU count</label>
</div>
<div class="col-sm-9">
<input type="text" value="{{ instance.num_cores }}" data-slider="true" data-slider-highlight="true" data-slider-range="0,8" data-slider-snap="true" data-slider-step="1" id="cpu-count-slider">
<input name="cpu-count" type="text" id="vm-cpu-count-slider" class="vm-slider" value=" {{ instance.num_cores }}" data-slider-min="0" data-slider-max="8" data-slider-step="1" data-slider-value="{{ instance.num_cores }}" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/>
</div>
</p>
......@@ -23,8 +22,13 @@
<div class="col-sm-3">
<label for="ram-slider"><i class="icon-ticket"></i> RAM amount</label>
</div>
<div class="col-sm-9">
<input type="text" data-slider="true" data-slider-highlight="true" id="ram-slider" data-slider-range="128,4096" data-slider-snap="true" data-slider-step="128" value="{{ instance.ram_size }}"> MiB
</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>
{% block extra_js %}
<style>
label {padding-top: 6px;}
</style>
{% endblock %}
......@@ -39,8 +39,8 @@
<a href="#resources" data-toggle="pill" class="text-center">
<i class="icon-tasks icon-2x"></i><br>
{% trans "Resources" %}</a></li>
<li>
<a href="#console" data-toggle="pill" class="text-center">
<li {% if instance.state != "RUNNING" %}class="disabled"{% endif %}>
<a href="#{% if instance.state == "RUNNING" %}console" data-toggle="pill{% endif %}" class="text-center">
<i class="icon-desktop icon-2x"></i><br>
{% trans "Console" %}</a></li>
<li>
......
......@@ -28,47 +28,12 @@
</p>
</div>
<div class="panel-body">
<table class="table table-bordered table-striped table-hover vm-list-table">
<thead>
<tr>
<!--<td style="width: 10px;"><input type="checkbox" id="check_all"/></td> -->
<td style="width: 10px;">Id</td>
<td>Name</td>
<td>State</td>
<td>Suspend in</td>
<td>Delete in</td>
<td style="width: 130px;">Admin</td>
<td style="width: 10px;">Details</td>
<td style="width: 10px;">Actions</td>
</tr>
</thead>
<tbody>
{% for i in "xxxxxxxxxxxxxxxxxxxxx" %}
{% with forloop.counter as c %}
{% include "dashboard/vm-list/test-one.html" %}
{% endwith %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</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 virtual machines</h3>
</div>
<div class="panel-body">
{% render_table table %}
</div>
</div>
</div>
</div>
<style>
.popover {
max-width: 600px;
......@@ -97,6 +62,7 @@
{% endblock %}
{% block extra_js %}
<script>
$(function() {
var ctrlDown, shiftDown = false;
var ctrlKey = 17;
......@@ -203,4 +169,5 @@ $(function() {
}
}
</script>
{% endblock %}
from django.conf.urls import patterns, url
from vm.models import Instance
from .views import IndexView, VmDetailView, VmList, AclUpdateView
from .views import (
IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView
)
urlpatterns = patterns(
'',
url(r'^$', IndexView.as_view()),
url(r'^template/(?P<pk>\d+)/$', TemplateDetail.as_view(),
name='dashboard.views.template-detail'),
url(r'^vm/(?P<pk>\d+)/$', VmDetailView.as_view(),
name='dashboard.views.detail'),
url(r'^vm/(?P<pk>\d+)/acl/$', AclUpdateView.as_view(model=Instance),
name='dashboard.views.vm-acl'),
url(r'^vm/list/$', VmList.as_view(), name='dashboard.views.vm-list'),
url(r'^vm/create/$', VmCreate.as_view(),
name='dashboard.views.vm-create'),
)
from os import getenv
import json
import re
from django.contrib.auth.models import User, Group
......@@ -7,12 +9,14 @@ from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.views.generic import TemplateView, DetailView, View
from django.views.generic.detail import SingleObjectMixin
from django.http import HttpResponse
from django_tables2 import SingleTableView
from vm.models import Instance
from .tables import VmListTable
from vm.models import Instance, InstanceTemplate, InterfaceTemplate
from firewall.models import Vlan
from storage.models import Disk
class IndexView(TemplateView):
......@@ -24,9 +28,18 @@ class IndexView(TemplateView):
else:
user = None
instances = Instance.objects.filter(owner=user)
context = super(IndexView, self).get_context_data(**kwargs)
context.update({
'instances': Instance.objects.filter(owner=user),
'instances': instances[:5],
'more_instances': instances.count() - len(instances[:5])
})
context.update({
'running_vms': instances.filter(state='RUNNING'),
'running_vm_num': instances.filter(state='RUNNING').count(),
'stopped_vm_num': instances.exclude(
state__in=['RUNNING', 'NOSTATE']).count()
})
return context
......@@ -50,9 +63,10 @@ class VmDetailView(DetailView):
instance = context['instance']
if instance.node:
port = instance.vnc_port
host = instance.node.host.ipv4
host = str(instance.node.host.ipv4)
value = signing.dumps({'host': host,
'port': port}, key='asdasd')
'port': port},
key=getenv("PROXY_SECRET", 'asdasd')),
context.update({
'vnc_url': '%s' % value
})
......@@ -84,8 +98,85 @@ class AclUpdateView(View, SingleObjectMixin):
return redirect(instance)
class TemplateDetail(DetailView):
model = InstanceTemplate
def get(self, request, *args, **kwargs):
if request.is_ajax():
template = InstanceTemplate.objects.get(pk=kwargs['pk'])
template = {
'num_cores': template.num_cores,
'ram_size': template.ram_size,
'priority': template.priority,
'arch': template.arch,
'description': template.description,
'system': template.system,
'name': template.name,
'disks': [{'pk': d.pk, 'name': d.name}
for d in template.disks.all()],
'network': [
{'vlan_pk': i.vlan.pk, 'vlan': i.vlan.name,
'managed': i.managed}
for i in InterfaceTemplate.objects.filter(
template=self.get_object()).all()
]
}
return HttpResponse(json.dumps(template),
content_type="application/json")
else:
# return super(TemplateDetail, self).get(request, *args, **kwargs)
return HttpResponse('soon')
class VmList(SingleTableView):
template_name = "dashboard/vm-list.html"
model = Instance
table_class = VmListTable
table_pagination = False
class VmCreate(TemplateView):
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/modal-wrapper.html']
else:
return ['dashboard/nojs-wrapper.html']
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/vm-create.html',
'box_title': 'Create a VM'
})
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super(VmCreate, self).get_context_data(**kwargs)
# TODO acl
context.update({
'templates': InstanceTemplate.objects.all(),
'vlans': Vlan.objects.all(),
'disks': Disk.objects.exclude(type="qcow2-snap")
})
return context
def post(self, request, *args, **kwargs):
if self.request.user.is_authenticated():
user = self.request.user
else:
user = None
resp = request.POST.copy()
resp['managed-vlans'] = request.POST.getlist('managed-vlans')
resp['unmanaged-vlans'] = request.POST.getlist('unmanaged-vlans')
resp['disks'] = request.POST.getlist('disks')
template = InstanceTemplate.objects.get(
pk=request.POST.get('template-pk'))
inst = Instance.create_from_template(template=template, owner=user)
inst.deploy_async()
# TODO handle response
return HttpResponse(json.dumps(resp), content_type="application/json")
......@@ -2,7 +2,8 @@
from django.contrib import admin
from firewall.models import (Rule, Host, Vlan, Group, VlanGroup, Firewall,
Domain, Record, Blacklist)
Domain, Record, Blacklist,
SwitchPort, EthernetDevice)
from django import contrib
......@@ -105,22 +106,20 @@ class DomainAdmin(admin.ModelAdmin):
class RecordAdmin(admin.ModelAdmin):
list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner')
@staticmethod
def address_(instance):
a = instance.get_data()
return a['address'] if a else None
@staticmethod
def name_(instance):
a = instance.get_data()
return a['name'] if a else None
list_display = ('name', 'type', 'address', 'ttl', 'host', 'owner')
class BlacklistAdmin(admin.ModelAdmin):
list_display = ('ipv4', 'reason', 'created_at', 'modified_at')
class SwitchPortAdmin(admin.ModelAdmin):
list_display = ()
class EthernetDeviceAdmin(admin.ModelAdmin):
list_display = ('name', )
admin.site.register(Host, HostAdmin)
admin.site.register(Vlan, VlanAdmin)
admin.site.register(Rule, RuleAdmin)
......@@ -130,3 +129,5 @@ admin.site.register(Firewall, FirewallAdmin)
admin.site.register(Domain, DomainAdmin)
admin.site.register(Record, RecordAdmin)
admin.site.register(Blacklist, BlacklistAdmin)
admin.site.register(SwitchPort)
admin.site.register(EthernetDevice, EthernetDeviceAdmin)
......@@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.ipv6 import is_valid_ipv6_address
from south.modelsinspector import add_introspection_rules
from django import forms
from netaddr import IPNetwork, AddrFormatError
from netaddr import IPAddress, IPNetwork, AddrFormatError, ZEROFILL
import re
......@@ -42,6 +42,76 @@ class MACAddressField(models.Field):
add_introspection_rules([], ["firewall\.fields\.MACAddressField"])
class IPAddressFormField(forms.Field):
default_error_messages = {
'invalid': _(u'Enter a valid IP address. %s'),
}
def validate(self, value):
try:
return IPAddressField.from_str(value, version=self.version)
except (AddrFormatError, TypeError), e:
raise ValidationError(self.default_error_messages['invalid']
% unicode(e))
def __init__(self, *args, **kwargs):
self.version = kwargs['version']
del kwargs['version']
super(IPAddressFormField, self).__init__(*args, **kwargs)
class IPAddressField(models.Field):
description = _('IP Network object')
__metaclass__ = models.SubfieldBase
def __init__(self, version=4, serialize=True, *args, **kwargs):
kwargs['max_length'] = 100
self.version = version
super(IPAddressField, self).__init__(*args, **kwargs)
@staticmethod
def from_str(value, version):
if not value or value == "":
return None
if isinstance(value, IPAddress):
return value
return IPAddress(value.split('/')[0], version=version,
flags=ZEROFILL)
def get_internal_type(self):
return "CharField"
def to_python(self, value):
return IPAddressField.from_str(value, self.version)
def get_db_prep_value(self, value, connection, prepared=False):
if not value or value == "":
return None
if isinstance(value, IPAddress):
if self.version == 4:
return ('.'.join(["%03d" % x for x in value.words]))
else:
return (':'.join(["%04X" % x for x in value.words]))
return value
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return str(self.get_prep_value(value))
def clean(self, value, model_instance):
value = super(IPAddressField, self).clean(value, model_instance)
return self.get_prep_value(value)
def formfield(self, **kwargs):
defaults = {'form_class': IPAddressFormField}
defaults['version'] = self.version
defaults.update(kwargs)
return super(IPAddressField, self).formfield(**defaults)
class IPNetworkFormField(forms.Field):
default_error_messages = {
'invalid': _(u'Enter a valid IP network. %s'),
......@@ -91,9 +161,11 @@ class IPNetworkField(models.Field):
if isinstance(value, IPNetwork):
if self.version == 4:
return '.'.join(map(lambda x: "%03d" % x, value.ip.words)) + '/%d' % value.prefixlen
return ('.'.join(["%03d" % x for x in value.ip.words])
+ '/%02d' % value.prefixlen)
else:
return ':'.join(map(lambda x: "%04X" % x, value.ip.words)) + '/%d' % value.prefixlen
return (':'.join(["%04X" % x for x in value.ip.words])
+ '/%03d' % value.prefixlen)
return value
def value_to_string(self, obj):
......@@ -110,7 +182,7 @@ class IPNetworkField(models.Field):
defaults.update(kwargs)
return super(IPNetworkField, self).formfield(**defaults)
add_introspection_rules([], ["^firewall\.fields\.IPNetworkField"])
add_introspection_rules([], ["^firewall\.fields\.IP(Address|Network)Field"])
def val_alfanum(value):
......
......@@ -37,9 +37,9 @@ class Firewall:
return
if self.proto == 6 and host.ipv6:
ipaddr = host.ipv6 + '/112'
ipaddr = str(host.ipv6) + '/112'
else:
ipaddr = host.ipv4
ipaddr = str(host.ipv4)
dport_sport = self.dportsport(rule)
......@@ -75,11 +75,11 @@ class Firewall:
for vlan in rule.foreign_network.vlans.all():
if rule.direction == '1': # going TO host
self.iptables('-A INPUT -i %s %s %s -g %s' %
(vlan.interface, dport_sport, rule.extra,
(vlan.name, dport_sport, rule.extra,
'LOG_ACC' if rule.accept else 'LOG_DROP'))
else:
self.iptables('-A OUTPUT -o %s %s %s -g %s' %
(vlan.interface, dport_sport, rule.extra,
(vlan.name, dport_sport, rule.extra,
'LOG_ACC' if rule.accept else 'LOG_DROP'))
def vlan2vlan(self, l_vlan, rule):
......@@ -189,7 +189,7 @@ class Firewall:
for d_vlan in s_vlan.snat_to.all():
self.iptablesnat('-A POSTROUTING -s %s -o %s -j SNAT '
'--to-source %s' %
(str(s_vlan.network4), d_vlan.interface,
(str(s_vlan.network4), d_vlan.name,
s_vlan.snat_ip))
self.iptablesnat('COMMIT')
......@@ -210,7 +210,7 @@ class Firewall:
for d_vlan in self.vlans:
self.iptables('-N %s_%s' % (s_vlan, d_vlan))
self.iptables('-A FORWARD -i %s -o %s -g %s_%s' %
(s_vlan.interface, d_vlan.interface, s_vlan,
(s_vlan.name, d_vlan.name, s_vlan,
d_vlan))
# hosts' rules
......@@ -320,81 +320,90 @@ def ipv6_to_arpa(ipv6):
# ^ PTR
# C CNAME
# : generic
# 'fqdn:s:ttl TXT
def dns():
vlans = models.Vlan.objects.all()
# regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
def generate_ptr_records():
DNS = []
for i_vlan in vlans:
# m = regex.search(i_vlan.net4)
rev = i_vlan.reverse_domain
for i_host in i_vlan.host_set.all():
ipv4 = (i_host.pub_ipv4 if i_host.pub_ipv4 and
not i_host.shared_ip else i_host.ipv4)
i = ipv4.split('.', 4)
reverse = (i_host.reverse if i_host.reverse and
len(i_host.reverse) else i_host.get_fqdn())
# ipv4
if i_host.ipv4:
DNS.append("^%s:%s:%s" % (
(rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
'd': int(i[3])}),
reverse, models.settings['dns_ttl']))
# ipv6
if i_host.ipv6:
DNS.append("^%s:%s:%s" % (ipv6_to_arpa(i_host.ipv6),
reverse, models.settings['dns_ttl']))
for host in models.Host.objects.order_by('vlan').all():
rev = host.vlan.reverse_domain
ipv4 = str(host.pub_ipv4 if host.pub_ipv4 and
not host.shared_ip else host.ipv4)
i = ipv4.split('.', 4)
reverse = (host.reverse if host.reverse and
len(host.reverse) else host.get_fqdn())
for domain in models.Domain.objects.all():
DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" %
(domain.name, settings['dns_hostname'],
models.settings['dns_ttl']))
# ipv4
if host.ipv4:
DNS.append("^%s:%s:%s" % (
(rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
'd': int(i[3])}),
reverse, models.settings['dns_ttl']))
# ipv6
if host.ipv6:
DNS.append("^%s:%s:%s" % (ipv6_to_arpa(str(host.ipv6)),
reverse, models.settings['dns_ttl']))
return DNS
def txt_to_octal(txt):
return '\\' + '\\'.join(['%03o' % ord(x) for x in txt])
def generate_records():
DNS = []
for r in models.Record.objects.all():
d = r.get_data()
if d['type'] == 'A':
DNS.append("+%s:%s:%s" % (d['name'], d['address'], d['ttl']))
elif d['type'] == 'AAAA':
if r.type == 'A':
DNS.append("+%s:%s:%s" % (r.fqdn, r.address, r.ttl))
elif r.type == 'AAAA':
DNS.append(":%s:28:%s:%s" %
(d['name'], ipv6_to_octal(d['address']), d['ttl']))
elif d['type'] == 'NS':
DNS.append("&%s::%s:%s" % (d['name'], d['address'], d['ttl']))
elif d['type'] == 'CNAME':
DNS.append("C%s:%s:%s" % (d['name'], d['address'], d['ttl']))
elif d['type'] == 'MX':
mx = d['address'].split(':', 2)
(r.fqdn, ipv6_to_octal(r.address), r.ttl))
elif r.type == 'NS':
DNS.append("&%s::%s:%s" % (r.fqdn, r.address, r.ttl))
elif r.type == 'CNAME':
DNS.append("C%s:%s:%s" % (r.fqdn, r.address, r.ttl))
elif r.type == 'MX':
mx = r.address.split(':', 2)
DNS.append("@%(fqdn)s::%(mx)s:%(dist)s:%(ttl)s" %
{'fqdn': d['name'], 'mx': mx[1], 'dist': mx[0],
'ttl': d['ttl']})
elif d['type'] == 'PTR':
DNS.append("^%s:%s:%s" % (d['name'], d['address'], d['ttl']))
{'fqdn': r.fqdn, 'mx': mx[1], 'dist': mx[0],
'ttl': r.ttl})
elif r.type == 'PTR':
DNS.append("^%s:%s:%s" % (r.fqdn, r.address, r.ttl))
elif r.type == 'TXT':
DNS.append("'%s:%s:%s" % (r.fqdn,
txt_to_octal(r.address), r.ttl))
return DNS
def prefix_to_mask(prefix):
t = [0, 0, 0, 0]
for i in range(0, 4):
if prefix > i * 8 + 7:
t[i] = 255
elif i * 8 < prefix and prefix <= (i + 1) * 8:
t[i] = 256 - (2 ** ((i + 1) * 8 - prefix))
return ".".join([str(i) for i in t])
def dns():
DNS = []
# host PTR record
DNS += generate_ptr_records()
# domain SOA record
for domain in models.Domain.objects.all():
DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" %
(domain.name, settings['dns_hostname'],
models.settings['dns_ttl']))
# records
DNS += generate_records()
return DNS
def dhcp():
vlans = models.Vlan.objects.all()
regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+'
r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$')
DHCP = []
# /tools/dhcp3/dhcpd.conf.generated
for i_vlan in vlans:
for i_vlan in models.Vlan.objects.all():
if(i_vlan.dhcp_pool):
m = regex.search(i_vlan.dhcp_pool)
if(m or i_vlan.dhcp_pool == "manual"):
......@@ -411,15 +420,15 @@ def dhcp():
filename \"pxelinux.0\";
allow bootp; allow booting;
}''' % {
'net': i_vlan.net4,
'netmask': prefix_to_mask(i_vlan.prefix4),
'net': str(i_vlan.network4.network),
'netmask': str(i_vlan.network4.netmask),
'domain': i_vlan.domain,
'router': i_vlan.ipv4,
'ntp': i_vlan.ipv4,
'dnsserver': settings['rdns_ip'],
'extra': ("range %s" % i_vlan.dhcp_pool
if m else "deny unknown-clients"),
'interface': i_vlan.interface,
'interface': i_vlan.name,
'name': i_vlan.name,
'tftp': i_vlan.ipv4
})
......@@ -440,7 +449,25 @@ def dhcp():
def vlan():
obj = models.Vlan.objects.values('vid', 'name', 'network4', 'network6')
return {x['name']: {'tag': x['vid'],
'addresses': [str(x['network4']),
str(x['network6'])]}
for x in obj}
retval = {x['name']: {'tag': x['vid'],
'type': 'internal',
'interfaces': [x['name']],
'addresses': [str(x['network4']),
str(x['network6'])]}
for x in obj}
for p in models.SwitchPort.objects.all():
eth_count = p.ethernet_devices.count()
if eth_count > 1:
name = 'bond%d' % p.id
elif eth_count == 1:
name = p.ethernet_devices.get().name
else: # 0
continue
tag = p.untagged_vlan.vid
retval[name] = {'tag': tag}
if p.tagged_vlans is not None:
trunk = list(p.tagged_vlans.vlans.values_list('vid', flat=True))
retval[name]['trunks'] = sorted(trunk)
retval[name]['interfaces'] = list(
p.ethernet_devices.values_list('name', flat=True))
return retval
......@@ -47,16 +47,16 @@ def reloadtask(type='Host'):
if type in ["Host", "Record", "Domain", "Vlan"]:
cache.add("dns_lock", "true", 30)
if type == "Host":
if type in ["Host", "Vlan"]:
cache.add("dhcp_lock", "true", 30)
if type in ["Host", "Rule", "Firewall"]:
if type in ["Host", "Rule", "Firewall", "Vlan"]:
cache.add("firewall_lock", "true", 30)
if type == "Blacklist":
cache.add("blacklist_lock", "true", 30)
if type == "Vlan":
if type in ["Vlan", "SwitchPort", "EthernetDevice"]:
cache.add("firewall_vlan_lock", "true", 30)
print type
from calvin import *
server_name = "0.0.0.0"
server_port = "8080"
query = Query()
query.setTarget("1889.foo.fook.fookie.com.DOMAIN")
query.setMetric("cpu.usage")
query.setFormat("json") # Not neccesary, default is json
query.setRelativeStart(1, "minutes") # Current cpu usage
query.generate()
# print(query.getGenerated())
print(query.getStart())
# query.setAbsoluteStart("1889", "04", "20", "00", "00")
# query.setRelativeEnd(...)
# query.setAbsoluteEnd(...)
handler = GraphiteHandler(server_name, server_port)
handler.put(query)
handler.send()
response = handler.pop()
print(response["target"])
print(response["datapoints"])
from calvin import *
import datetime
query = Query()
query.setTarget("2008.vm.ik.bme.hu.circle")
query.setMetric("cpu.usage")
query.setAbsoluteStart("2013", "10", "23", "00", "00")
query.generate()
handler = GraphiteHandler("10.9.1.209")
times = int(input("How many requests do you intend to send? [postive integer] "))
global_start = datetime.datetime.now()
for i in range (1,times):
local_start = datetime.datetime.now()
handler.put(query)
handler.send()
local_end = datetime.datetime.now()
print((local_end-local_start).microseconds)
global_end = datetime.datetime.now()
print("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*")
print("Summary:")
print(global_end - global_start)
print("AVG:")
print((global_end -global_start) / times)
print("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*")
import json, requests
class GraphiteHandler:
def __init__(self, server_name = "localhost", server_port = "8080"):
self.__server_name = server_name
self.__server_port = server_port
self.__queries = []
self.__responses = []
def put(self, query):
self.__queries.append(query)
def cleanUpQueries(self):
self.__queries = []
def cleanUpResponses(self):
self.__responses = []
def isEmpty(self):
return len(self.__queries) is 0
def generateAll(self):
"""
Regenerate the queries before sending.
"""
for query in self.__queries:
query.generate()
def send(self):
"""
Generates the corrent query for the Graphite webAPI and flush all the
queries in the fifo.
Important: After sending queries to the server the fifo will lost its
content.
"""
url_base = "http://%s:%s/render?" % (self.__server_name, self.__server_port)
for query in self.__queries:
response = requests.get(url_base + query.getGenerated())
if query.getFormat() is "json":
self.__responses.append(response.json()[0]) #DICT
else:
self.__responses.append(response)
self.cleanUpQueries()
def pop(self):
"""
Pop the first query has got from the server.
"""
try:
return self.__responses.pop(0) # Transform to dictionary
except:
print("There is no more responses.")
class Query:
def __init__(self):
"""
Query initializaion:
default format is json dictionary
keys: ("target <string>","datapoints <list>")
"""
self.__target = ""
self.__metric = ""
self.__start = ""
self.__end = ""
self.__function = ""
self.__response_format = "json"
self.__generated = ""
def setTarget(self, target):
"""
Hostname of the target we should get the information from.
After the hostname you should use the domain the target is in.
Example: "foo.foodomain.domain.com.DOMAIN" where DOMAIN is
the root of the graphite server.
"""
self.__target = '.'.join(target.split('.')[::-1])
def getTarget(self):
return self.__target
def setMetric(self, metric):
self.__metric = metric
def getMetric(self):
return self.__metric
def setAbsoluteStart(self, year, month, day, hour, minute):
"""
Function for setting the time you want to get the reports from.
"""
if(len(year)>4 or len(year)<2):
raise
self.__start = hour + ":" + minute + "_" + year + month + day
def setRelativeStart(self, value, scale):
"""
Function for setting the time you want to get the reports from.
"""
if scale not in ["years", "months", "days", "hours", "minutes", "seconds"]:
raise
self.__start = "-" + str(value) + scale
def getStart(self):
return self.__start
def setAbsoluteEnd(self, year, month, day, hour, minute):
"""
Function for setting the time until you want to get the reports from.
"""
if(len(year)>4 or len(year)<2):
raise
self.__end = hour + ":" + minute + "_" + year + month + day
def setRelativeEnd(self, value, scale):
"""
Function for setting the time until you want to get the reports from.
"""
if scale not in ["years", "months", "days", "hours", "minutes", "seconds"]:
raise
self.__end = "-" + str(value) + scale
def getEnd(self):
return self.__end
def setFormat(self, fmat):
"""
Function for setting the format of the response from the server.
Valid values: ["csv", "raw", "json"]
"""
valid_formats = ["csv", "raw", "json"]
if fmat not in valid_formats:
raise
self.__response_format = fmat
def getFormat(self):
return self.__response_format
def generate(self):
"""
You must always call this function before sending the metric to the
server for it generates the valid format that the graphite API can
parse.
"""
tmp = "target=" + self.__target + "." + self.__metric
if len(self.__start) is not 0:
tmp = tmp + "&from=" + self.__start
if len(self.__end) is not 0:
tmp = tmp + "&until=" + self.__end
tmp = tmp + "&format=" + self.__response_format
self.__generated = tmp
return self.__generated
def getGenerated(self):
"""
Returns the generated query string.
Throws exception if it haven't been done yet.
"""
if len(self.__generated) is 0:
raise
return self.__generated
......@@ -6,7 +6,7 @@ from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
from crispy_forms.bootstrap import FormActions
from firewall.models import (Host, Vlan, Domain, Group, Record, Blacklist,
Rule, VlanGroup)
Rule, VlanGroup, SwitchPort)
class LinkButton(BaseInput):
......@@ -188,6 +188,28 @@ class RuleForm(ModelForm):
model = Rule
class SwitchPortForm(ModelForm):
helper = FormHelper()
helper.layout = Layout(
Div(
Fieldset(
'',
'untagged_vlan',
'tagged_vlans',
'description',
)
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back',
reverse_lazy('network.switch_port_list'))
)
)
class Meta:
model = SwitchPort
class VlanForm(ModelForm):
helper = FormHelper()
helper.layout = Layout(
......
$('i[class="icon-remove"]').click(function() {
href = $(this).parent('a').attr('href');
csrf = getCookie('csrftoken');
var click_this = this;
group = $(this).closest('h4').text();
s = gettext('Are you sure you want to delete this device?');
bootbox.dialog({
message: s,
buttons: {
cancel: {
'label': gettext("Cancel"),
'className': "btn-info",
'callback': function () {}
},
remove: {
'label': gettext("Remove"),
'className': "btn-danger",
'callback': function() {
delete_device(click_this);
}
}
}
});
return false;
});
function delete_device(click_this) {
ajax = $.ajax({
type: 'POST',
url: href,
headers: {"X-CSRFToken": csrf},
context: click_this,
success: function(data, textStatus, xhr) {
if(xhr.status == 200) {
$(this).closest('tr').fadeOut(500, function() {
$(this).remove();
});
}
}
});
return false;
}
from django_tables2 import Table, A
from django_tables2.columns import LinkColumn, TemplateColumn
from firewall.models import Host, Vlan, Domain, Group, Record, Rule
from firewall.models import Host, Vlan, Domain, Group, Record, Rule, SwitchPort
class BlacklistTable(Table):
......@@ -129,6 +129,17 @@ class RuleTable(Table):
order_by = 'direction'
class SwitchPortTable(Table):
pk = LinkColumn('network.switch_port', args=[A('pk')],
verbose_name="ID")
class Meta:
model = SwitchPort
attrs = {'class': 'table table-striped table-condensed'}
fields = ('pk', 'untagged_vlan', 'tagged_vlans', 'description', )
order_by = 'pk'
class VlanTable(Table):
name = LinkColumn('network.vlan', args=[A('vid')])
......
......@@ -19,6 +19,9 @@
{% url "network.rule_list" as u %}
{% trans "Rules" as t %}
{% include "network/menu-item.html" with href=u text=t %}
{% url "network.switch_port_list" as u %}
{% trans "Switch ports" as t %}
{% include "network/menu-item.html" with href=u text=t %}
<li class="dropdown{% if "groups" in request.path %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Groups <b class="caret"></b></a>
......
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block content %}
<div class="page-header">
<h2>{% trans "Create a new switch port" %}</h2>
</div>
<div class="row">
<div class="col-sm-8">
{% crispy form %}
</div>
<div class="col-sm-4">
Halp
</div>
</div>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block content %}
<div class="page-header">
<a href="{% url "network.switch_port_delete" pk=switch_port_pk %}" class="btn btn-danger pull-right"><i class="icon-remove-sign"></i> {% trans "Delete this switchport" %}</a>
<h2>Welp <small>dunno what to write here</small></h2>
</div>
<div class="row">
<div class="col-sm-5">
{% crispy form %}
</div>
<style>
.ethernet-devices-mini-table tr td:last-child {
width: 1px;
}
</style>
<div class="col-sm-5 col-sm-offset-1">
<h3>{% trans "Ethernet Devices" %}</h3>
<hr />
{% if devices %}
<table class="table table-condensed table-bordered ethernet-devices-mini-table">
{% for i in devices %}
<tr>
<td>{{ i }}</td>
<td>
<a href="{% url "network.remove_switch_port_device" pk=switch_port_pk device_pk=i.pk %}"><i class="icon-remove"></i></a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
{% trans "No ethernet device" %}
{% endif %}
<hr />
<form action="{% url "network.add_switch_port_device" pk=switch_port_pk %}" method="POST">
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control" name="device_name" placeholder="{% trans "Name" %}"/>
<div class="input-group-btn">
<input type="submit" value="{% trans "Add new Ethernet Device" %}" class="btn btn-default"></input>
</div>
</div><!-- input-group -->
</form>
</div>
</div>
{% endblock %}
{% block extra_etc %}
<script src="{% static "js/switch-port.js" %}"></script>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% block content %}
<div class="page-header">
<a href="{% url "network.switch_port_create" %}" class="btn btn-success pull-right"><i class="icon-plus-sign"></i> {% trans "Create a new switch port" %}</a>
<h1>{% trans "Switch ports" %} <small></small></h1>
</div>
<div class="table-responsive">
{% render_table table %}
</div>
{% endblock %}
......@@ -8,9 +8,12 @@ from .views import (IndexView,
BlacklistList, BlacklistDetail, BlacklistDelete,
BlacklistCreate,
RuleList, RuleDetail, RuleDelete, RuleCreate,
SwitchPortList, SwitchPortDetail, SwitchPortCreate,
SwitchPortDelete,
VlanGroupList, VlanGroupDetail, VlanGroupDelete,
VlanGroupCreate,
remove_host_group, add_host_group)
remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device)
js_info_dict = {
'packages': ('network', ),
......@@ -56,6 +59,14 @@ urlpatterns = patterns(
url('^rules/create$', RuleCreate.as_view(), name='network.rule_create'),
url('^rules/(?P<pk>\d+)/$', RuleDetail.as_view(),
name='network.rule'),
url('^switchports/$', SwitchPortList.as_view(),
name='network.switch_port_list'),
url('^switchports/create$', SwitchPortCreate.as_view(),
name='network.switch_port_create'),
url('^switchports/(?P<pk>\d+)/$', SwitchPortDetail.as_view(),
name='network.switch_port'),
url('^switchports/delete/(?P<pk>\d+)/$', SwitchPortDelete.as_view(),
name="network.switch_port_delete"),
url('^vlans/$', VlanList.as_view(), name='network.vlan_list'),
url('^vlans/create$', VlanCreate.as_view(), name='network.vlan_create'),
url('^vlans/(?P<vid>\d+)/$', VlanDetail.as_view(), name='network.vlan'),
......@@ -71,10 +82,18 @@ urlpatterns = patterns(
name="network.vlan_group_delete"),
url('^rules/delete/(?P<pk>\d+)/$', RuleDelete.as_view(),
name="network.rule_delete"),
# non class based views
url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group,
name='network.remove_host_group'),
url('^hosts/(?P<pk>\d+)/add/$', add_host_group,
name='network.add_host_group'),
url('^switchports/(?P<pk>\d+)/remove/(?P<device_pk>\d+)/$',
remove_switch_port_device, name='network.remove_switch_port_device'),
url('^switchports/(?P<pk>\d+)/add/$', add_switch_port_device,
name='network.add_switch_port_device'),
# js gettext catalog
url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict,
name="network.js_catalog"),
)
......@@ -7,13 +7,13 @@ from django.http import HttpResponse
from django_tables2 import SingleTableView
from firewall.models import (Host, Vlan, Domain, Group, Record, Blacklist,
Rule, VlanGroup)
Rule, VlanGroup, SwitchPort, EthernetDevice)
from .tables import (HostTable, VlanTable, SmallHostTable, DomainTable,
GroupTable, RecordTable, BlacklistTable, RuleTable,
VlanGroupTable, SmallRuleTable, SmallGroupRuleTable,
SmallRecordTable)
SmallRecordTable, SwitchPortTable)
from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm,
BlacklistForm, RuleForm, VlanGroupForm)
BlacklistForm, RuleForm, VlanGroupForm, SwitchPortForm)
from django.contrib import messages
from django.views.generic.edit import FormMixin
......@@ -530,6 +530,50 @@ class RuleDelete(DeleteView):
return reverse_lazy('network.rule_list')
class SwitchPortList(SingleTableView):
model = SwitchPort
table_class = SwitchPortTable
template_name = "network/switch-port-list.html"
table_pagination = False
class SwitchPortDetail(UpdateView, SuccessMessageMixin):
model = SwitchPort
template_name = "network/switch-port-edit.html"
form_class = SwitchPortForm
success_message = _(u'Succesfully modified switch port!')
def get_success_url(self):
if 'pk' in self.kwargs:
return reverse_lazy('network.switch_port', kwargs=self.kwargs)
def get_context_data(self, **kwargs):
context = super(SwitchPortDetail, self).get_context_data(**kwargs)
context['switch_port_pk'] = self.object.pk
context['devices'] = EthernetDevice.objects.filter(
switch_port=self.object.pk)
return context
class SwitchPortCreate(CreateView, SuccessMessageMixin):
model = SwitchPort
template_name = "network/switch-port-create.html"
form_class = SwitchPortForm
success_message = _(u'Successfully created switch port!')
class SwitchPortDelete(DeleteView):
model = SwitchPort
template_name = "network/confirm/base_delete.html"
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('network.switch_port_list')
class VlanList(SingleTableView):
model = Vlan
table_class = VlanTable
......@@ -690,3 +734,47 @@ def add_host_group(request, **kwargs):
'group': group
}))
return redirect(reverse_lazy('network.host', kwargs=kwargs))
def remove_switch_port_device(request, **kwargs):
device = EthernetDevice.objects.get(pk=kwargs['device_pk'])
# for get we show the confirmation page
if request.method == "GET":
return render(request, 'network/confirm/base_delete.html',
{'object': device})
# for post we actually remove the group from the host
elif request.method == "POST":
device.delete()
if not request.is_ajax():
messages.success(request, _(u"Successfully deleted ethernet device"
" %(name)s!" % {
'name': device.name,
}))
return redirect(reverse_lazy('network.switch_port',
kwargs={'pk': kwargs['pk']}))
def add_switch_port_device(request, **kwargs):
device_name = request.POST.get('device_name')
if (request.method == "POST" and device_name and len(device_name) > 0
and EthernetDevice.objects.filter(name=device_name).count() == 0):
switch_port = SwitchPort.objects.get(pk=kwargs['pk'])
new_device = EthernetDevice(name=device_name, switch_port=switch_port)
new_device.save()
if not request.is_ajax():
messages.success(request, _(u"Successfully added %(name)s to this"
" switch port" % {
'name': device_name,
}))
return redirect(reverse_lazy('network.switch_port', kwargs=kwargs))
elif not len(device_name) > 0:
messages.error(request, _("Ethernet device name cannot be empty!"))
return redirect(reverse_lazy('network.switch_port', kwargs=kwargs))
elif EthernetDevice.objects.get(name=device_name) is not None:
messages.error(request, _("There is already an ethernet device with"
" that name!"))
return redirect(reverse_lazy('network.switch_port', kwargs=kwargs))
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'Disk.removed'
db.delete_column(u'storage_disk', 'removed')
# Adding field 'Disk.destroyed'
db.add_column(u'storage_disk', 'destroyed',
self.gf('django.db.models.fields.DateTimeField')(default=None, null=True, blank=True),
keep_default=False)
def backwards(self, orm):
# Adding field 'Disk.removed'
db.add_column(u'storage_disk', 'removed',
self.gf('django.db.models.fields.DateTimeField')(default=None, null=True, blank=True),
keep_default=False)
# Deleting field 'Disk.destroyed'
db.delete_column(u'storage_disk', 'destroyed')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'storage.datastore': {
'Meta': {'ordering': "['name']", 'object_name': 'DataStore'},
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'})
},
u'storage.disk': {
'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}),
'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'dev_num': ('django.db.models.fields.CharField', [], {'default': "'a'", 'max_length': '1'}),
'filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'size': ('sizefield.models.FileSizeField', [], {}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
},
u'storage.diskactivity': {
'Meta': {'object_name': 'DiskActivity'},
'activity_code': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'disk': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_log'", 'to': u"orm['storage.Disk']"}),
'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'result': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '50'}),
'task_uuid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['storage']
\ No newline at end of file
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'DiskActivity.state'
db.delete_column(u'storage_diskactivity', 'state')
# Adding field 'DiskActivity.parent'
db.add_column(u'storage_diskactivity', 'parent',
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['storage.DiskActivity'], null=True, blank=True),
keep_default=False)
def backwards(self, orm):
# Adding field 'DiskActivity.state'
db.add_column(u'storage_diskactivity', 'state',
self.gf('django.db.models.fields.CharField')(default='PENDING', max_length=50),
keep_default=False)
# Deleting field 'DiskActivity.parent'
db.delete_column(u'storage_diskactivity', 'parent_id')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'storage.datastore': {
'Meta': {'ordering': "['name']", 'object_name': 'DataStore'},
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'})
},
u'storage.disk': {
'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}),
'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'dev_num': ('django.db.models.fields.CharField', [], {'default': "'a'", 'max_length': '1'}),
'filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'size': ('sizefield.models.FileSizeField', [], {}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
},
u'storage.diskactivity': {
'Meta': {'object_name': 'DiskActivity'},
'activity_code': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'disk': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_log'", 'to': u"orm['storage.Disk']"}),
'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DiskActivity']", 'null': 'True', 'blank': 'True'}),
'result': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'task_uuid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['storage']
\ No newline at end of file
# coding=utf-8
from contextlib import contextmanager
import logging
import uuid
from django.contrib.auth.models import User
from django.db.models import (Model, BooleanField, CharField, DateTimeField,
ForeignKey, TextField)
ForeignKey)
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
from sizefield.models import FileSizeField
from .tasks import local_tasks, remote_tasks
from common.models import ActivityModel, activitycontextimpl
logger = logging.getLogger(__name__)
......@@ -51,7 +52,7 @@ class Disk(TimeStampedModel):
ready = BooleanField(default=False)
dev_num = CharField(default='a', max_length=1,
verbose_name=_("device number"))
removed = DateTimeField(blank=True, default=None, null=True)
destroyed = DateTimeField(blank=True, default=None, null=True)
class Meta:
ordering = ['name']
......@@ -84,7 +85,7 @@ class Disk(TimeStampedModel):
return {
'qcow2-norm': 'qcow2',
'qcow2-snap': 'qcow2',
'iso': 'iso',
'iso': 'raw',
'raw-ro': 'raw',
'raw-rw': 'raw',
}[self.type]
......@@ -92,10 +93,12 @@ class Disk(TimeStampedModel):
@property
def device_type(self):
return {
'qcow2': 'vd',
'raw': 'vd',
'qcow2-norm': 'vd',
'qcow2-snap': 'vd',
'iso': 'hd',
}[self.format]
'raw-ro': 'vd',
'raw-rw': 'vd',
}[self.type]
def is_in_use(self):
return self.instance_set.exclude(state='SHUTOFF').exists()
......@@ -126,7 +129,8 @@ class Disk(TimeStampedModel):
'source': self.path,
'driver_type': self.format,
'driver_cache': 'default',
'target_device': self.device_type + self.dev_num
'target_device': self.device_type + self.dev_num,
'disk_device': 'cdrom' if self.type == 'iso' else 'disk'
}
def get_disk_desc(self):
......@@ -160,31 +164,25 @@ class Disk(TimeStampedModel):
if self.ready:
return False
act = DiskActivity(activity_code='storage.Disk.deploy')
act.disk = self
act.started = timezone.now()
act.state = 'PENDING'
act.task_uuid = task_uuid
act.user = user
act.save()
# Delegate create / snapshot jobs
queue_name = self.datastore.hostname + ".storage"
disk_desc = self.get_disk_desc()
if self.type == 'qcow2-snap':
act.update_state('CREATING SNAPSHOT')
remote_tasks.snapshot.apply_async(args=[disk_desc],
queue=queue_name).get()
else:
act.update_state('CREATING DISK')
remote_tasks.create.apply_async(args=[disk_desc],
queue=queue_name).get()
self.ready = True
self.save()
with disk_activity(code_suffix='deploy', disk=self,
task_uuid=task_uuid, user=user) as act:
# Delegate create / snapshot jobs
queue_name = self.datastore.hostname + ".storage"
disk_desc = self.get_disk_desc()
if self.type == 'qcow2-snap':
with act.sub_activity('creating_snapshot'):
remote_tasks.snapshot.apply_async(args=[disk_desc],
queue=queue_name).get()
else:
with act.sub_activity('creating_disk'):
remote_tasks.create.apply_async(args=[disk_desc],
queue=queue_name).get()
self.ready = True
self.save()
act.finish('SUCCESS')
return True
return True
def deploy_async(self, user=None):
"""Execute deploy asynchronously.
......@@ -192,17 +190,17 @@ class Disk(TimeStampedModel):
local_tasks.deploy.apply_async(args=[self, user],
queue="localhost.man")
def remove(self, user=None, task_uuid=None):
def destroy(self, user=None, task_uuid=None):
# TODO add activity logging
self.removed = timezone.now()
self.destroyed = timezone.now()
self.save()
def remove_async(self, user=None):
local_tasks.remove.apply_async(args=[self, user],
queue='localhost.man')
def destroy_async(self, user=None):
local_tasks.destroy.apply_async(args=[self, user],
queue='localhost.man')
def restore(self, user=None, task_uuid=None):
"""Restore removed disk.
"""Restore destroyed disk.
"""
# TODO
pass
......@@ -211,7 +209,7 @@ class Disk(TimeStampedModel):
local_tasks.restore.apply_async(args=[self, user],
queue='localhost.man')
def save_as(self, name, user=None, task_uuid=None):
def save_as(self, user=None, task_uuid=None):
mapping = {
'qcow2-snap': ('qcow2-norm', self.base),
}
......@@ -224,53 +222,55 @@ class Disk(TimeStampedModel):
# from this point on, the caller has to guarantee that the disk is not
# going to be used until the operation is complete
act = DiskActivity(activity_code='storage.Disk.save_as')
act.disk = self
act.started = timezone.now()
act.state = 'PENDING'
act.task_uuid = task_uuid
act.user = user
with disk_activity(code_suffix='save_as', disk=self,
task_uuid=task_uuid, user=user):
filename = str(uuid.uuid4())
new_type, new_base = mapping[self.type]
disk = Disk.objects.create(base=new_base, datastore=self.datastore,
filename=filename, name=self.name,
size=self.size, type=new_type)
queue_name = self.datastore.hostname + ".storage"
remote_tasks.merge.apply_async(args=[self.get_disk_desc(),
disk.get_disk_desc()],
queue=queue_name).get()
disk.ready = True
disk.save()
return disk
class DiskActivity(ActivityModel):
disk = ForeignKey(Disk, related_name='activity_log',
help_text=_('Disk this activity works on.'),
verbose_name=_('disk'))
@classmethod
def create(cls, code_suffix, disk, task_uuid=None, user=None):
act = cls(activity_code='storage.Disk.' + code_suffix,
disk=disk, parent=None, started=timezone.now(),
task_uuid=task_uuid, user=user)
act.save()
return act
filename = str(uuid.uuid4())
new_type, new_base = mapping[self.type]
def create_sub(self, code_suffix, task_uuid=None):
act = DiskActivity(
activity_code=self.activity_code + '.' + code_suffix,
disk=self.disk, parent=self, started=timezone.now(),
task_uuid=task_uuid, user=self.user)
act.save()
return act
disk = Disk.objects.create(base=new_base, datastore=self.datastore,
filename=filename, name=name,
size=self.size, type=new_type)
@contextmanager
def sub_activity(self, code_suffix, task_uuid=None):
act = self.create_sub(code_suffix, task_uuid)
return activitycontextimpl(act)
queue_name = self.datastore.hostname + ".storage"
remote_tasks.merge.apply_async(args=[self.get_disk_desc(),
disk.get_disk_desc()],
queue=queue_name).get()
disk.ready = True
disk.save()
act.finish('SUCCESS')
return disk
class DiskActivity(TimeStampedModel):
activity_code = CharField(verbose_name=_('activity_code'), max_length=100)
task_uuid = CharField(verbose_name=_('task_uuid'), blank=True,
max_length=50, null=True, unique=True)
disk = ForeignKey(Disk, verbose_name=_('disk'),
related_name='activity_log')
user = ForeignKey(User, verbose_name=_('user'), blank=True, null=True)
started = DateTimeField(verbose_name=_('started'), blank=True, null=True)
finished = DateTimeField(verbose_name=_('finished'), blank=True, null=True)
result = TextField(verbose_name=_('result'), blank=True, null=True)
state = CharField(verbose_name=_('state'), default='PENDING',
max_length=50)
def update_state(self, new_state):
self.state = new_state
self.save()
def finish(self, result=None):
if not self.finished:
self.finished = timezone.now()
self.result = result
self.state = 'COMPLETED'
self.save()
@contextmanager
def disk_activity(code_suffix, disk, task_uuid=None, user=None):
act = DiskActivity.create(code_suffix, disk, task_uuid, user)
return activitycontextimpl(act)
......@@ -7,8 +7,8 @@ def deploy(disk, user):
@celery.task
def remove(disk, user):
disk.remove(task_uuid=remove.request.id, user=user)
def destroy(disk, user):
disk.destroy(task_uuid=destroy.request.id, user=user)
@celery.task
......
from manager.mancelery import celery
# TODO: Keep syncronhised with Instance funcs
# TODO: Keep synchronised with Instance funcs
@celery.task
def deploy(instance, user):
''' Call Insance.deploy() from celery task.
'''
instance.deploy(task_uuid=deploy.request.id, user=user)
def destroy():
pass
@celery.task
def destroy(instance, user):
instance.destroy(task_uuid=destroy.request.id, user=user)
def save_as():
pass
@celery.task
def sleep(instance, user):
instance.sleep(task_uuid=sleep.request.id, user=user)
def suspend():
pass
@celery.task
def wake_up(instance, user):
instance.wake_up(task_uuid=wake_up.request.id, user=user)
def resume():
pass
@celery.task
def shutdown(instance, user):
instance.shutdown(task_uuid=shutdown.request.id, user=user)
def restart():
pass
@celery.task
def reset(instance, user):
instance.reset(task_uuid=reset.request.id, user=user)
def reset():
pass
@celery.task
def reboot(instance, user):
instance.reboot(task_uuid=reboot.request.id, user=user)
def migrate():
pass
@celery.task
def migrate(instance, user):
instance.migrate(task_uuid=migrate.request.id, user=user)
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