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 () { $(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) { $('[href=#vm-graph-view]').click(function (e) {
$('#vm-list-view').hide(); $('#vm-list-view').hide();
$('#vm-graph-view').show(); $('#vm-graph-view').show();
...@@ -18,26 +34,31 @@ $(function () { ...@@ -18,26 +34,31 @@ $(function () {
$('[title]').tooltip(); $('[title]').tooltip();
$(':input[title]').tooltip({trigger: 'focus', placement: 'auto right'}); $(':input[title]').tooltip({trigger: 'focus', placement: 'auto right'});
$(".knob").knob(); $(".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) if (window.location.hash)
$("a[href=" + window.location.hash +"]").tab('show'); $("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 @@ ...@@ -15,9 +15,8 @@
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css"> <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"> <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> <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/bootstrap-slider/bootstrap-slider.js"></script>
<script src="{{ STATIC_URL }}dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js"></script> <link rel="stylesheet" href="{{ STATIC_URL }}dashboard/bootstrap-slider/slider.css"/>
<link href="{{ STATIC_URL }}dashboard/dashboard.css" rel="stylesheet"> <link href="{{ STATIC_URL }}dashboard/dashboard.css" rel="stylesheet">
<script src="{{ STATIC_URL }}dashboard/dashboard.js"></script> <script src="{{ STATIC_URL }}dashboard/dashboard.js"></script>
</head> </head>
...@@ -39,9 +38,6 @@ ...@@ -39,9 +38,6 @@
</div> <!-- /container --> </div> <!-- /container -->
</body> </body>
<script>
{% block extra_js %} {% block extra_js %}
{% endblock %} {% endblock %}
</script>
</html> </html>
...@@ -15,51 +15,43 @@ ...@@ -15,51 +15,43 @@
<div class="list-group" id="vm-list-view"> <div class="list-group" id="vm-list-view">
{% for i in instances %} {% for i in instances %}
<a href="{{ i.get_absolute_url }}" class="list-group-item"> <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> </a>
{% endfor %} {% 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 href="#" class="list-group-item list-group-footer">
<div class="row"> <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..." /> <input type="text" class="form-control" placeholder="Search..." />
<div class="input-group-btn"> <div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary" title="search"><i class="icon-search"></i></button> <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"> <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> {% if more_instances > 0 %}
<a class="btn btn-success btn-xs"><i class="icon-plus-sign"></i> new </a> <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> </div>
</div> </div>
<div class="panel-body" id="vm-graph-view" style="display: none"> <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 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>13</big> running </span> <p><span class="bigbig"><big>{{ running_vm_num }}</big> running </span>
<ul class="list-inline"> <ul class="list-inline">
<li class="label label-success"><i class="icon-play-sign"></i> szilva</li> {% for vm in running_vms %}
<li class="label label-success"><i class="icon-play-sign"></i> korte</li> <li class="label label-success"><i class="icon-play-sign"></i> {{ vm.name}}</li>
<li class="label label-success"><i class="icon-play-sign"></i> cseresznye</li> {% endfor %}
</ul> </ul>
</p> </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> <p>Total: $3000 </p>
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="text-right"> <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> </div>
</div> </div>
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/vm-create.js"></script>
{% endblock %} {% 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"> <p class="row">
<div class="col-sm-3"> <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>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" data-slider="true" data-slider-highlight="true" <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"/>
data-slider-snap="true" data-slider-range="0,100" data-slider-step="1" id="cpu-priority-slider" value="{{ instance.priority }}">
</div> </div>
</p> </p>
...@@ -14,7 +13,7 @@ ...@@ -14,7 +13,7 @@
<label for="cpu-count-slider"><i class="icon-cogs"></i> CPU count</label> <label for="cpu-count-slider"><i class="icon-cogs"></i> CPU count</label>
</div> </div>
<div class="col-sm-9"> <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> </div>
</p> </p>
...@@ -24,7 +23,12 @@ ...@@ -24,7 +23,12 @@
<label for="ram-slider"><i class="icon-ticket"></i> RAM amount</label> <label for="ram-slider"><i class="icon-ticket"></i> RAM amount</label>
</div> </div>
<div class="col-sm-9"> <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 <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> </div>
</p> </p>
{% block extra_js %}
<style>
label {padding-top: 6px;}
</style>
{% endblock %}
...@@ -39,8 +39,8 @@ ...@@ -39,8 +39,8 @@
<a href="#resources" data-toggle="pill" class="text-center"> <a href="#resources" data-toggle="pill" class="text-center">
<i class="icon-tasks icon-2x"></i><br> <i class="icon-tasks icon-2x"></i><br>
{% trans "Resources" %}</a></li> {% trans "Resources" %}</a></li>
<li> <li {% if instance.state != "RUNNING" %}class="disabled"{% endif %}>
<a href="#console" data-toggle="pill" class="text-center"> <a href="#{% if instance.state == "RUNNING" %}console" data-toggle="pill{% endif %}" class="text-center">
<i class="icon-desktop icon-2x"></i><br> <i class="icon-desktop icon-2x"></i><br>
{% trans "Console" %}</a></li> {% trans "Console" %}</a></li>
<li> <li>
......
...@@ -28,47 +28,12 @@ ...@@ -28,47 +28,12 @@
</p> </p>
</div> </div>
<div class="panel-body"> <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 %} {% render_table table %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<style> <style>
.popover { .popover {
max-width: 600px; max-width: 600px;
...@@ -97,6 +62,7 @@ ...@@ -97,6 +62,7 @@
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script>
$(function() { $(function() {
var ctrlDown, shiftDown = false; var ctrlDown, shiftDown = false;
var ctrlKey = 17; var ctrlKey = 17;
...@@ -203,4 +169,5 @@ $(function() { ...@@ -203,4 +169,5 @@ $(function() {
} }
} }
</script>
{% endblock %} {% endblock %}
from django.conf.urls import patterns, url 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, AclUpdateView IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView
)
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url(r'^$', IndexView.as_view()), 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(), url(r'^vm/(?P<pk>\d+)/$', VmDetailView.as_view(),
name='dashboard.views.detail'), name='dashboard.views.detail'),
url(r'^vm/(?P<pk>\d+)/acl/$', AclUpdateView.as_view(model=Instance), url(r'^vm/(?P<pk>\d+)/acl/$', AclUpdateView.as_view(model=Instance),
name='dashboard.views.vm-acl'), name='dashboard.views.vm-acl'),
url(r'^vm/list/$', VmList.as_view(), name='dashboard.views.vm-list'), 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 import re
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
...@@ -7,12 +9,14 @@ from django.core.urlresolvers import reverse ...@@ -7,12 +9,14 @@ from django.core.urlresolvers import reverse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.views.generic import TemplateView, DetailView, View from django.views.generic import TemplateView, DetailView, View
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.http import HttpResponse
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from vm.models import Instance
from .tables import VmListTable from .tables import VmListTable
from vm.models import Instance, InstanceTemplate, InterfaceTemplate
from firewall.models import Vlan
from storage.models import Disk
class IndexView(TemplateView): class IndexView(TemplateView):
...@@ -24,9 +28,18 @@ class IndexView(TemplateView): ...@@ -24,9 +28,18 @@ class IndexView(TemplateView):
else: else:
user = None user = None
instances = Instance.objects.filter(owner=user)
context = super(IndexView, self).get_context_data(**kwargs) context = super(IndexView, self).get_context_data(**kwargs)
context.update({ 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 return context
...@@ -50,9 +63,10 @@ class VmDetailView(DetailView): ...@@ -50,9 +63,10 @@ class VmDetailView(DetailView):
instance = context['instance'] instance = context['instance']
if instance.node: if instance.node:
port = instance.vnc_port port = instance.vnc_port
host = instance.node.host.ipv4 host = str(instance.node.host.ipv4)
value = signing.dumps({'host': host, value = signing.dumps({'host': host,
'port': port}, key='asdasd') 'port': port},
key=getenv("PROXY_SECRET", 'asdasd')),
context.update({ context.update({
'vnc_url': '%s' % value 'vnc_url': '%s' % value
}) })
...@@ -84,8 +98,85 @@ class AclUpdateView(View, SingleObjectMixin): ...@@ -84,8 +98,85 @@ class AclUpdateView(View, SingleObjectMixin):
return redirect(instance) 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): class VmList(SingleTableView):
template_name = "dashboard/vm-list.html" template_name = "dashboard/vm-list.html"
model = Instance model = Instance
table_class = VmListTable table_class = VmListTable
table_pagination = False 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 @@ ...@@ -2,7 +2,8 @@
from django.contrib import admin from django.contrib import admin
from firewall.models import (Rule, Host, Vlan, Group, VlanGroup, Firewall, from firewall.models import (Rule, Host, Vlan, Group, VlanGroup, Firewall,
Domain, Record, Blacklist) Domain, Record, Blacklist,
SwitchPort, EthernetDevice)
from django import contrib from django import contrib
...@@ -105,22 +106,20 @@ class DomainAdmin(admin.ModelAdmin): ...@@ -105,22 +106,20 @@ class DomainAdmin(admin.ModelAdmin):
class RecordAdmin(admin.ModelAdmin): class RecordAdmin(admin.ModelAdmin):
list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner') 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
class BlacklistAdmin(admin.ModelAdmin): class BlacklistAdmin(admin.ModelAdmin):
list_display = ('ipv4', 'reason', 'created_at', 'modified_at') 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(Host, HostAdmin)
admin.site.register(Vlan, VlanAdmin) admin.site.register(Vlan, VlanAdmin)
admin.site.register(Rule, RuleAdmin) admin.site.register(Rule, RuleAdmin)
...@@ -130,3 +129,5 @@ admin.site.register(Firewall, FirewallAdmin) ...@@ -130,3 +129,5 @@ admin.site.register(Firewall, FirewallAdmin)
admin.site.register(Domain, DomainAdmin) admin.site.register(Domain, DomainAdmin)
admin.site.register(Record, RecordAdmin) admin.site.register(Record, RecordAdmin)
admin.site.register(Blacklist, BlacklistAdmin) 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 _ ...@@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.ipv6 import is_valid_ipv6_address from django.utils.ipv6 import is_valid_ipv6_address
from south.modelsinspector import add_introspection_rules from south.modelsinspector import add_introspection_rules
from django import forms from django import forms
from netaddr import IPNetwork, AddrFormatError from netaddr import IPAddress, IPNetwork, AddrFormatError, ZEROFILL
import re import re
...@@ -42,6 +42,76 @@ class MACAddressField(models.Field): ...@@ -42,6 +42,76 @@ class MACAddressField(models.Field):
add_introspection_rules([], ["firewall\.fields\.MACAddressField"]) 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): class IPNetworkFormField(forms.Field):
default_error_messages = { default_error_messages = {
'invalid': _(u'Enter a valid IP network. %s'), 'invalid': _(u'Enter a valid IP network. %s'),
...@@ -91,9 +161,11 @@ class IPNetworkField(models.Field): ...@@ -91,9 +161,11 @@ class IPNetworkField(models.Field):
if isinstance(value, IPNetwork): if isinstance(value, IPNetwork):
if self.version == 4: 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: 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 return value
def value_to_string(self, obj): def value_to_string(self, obj):
...@@ -110,7 +182,7 @@ class IPNetworkField(models.Field): ...@@ -110,7 +182,7 @@ class IPNetworkField(models.Field):
defaults.update(kwargs) defaults.update(kwargs)
return super(IPNetworkField, self).formfield(**defaults) 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): def val_alfanum(value):
......
...@@ -37,9 +37,9 @@ class Firewall: ...@@ -37,9 +37,9 @@ class Firewall:
return return
if self.proto == 6 and host.ipv6: if self.proto == 6 and host.ipv6:
ipaddr = host.ipv6 + '/112' ipaddr = str(host.ipv6) + '/112'
else: else:
ipaddr = host.ipv4 ipaddr = str(host.ipv4)
dport_sport = self.dportsport(rule) dport_sport = self.dportsport(rule)
...@@ -75,11 +75,11 @@ class Firewall: ...@@ -75,11 +75,11 @@ class Firewall:
for vlan in rule.foreign_network.vlans.all(): for vlan in rule.foreign_network.vlans.all():
if rule.direction == '1': # going TO host if rule.direction == '1': # going TO host
self.iptables('-A INPUT -i %s %s %s -g %s' % 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')) 'LOG_ACC' if rule.accept else 'LOG_DROP'))
else: else:
self.iptables('-A OUTPUT -o %s %s %s -g %s' % 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')) 'LOG_ACC' if rule.accept else 'LOG_DROP'))
def vlan2vlan(self, l_vlan, rule): def vlan2vlan(self, l_vlan, rule):
...@@ -189,7 +189,7 @@ class Firewall: ...@@ -189,7 +189,7 @@ class Firewall:
for d_vlan in s_vlan.snat_to.all(): for d_vlan in s_vlan.snat_to.all():
self.iptablesnat('-A POSTROUTING -s %s -o %s -j SNAT ' self.iptablesnat('-A POSTROUTING -s %s -o %s -j SNAT '
'--to-source %s' % '--to-source %s' %
(str(s_vlan.network4), d_vlan.interface, (str(s_vlan.network4), d_vlan.name,
s_vlan.snat_ip)) s_vlan.snat_ip))
self.iptablesnat('COMMIT') self.iptablesnat('COMMIT')
...@@ -210,7 +210,7 @@ class Firewall: ...@@ -210,7 +210,7 @@ class Firewall:
for d_vlan in self.vlans: for d_vlan in self.vlans:
self.iptables('-N %s_%s' % (s_vlan, d_vlan)) self.iptables('-N %s_%s' % (s_vlan, d_vlan))
self.iptables('-A FORWARD -i %s -o %s -g %s_%s' % 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)) d_vlan))
# hosts' rules # hosts' rules
...@@ -320,81 +320,90 @@ def ipv6_to_arpa(ipv6): ...@@ -320,81 +320,90 @@ def ipv6_to_arpa(ipv6):
# ^ PTR # ^ PTR
# C CNAME # C CNAME
# : generic # : generic
# 'fqdn:s:ttl TXT
def dns(): def generate_ptr_records():
vlans = models.Vlan.objects.all()
# regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
DNS = [] DNS = []
for i_vlan in vlans: for host in models.Host.objects.order_by('vlan').all():
# m = regex.search(i_vlan.net4) rev = host.vlan.reverse_domain
rev = i_vlan.reverse_domain ipv4 = str(host.pub_ipv4 if host.pub_ipv4 and
not host.shared_ip else host.ipv4)
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) i = ipv4.split('.', 4)
reverse = (i_host.reverse if i_host.reverse and reverse = (host.reverse if host.reverse and
len(i_host.reverse) else i_host.get_fqdn()) len(host.reverse) else host.get_fqdn())
# ipv4 # ipv4
if i_host.ipv4: if host.ipv4:
DNS.append("^%s:%s:%s" % ( DNS.append("^%s:%s:%s" % (
(rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]), (rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
'd': int(i[3])}), 'd': int(i[3])}),
reverse, models.settings['dns_ttl'])) reverse, models.settings['dns_ttl']))
# ipv6 # ipv6
if i_host.ipv6: if host.ipv6:
DNS.append("^%s:%s:%s" % (ipv6_to_arpa(i_host.ipv6), DNS.append("^%s:%s:%s" % (ipv6_to_arpa(str(host.ipv6)),
reverse, models.settings['dns_ttl'])) reverse, models.settings['dns_ttl']))
return DNS
for domain in models.Domain.objects.all():
DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" % def txt_to_octal(txt):
(domain.name, settings['dns_hostname'], return '\\' + '\\'.join(['%03o' % ord(x) for x in txt])
models.settings['dns_ttl']))
def generate_records():
DNS = []
for r in models.Record.objects.all(): for r in models.Record.objects.all():
d = r.get_data() if r.type == 'A':
if d['type'] == 'A': DNS.append("+%s:%s:%s" % (r.fqdn, r.address, r.ttl))
DNS.append("+%s:%s:%s" % (d['name'], d['address'], d['ttl'])) elif r.type == 'AAAA':
elif d['type'] == 'AAAA':
DNS.append(":%s:28:%s:%s" % DNS.append(":%s:28:%s:%s" %
(d['name'], ipv6_to_octal(d['address']), d['ttl'])) (r.fqdn, ipv6_to_octal(r.address), r.ttl))
elif d['type'] == 'NS': elif r.type == 'NS':
DNS.append("&%s::%s:%s" % (d['name'], d['address'], d['ttl'])) DNS.append("&%s::%s:%s" % (r.fqdn, r.address, r.ttl))
elif d['type'] == 'CNAME': elif r.type == 'CNAME':
DNS.append("C%s:%s:%s" % (d['name'], d['address'], d['ttl'])) DNS.append("C%s:%s:%s" % (r.fqdn, r.address, r.ttl))
elif d['type'] == 'MX': elif r.type == 'MX':
mx = d['address'].split(':', 2) mx = r.address.split(':', 2)
DNS.append("@%(fqdn)s::%(mx)s:%(dist)s:%(ttl)s" % DNS.append("@%(fqdn)s::%(mx)s:%(dist)s:%(ttl)s" %
{'fqdn': d['name'], 'mx': mx[1], 'dist': mx[0], {'fqdn': r.fqdn, 'mx': mx[1], 'dist': mx[0],
'ttl': d['ttl']}) 'ttl': r.ttl})
elif d['type'] == 'PTR': elif r.type == 'PTR':
DNS.append("^%s:%s:%s" % (d['name'], d['address'], d['ttl'])) 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 return DNS
def prefix_to_mask(prefix): def dns():
t = [0, 0, 0, 0] DNS = []
for i in range(0, 4):
if prefix > i * 8 + 7: # host PTR record
t[i] = 255 DNS += generate_ptr_records()
elif i * 8 < prefix and prefix <= (i + 1) * 8:
t[i] = 256 - (2 ** ((i + 1) * 8 - prefix)) # domain SOA record
return ".".join([str(i) for i in t]) 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(): def dhcp():
vlans = models.Vlan.objects.all()
regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+' regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+'
r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$') r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$')
DHCP = [] DHCP = []
# /tools/dhcp3/dhcpd.conf.generated # /tools/dhcp3/dhcpd.conf.generated
for i_vlan in vlans: for i_vlan in models.Vlan.objects.all():
if(i_vlan.dhcp_pool): if(i_vlan.dhcp_pool):
m = regex.search(i_vlan.dhcp_pool) m = regex.search(i_vlan.dhcp_pool)
if(m or i_vlan.dhcp_pool == "manual"): if(m or i_vlan.dhcp_pool == "manual"):
...@@ -411,15 +420,15 @@ def dhcp(): ...@@ -411,15 +420,15 @@ def dhcp():
filename \"pxelinux.0\"; filename \"pxelinux.0\";
allow bootp; allow booting; allow bootp; allow booting;
}''' % { }''' % {
'net': i_vlan.net4, 'net': str(i_vlan.network4.network),
'netmask': prefix_to_mask(i_vlan.prefix4), 'netmask': str(i_vlan.network4.netmask),
'domain': i_vlan.domain, 'domain': i_vlan.domain,
'router': i_vlan.ipv4, 'router': i_vlan.ipv4,
'ntp': i_vlan.ipv4, 'ntp': i_vlan.ipv4,
'dnsserver': settings['rdns_ip'], 'dnsserver': settings['rdns_ip'],
'extra': ("range %s" % i_vlan.dhcp_pool 'extra': ("range %s" % i_vlan.dhcp_pool
if m else "deny unknown-clients"), if m else "deny unknown-clients"),
'interface': i_vlan.interface, 'interface': i_vlan.name,
'name': i_vlan.name, 'name': i_vlan.name,
'tftp': i_vlan.ipv4 'tftp': i_vlan.ipv4
}) })
...@@ -440,7 +449,25 @@ def dhcp(): ...@@ -440,7 +449,25 @@ def dhcp():
def vlan(): def vlan():
obj = models.Vlan.objects.values('vid', 'name', 'network4', 'network6') obj = models.Vlan.objects.values('vid', 'name', 'network4', 'network6')
return {x['name']: {'tag': x['vid'], retval = {x['name']: {'tag': x['vid'],
'type': 'internal',
'interfaces': [x['name']],
'addresses': [str(x['network4']), 'addresses': [str(x['network4']),
str(x['network6'])]} str(x['network6'])]}
for x in obj} 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'): ...@@ -47,16 +47,16 @@ def reloadtask(type='Host'):
if type in ["Host", "Record", "Domain", "Vlan"]: if type in ["Host", "Record", "Domain", "Vlan"]:
cache.add("dns_lock", "true", 30) cache.add("dns_lock", "true", 30)
if type == "Host": if type in ["Host", "Vlan"]:
cache.add("dhcp_lock", "true", 30) 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) cache.add("firewall_lock", "true", 30)
if type == "Blacklist": if type == "Blacklist":
cache.add("blacklist_lock", "true", 30) cache.add("blacklist_lock", "true", 30)
if type == "Vlan": if type in ["Vlan", "SwitchPort", "EthernetDevice"]:
cache.add("firewall_vlan_lock", "true", 30) cache.add("firewall_vlan_lock", "true", 30)
print type 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 ...@@ -6,7 +6,7 @@ from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions
from firewall.models import (Host, Vlan, Domain, Group, Record, Blacklist, from firewall.models import (Host, Vlan, Domain, Group, Record, Blacklist,
Rule, VlanGroup) Rule, VlanGroup, SwitchPort)
class LinkButton(BaseInput): class LinkButton(BaseInput):
...@@ -188,6 +188,28 @@ class RuleForm(ModelForm): ...@@ -188,6 +188,28 @@ class RuleForm(ModelForm):
model = Rule 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): class VlanForm(ModelForm):
helper = FormHelper() helper = FormHelper()
helper.layout = Layout( 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 import Table, A
from django_tables2.columns import LinkColumn, TemplateColumn 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): class BlacklistTable(Table):
...@@ -129,6 +129,17 @@ class RuleTable(Table): ...@@ -129,6 +129,17 @@ class RuleTable(Table):
order_by = 'direction' 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): class VlanTable(Table):
name = LinkColumn('network.vlan', args=[A('vid')]) name = LinkColumn('network.vlan', args=[A('vid')])
......
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
{% url "network.rule_list" as u %} {% url "network.rule_list" as u %}
{% trans "Rules" as t %} {% trans "Rules" as t %}
{% include "network/menu-item.html" with href=u text=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 %}"> <li class="dropdown{% if "groups" in request.path %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Groups <b class="caret"></b></a> <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, ...@@ -8,9 +8,12 @@ from .views import (IndexView,
BlacklistList, BlacklistDetail, BlacklistDelete, BlacklistList, BlacklistDetail, BlacklistDelete,
BlacklistCreate, BlacklistCreate,
RuleList, RuleDetail, RuleDelete, RuleCreate, RuleList, RuleDetail, RuleDelete, RuleCreate,
SwitchPortList, SwitchPortDetail, SwitchPortCreate,
SwitchPortDelete,
VlanGroupList, VlanGroupDetail, VlanGroupDelete, VlanGroupList, VlanGroupDetail, VlanGroupDelete,
VlanGroupCreate, VlanGroupCreate,
remove_host_group, add_host_group) remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device)
js_info_dict = { js_info_dict = {
'packages': ('network', ), 'packages': ('network', ),
...@@ -56,6 +59,14 @@ urlpatterns = patterns( ...@@ -56,6 +59,14 @@ urlpatterns = patterns(
url('^rules/create$', RuleCreate.as_view(), name='network.rule_create'), url('^rules/create$', RuleCreate.as_view(), name='network.rule_create'),
url('^rules/(?P<pk>\d+)/$', RuleDetail.as_view(), url('^rules/(?P<pk>\d+)/$', RuleDetail.as_view(),
name='network.rule'), 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/$', VlanList.as_view(), name='network.vlan_list'),
url('^vlans/create$', VlanCreate.as_view(), name='network.vlan_create'), url('^vlans/create$', VlanCreate.as_view(), name='network.vlan_create'),
url('^vlans/(?P<vid>\d+)/$', VlanDetail.as_view(), name='network.vlan'), url('^vlans/(?P<vid>\d+)/$', VlanDetail.as_view(), name='network.vlan'),
...@@ -71,10 +82,18 @@ urlpatterns = patterns( ...@@ -71,10 +82,18 @@ urlpatterns = patterns(
name="network.vlan_group_delete"), name="network.vlan_group_delete"),
url('^rules/delete/(?P<pk>\d+)/$', RuleDelete.as_view(), url('^rules/delete/(?P<pk>\d+)/$', RuleDelete.as_view(),
name="network.rule_delete"), name="network.rule_delete"),
# non class based views
url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group, url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group,
name='network.remove_host_group'), name='network.remove_host_group'),
url('^hosts/(?P<pk>\d+)/add/$', add_host_group, url('^hosts/(?P<pk>\d+)/add/$', add_host_group,
name='network.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, url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict,
name="network.js_catalog"), name="network.js_catalog"),
) )
...@@ -7,13 +7,13 @@ from django.http import HttpResponse ...@@ -7,13 +7,13 @@ from django.http import HttpResponse
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from firewall.models import (Host, Vlan, Domain, Group, Record, Blacklist, from firewall.models import (Host, Vlan, Domain, Group, Record, Blacklist,
Rule, VlanGroup) Rule, VlanGroup, SwitchPort, EthernetDevice)
from .tables import (HostTable, VlanTable, SmallHostTable, DomainTable, from .tables import (HostTable, VlanTable, SmallHostTable, DomainTable,
GroupTable, RecordTable, BlacklistTable, RuleTable, GroupTable, RecordTable, BlacklistTable, RuleTable,
VlanGroupTable, SmallRuleTable, SmallGroupRuleTable, VlanGroupTable, SmallRuleTable, SmallGroupRuleTable,
SmallRecordTable) SmallRecordTable, SwitchPortTable)
from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm, from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm,
BlacklistForm, RuleForm, VlanGroupForm) BlacklistForm, RuleForm, VlanGroupForm, SwitchPortForm)
from django.contrib import messages from django.contrib import messages
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
...@@ -530,6 +530,50 @@ class RuleDelete(DeleteView): ...@@ -530,6 +530,50 @@ class RuleDelete(DeleteView):
return reverse_lazy('network.rule_list') 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): class VlanList(SingleTableView):
model = Vlan model = Vlan
table_class = VlanTable table_class = VlanTable
...@@ -690,3 +734,47 @@ def add_host_group(request, **kwargs): ...@@ -690,3 +734,47 @@ def add_host_group(request, **kwargs):
'group': group 'group': group
})) }))
return redirect(reverse_lazy('network.host', kwargs=kwargs)) 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 # coding=utf-8
from contextlib import contextmanager
import logging import logging
import uuid import uuid
from django.contrib.auth.models import User
from django.db.models import (Model, BooleanField, CharField, DateTimeField, from django.db.models import (Model, BooleanField, CharField, DateTimeField,
ForeignKey, TextField) ForeignKey)
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 model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from sizefield.models import FileSizeField from sizefield.models import FileSizeField
from .tasks import local_tasks, remote_tasks from .tasks import local_tasks, remote_tasks
from common.models import ActivityModel, activitycontextimpl
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -51,7 +52,7 @@ class Disk(TimeStampedModel): ...@@ -51,7 +52,7 @@ class Disk(TimeStampedModel):
ready = BooleanField(default=False) ready = BooleanField(default=False)
dev_num = CharField(default='a', max_length=1, dev_num = CharField(default='a', max_length=1,
verbose_name=_("device number")) verbose_name=_("device number"))
removed = DateTimeField(blank=True, default=None, null=True) destroyed = DateTimeField(blank=True, default=None, null=True)
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
...@@ -84,7 +85,7 @@ class Disk(TimeStampedModel): ...@@ -84,7 +85,7 @@ class Disk(TimeStampedModel):
return { return {
'qcow2-norm': 'qcow2', 'qcow2-norm': 'qcow2',
'qcow2-snap': 'qcow2', 'qcow2-snap': 'qcow2',
'iso': 'iso', 'iso': 'raw',
'raw-ro': 'raw', 'raw-ro': 'raw',
'raw-rw': 'raw', 'raw-rw': 'raw',
}[self.type] }[self.type]
...@@ -92,10 +93,12 @@ class Disk(TimeStampedModel): ...@@ -92,10 +93,12 @@ class Disk(TimeStampedModel):
@property @property
def device_type(self): def device_type(self):
return { return {
'qcow2': 'vd', 'qcow2-norm': 'vd',
'raw': 'vd', 'qcow2-snap': 'vd',
'iso': 'hd', 'iso': 'hd',
}[self.format] 'raw-ro': 'vd',
'raw-rw': 'vd',
}[self.type]
def is_in_use(self): def is_in_use(self):
return self.instance_set.exclude(state='SHUTOFF').exists() return self.instance_set.exclude(state='SHUTOFF').exists()
...@@ -126,7 +129,8 @@ class Disk(TimeStampedModel): ...@@ -126,7 +129,8 @@ class Disk(TimeStampedModel):
'source': self.path, 'source': self.path,
'driver_type': self.format, 'driver_type': self.format,
'driver_cache': 'default', '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): def get_disk_desc(self):
...@@ -160,30 +164,24 @@ class Disk(TimeStampedModel): ...@@ -160,30 +164,24 @@ class Disk(TimeStampedModel):
if self.ready: if self.ready:
return False return False
act = DiskActivity(activity_code='storage.Disk.deploy') with disk_activity(code_suffix='deploy', disk=self,
act.disk = self task_uuid=task_uuid, user=user) as act:
act.started = timezone.now()
act.state = 'PENDING'
act.task_uuid = task_uuid
act.user = user
act.save()
# Delegate create / snapshot jobs # Delegate create / snapshot jobs
queue_name = self.datastore.hostname + ".storage" queue_name = self.datastore.hostname + ".storage"
disk_desc = self.get_disk_desc() disk_desc = self.get_disk_desc()
if self.type == 'qcow2-snap': if self.type == 'qcow2-snap':
act.update_state('CREATING SNAPSHOT') with act.sub_activity('creating_snapshot'):
remote_tasks.snapshot.apply_async(args=[disk_desc], remote_tasks.snapshot.apply_async(args=[disk_desc],
queue=queue_name).get() queue=queue_name).get()
else: else:
act.update_state('CREATING DISK') with act.sub_activity('creating_disk'):
remote_tasks.create.apply_async(args=[disk_desc], remote_tasks.create.apply_async(args=[disk_desc],
queue=queue_name).get() queue=queue_name).get()
self.ready = True self.ready = True
self.save() self.save()
act.finish('SUCCESS')
return True return True
def deploy_async(self, user=None): def deploy_async(self, user=None):
...@@ -192,17 +190,17 @@ class Disk(TimeStampedModel): ...@@ -192,17 +190,17 @@ class Disk(TimeStampedModel):
local_tasks.deploy.apply_async(args=[self, user], local_tasks.deploy.apply_async(args=[self, user],
queue="localhost.man") queue="localhost.man")
def remove(self, user=None, task_uuid=None): def destroy(self, user=None, task_uuid=None):
# TODO add activity logging # TODO add activity logging
self.removed = timezone.now() self.destroyed = timezone.now()
self.save() self.save()
def remove_async(self, user=None): def destroy_async(self, user=None):
local_tasks.remove.apply_async(args=[self, user], local_tasks.destroy.apply_async(args=[self, user],
queue='localhost.man') queue='localhost.man')
def restore(self, user=None, task_uuid=None): def restore(self, user=None, task_uuid=None):
"""Restore removed disk. """Restore destroyed disk.
""" """
# TODO # TODO
pass pass
...@@ -211,7 +209,7 @@ class Disk(TimeStampedModel): ...@@ -211,7 +209,7 @@ class Disk(TimeStampedModel):
local_tasks.restore.apply_async(args=[self, user], local_tasks.restore.apply_async(args=[self, user],
queue='localhost.man') queue='localhost.man')
def save_as(self, name, user=None, task_uuid=None): def save_as(self, user=None, task_uuid=None):
mapping = { mapping = {
'qcow2-snap': ('qcow2-norm', self.base), 'qcow2-snap': ('qcow2-norm', self.base),
} }
...@@ -224,19 +222,14 @@ class Disk(TimeStampedModel): ...@@ -224,19 +222,14 @@ class Disk(TimeStampedModel):
# from this point on, the caller has to guarantee that the disk is not # from this point on, the caller has to guarantee that the disk is not
# going to be used until the operation is complete # going to be used until the operation is complete
act = DiskActivity(activity_code='storage.Disk.save_as') with disk_activity(code_suffix='save_as', disk=self,
act.disk = self task_uuid=task_uuid, user=user):
act.started = timezone.now()
act.state = 'PENDING'
act.task_uuid = task_uuid
act.user = user
act.save()
filename = str(uuid.uuid4()) filename = str(uuid.uuid4())
new_type, new_base = mapping[self.type] new_type, new_base = mapping[self.type]
disk = Disk.objects.create(base=new_base, datastore=self.datastore, disk = Disk.objects.create(base=new_base, datastore=self.datastore,
filename=filename, name=name, filename=filename, name=self.name,
size=self.size, type=new_type) size=self.size, type=new_type)
queue_name = self.datastore.hostname + ".storage" queue_name = self.datastore.hostname + ".storage"
...@@ -246,31 +239,38 @@ class Disk(TimeStampedModel): ...@@ -246,31 +239,38 @@ class Disk(TimeStampedModel):
disk.ready = True disk.ready = True
disk.save() disk.save()
act.finish('SUCCESS')
return disk return disk
class DiskActivity(TimeStampedModel): class DiskActivity(ActivityModel):
activity_code = CharField(verbose_name=_('activity_code'), max_length=100) disk = ForeignKey(Disk, related_name='activity_log',
task_uuid = CharField(verbose_name=_('task_uuid'), blank=True, help_text=_('Disk this activity works on.'),
max_length=50, null=True, unique=True) verbose_name=_('disk'))
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): @classmethod
if not self.finished: def create(cls, code_suffix, disk, task_uuid=None, user=None):
self.finished = timezone.now() act = cls(activity_code='storage.Disk.' + code_suffix,
self.result = result disk=disk, parent=None, started=timezone.now(),
self.state = 'COMPLETED' task_uuid=task_uuid, user=user)
self.save() act.save()
return act
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
@contextmanager
def sub_activity(self, code_suffix, task_uuid=None):
act = self.create_sub(code_suffix, task_uuid)
return activitycontextimpl(act)
@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): ...@@ -7,8 +7,8 @@ def deploy(disk, user):
@celery.task @celery.task
def remove(disk, user): def destroy(disk, user):
disk.remove(task_uuid=remove.request.id, user=user) disk.destroy(task_uuid=destroy.request.id, user=user)
@celery.task @celery.task
......
from manager.mancelery import celery from manager.mancelery import celery
# TODO: Keep syncronhised with Instance funcs # TODO: Keep synchronised with Instance funcs
@celery.task @celery.task
def deploy(instance, user): def deploy(instance, user):
''' Call Insance.deploy() from celery task.
'''
instance.deploy(task_uuid=deploy.request.id, user=user) instance.deploy(task_uuid=deploy.request.id, user=user)
def destroy(): @celery.task
pass def destroy(instance, user):
instance.destroy(task_uuid=destroy.request.id, user=user)
def save_as(): @celery.task
pass def sleep(instance, user):
instance.sleep(task_uuid=sleep.request.id, user=user)
def suspend(): @celery.task
pass def wake_up(instance, user):
instance.wake_up(task_uuid=wake_up.request.id, user=user)
def resume(): @celery.task
pass def shutdown(instance, user):
instance.shutdown(task_uuid=shutdown.request.id, user=user)
def restart(): @celery.task
pass def reset(instance, user):
instance.reset(task_uuid=reset.request.id, user=user)
def reset(): @celery.task
pass def reboot(instance, user):
instance.reboot(task_uuid=reboot.request.id, user=user)
def migrate(): @celery.task
pass 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