Commit 986782b5 by Őry Máté

Merge branch 'master' into feature-template-list

Conflicts:
	circle/dashboard/views.py
parents 6ad5c00e 0963fa06
......@@ -20,9 +20,12 @@
from os import environ
from sys import argv
from base import * # noqa
if 'runserver' in argv:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
########## HOST CONFIGURATION
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/
......
......@@ -21,18 +21,22 @@ from django import contrib
from django.contrib.auth.admin import UserAdmin, GroupAdmin
from django.contrib.auth.models import User, Group
from dashboard.models import Profile, GroupProfile
from dashboard.models import Profile, GroupProfile, ConnectCommand
class ProfileInline(contrib.admin.TabularInline):
model = Profile
class CommandInline(contrib.admin.TabularInline):
model = ConnectCommand
class GroupProfileInline(contrib.admin.TabularInline):
model = GroupProfile
UserAdmin.inlines = (ProfileInline, )
UserAdmin.inlines = (ProfileInline, CommandInline, )
GroupAdmin.inlines = (GroupProfileInline, )
contrib.admin.site.unregister(User)
......
......@@ -54,7 +54,9 @@ from .models import Profile, GroupProfile
from circle.settings.base import LANGUAGES, MAX_NODE_RAM
from django.utils.translation import string_concat
from .virtvalidator import domain_validator
from .validators import domain_validator
from dashboard.models import ConnectCommand
LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")"))
for l in LANGUAGES)
......@@ -176,7 +178,14 @@ class GroupCreateForm(forms.ModelForm):
self.fields['org_id'] = forms.ChoiceField(
# TRANSLATORS: directory like in LDAP
choices=choices, required=False, label=_('Directory identifier'))
if not new_groups:
if new_groups:
self.fields['org_id'].help_text = _(
"If you select an item here, the members of this directory "
"group will be automatically added to the group at the time "
"they log in. Please note that other users (those with "
"permissions like yours) may also automatically become a "
"group co-owner).")
else:
self.fields['org_id'].widget = HiddenInput()
def save(self, commit=True):
......@@ -1057,6 +1066,22 @@ class UserKeyForm(forms.ModelForm):
return super(UserKeyForm, self).clean()
class ConnectCommandForm(forms.ModelForm):
class Meta:
fields = ('name', 'access_method', 'template')
model = ConnectCommand
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super(ConnectCommandForm, self).__init__(*args, **kwargs)
def clean(self):
if self.user:
self.instance.user = self.user
return super(ConnectCommandForm, self).clean()
class TraitsForm(forms.ModelForm):
class Meta:
......
......@@ -46,8 +46,10 @@ from acl.models import AclBase
from common.models import HumanReadableObject, create_readable, Encoder
from vm.tasks.agent_tasks import add_keys, del_keys
from vm.models.instance import ACCESS_METHODS
from .store_api import Store, NoStoreException, NotOkException
from .validators import connect_command_template_validator
logger = getLogger(__name__)
......@@ -100,6 +102,25 @@ class Notification(TimeStampedModel):
self.message_data = None if value is None else value.to_dict()
class ConnectCommand(Model):
user = ForeignKey(User, related_name='command_set')
access_method = CharField(max_length=10, choices=ACCESS_METHODS,
verbose_name=_('access method'),
help_text=_('Type of the remote access method.'))
name = CharField(max_length="128", verbose_name=_('name'), blank=False,
help_text=_("Name of your custom command."))
template = CharField(blank=True, null=True, max_length=256,
verbose_name=_('command template'),
help_text=_('Template for connection command string. '
'Available parameters are: '
'username, password, '
'host, port.'),
validators=[connect_command_template_validator])
def __unicode__(self):
return self.template
class Profile(Model):
user = OneToOneField(User)
preferred_language = CharField(verbose_name=_('preferred language'),
......@@ -129,6 +150,25 @@ class Profile(Model):
default=2048 * 1024 * 1024,
help_text=_('Disk quota in mebibytes.'))
def get_connect_commands(self, instance, use_ipv6=False):
""" Generate connection command based on template."""
single_command = instance.get_connect_command(use_ipv6)
if single_command: # can we even connect to that VM
commands = self.user.command_set.filter(
access_method=instance.access_method)
if commands.count() < 1:
return [single_command]
else:
return [
command.template % {
'port': instance.get_connect_port(use_ipv6=use_ipv6),
'host': instance.get_connect_host(use_ipv6=use_ipv6),
'password': instance.pw,
'username': 'cloud',
} for command in commands]
else:
return []
def notify(self, subject, template, context=None, valid_until=None,
**kwargs):
if context is not None:
......
......@@ -654,7 +654,8 @@ textarea[name="list-new-namelist"] {
width: 130px;
}
#vm-details-connection-string-copy {
.vm-details-connection-string-copy,
#vm-details-pw-show {
cursor: pointer;
}
......@@ -681,10 +682,9 @@ textarea[name="list-new-namelist"] {
max-width: 200px;
}
#dashboard-vm-details-connect-command {
.dashboard-vm-details-connect-command {
/* for mobile view */
margin-bottom: 20px;
}
#store-list-list {
......@@ -868,6 +868,12 @@ textarea[name="list-new-namelist"] {
padding: 5px 0px;
}
#profile-key-list-table td:last-child, #profile-key-list-table th:last-child,
#profile-command-list-table td:last-child, #profile-command-list-table th:last-child,
#profile-command-list-table td:nth-child(2), #profile-command-list-table th:nth-child(2) {
text-align: center;
vertical-align: middle;
}
#vm-list-table .migrating-icon {
-webkit-animation: passing 2s linear infinite;
......
......@@ -105,19 +105,20 @@ $(function() {
$("#vm-details-pw-show").click(function() {
var input = $(this).parent("div").children("input");
var eye = $(this).children("#vm-details-pw-eye");
var span = $(this);
eye.tooltip("destroy")
span.tooltip("destroy")
if(eye.hasClass("fa-eye")) {
eye.removeClass("fa-eye").addClass("fa-eye-slash");
input.prop("type", "text");
input.focus();
eye.prop("title", "Hide password");
input.select();
span.prop("title", gettext("Hide password"));
} else {
eye.removeClass("fa-eye-slash").addClass("fa-eye");
input.prop("type", "password");
eye.prop("title", "Show password");
span.prop("title", gettext("Show password"));
}
eye.tooltip();
span.tooltip();
});
/* change password confirmation */
......@@ -198,7 +199,7 @@ $(function() {
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
$("#vm-details-rename-name").focus();
$("#vm-details-rename-name").select();
return false;
});
......@@ -206,7 +207,7 @@ $(function() {
$(".vm-details-home-edit-name-click").click(function() {
$(".vm-details-home-edit-name-click").hide();
$("#vm-details-home-rename").show();
$("input", $("#vm-details-home-rename")).focus();
$("input", $("#vm-details-home-rename")).select();
return false;
});
......@@ -306,8 +307,8 @@ $(function() {
});
// select connection string
$("#vm-details-connection-string-copy").click(function() {
$("#vm-details-connection-string").focus();
$(".vm-details-connection-string-copy").click(function() {
$(this).parent("div").find("input").select();
});
$("a.operation-password_reset").click(function() {
......
......@@ -25,6 +25,7 @@ from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
from vm.models import Node, InstanceTemplate, Lease
from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey
from dashboard.models import ConnectCommand
class NodeListTable(Table):
......@@ -249,5 +250,41 @@ class UserKeyListTable(Table):
class Meta:
model = UserKey
attrs = {'class': ('table table-bordered table-striped table-hover')}
attrs = {'class': ('table table-bordered table-striped table-hover'),
'id': "profile-key-list-table"}
fields = ('name', 'fingerprint', 'created', 'actions')
prefix = "key-"
empty_text = _("You haven't added any public keys yet.")
class ConnectCommandListTable(Table):
name = LinkColumn(
'dashboard.views.connect-command-detail',
args=[A('pk')],
attrs={'th': {'data-sort': "string"}}
)
access_method = Column(
verbose_name=_("Access method"),
attrs={'th': {'data-sort': "string"}}
)
template = Column(
verbose_name=_("Template"),
attrs={'th': {'data-sort': "string"}}
)
actions = TemplateColumn(
verbose_name=_("Actions"),
template_name=("dashboard/connect-command-list/column-command"
"-actions.html"),
orderable=False,
)
class Meta:
model = ConnectCommand
attrs = {'class': ('table table-bordered table-striped table-hover'),
'id': "profile-command-list-table"}
fields = ('name', 'access_method', 'template', 'actions')
prefix = "cmd-"
empty_text = _(
"You don't have any custom connection commands yet. You can "
"specify commands to be displayed on VM detail pages instead of "
"the defaults.")
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create command template" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.views.profile-preferences" %}">{% trans "Back" %}</a>
<h3 class="no-margin"><i class="fa fa-code"></i> {% trans "Create new command template" %}</h3>
</div>
<div class="panel-body">
<form method="POST">
{% csrf_token %}
{{ form.name|as_crispy_field }}
{{ form.access_method|as_crispy_field }}
{{ form.template|as_crispy_field }}
<p class="text-muted">
{% trans "Examples" %}
</p>
<p>
<strong>SSH:</strong>
<span class="text-muted">
sshpass -p %(password)s ssh -o StrictHostKeyChecking=no cloud@%(host)s -p %(port)d
</span>
</p>
<p>
<strong>RDP:</strong>
<span class="text-muted">
rdesktop %(host)s:%(port)d -u cloud -p %(password)s
</span>
</p>
<input type="submit" class="btn btn-primary" value="{% trans "Save" %}">
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Edit command template" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.views.profile-preferences" %}">{% trans "Back" %}</a>
<h3 class="no-margin"><i class="fa fa-code"></i> {% trans "Edit command template" %}</h3>
</div>
<div class="panel-body">
<form method="POST">
{% csrf_token %}
{{ form.name|as_crispy_field }}
{{ form.access_method|as_crispy_field }}
{{ form.template|as_crispy_field }}
<p class="text-muted">
{% trans "Examples" %}
</p>
<p>
<strong>SSH:</strong>
<span class="text-muted">
sshpass -p %(password)s ssh -o StrictHostKeyChecking=no cloud@%(host)s -p %(port)d
</span>
</p>
<p>
<strong>RDP:</strong>
<span class="text-muted">
rdesktop %(host)s:%(port)d -u cloud -p %(password)s
</span>
</p>
<input type="submit" class="btn btn-primary" value="{% trans "Save" %}">
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% load i18n %}
<a href="{% url "dashboard.views.connect-command-detail" pk=record.pk%}" id="template-list-edit-button" class="btn btn-default btn-xs" title="{% trans "Edit" %}">
<i class="fa fa-edit"></i>
</a>
<a data-template-pk="{{ record.pk }}" href="{% url "dashboard.views.connect-command-delete" pk=record.pk %}" class="btn btn-danger btn-xs template-delete" title="{% trans "Delete" %}">
<i class="fa fa-times"></i>
</a>
{% load crispy_forms_tags %}
{% load i18n %}
<p class="text-muted">
{% trans "User groups allow sharing templates or other resources with multiple users at once." %}
</p>
<form method="POST" action="{% url "dashboard.views.group-create" %}">
{% csrf_token %}
......
......@@ -66,4 +66,20 @@
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a href="{% url "dashboard.views.connect-command-create" %}"
class="pull-right btn btn-success btn-xs" style="margin-right: 10px;">
<i class="fa fa-plus"></i> {% trans "add command template" %}
</a>
<h3 class="no-margin"><i class="fa fa-code"></i> {% trans "Command templates" %}</h3>
</div>
<div class="panel-body">
{% render_table connectcommand_table %}
</div>
</div>
</div>
</div>
{% endblock %}
......@@ -98,8 +98,9 @@
<div class="input-group">
<input type="text" id="vm-details-pw-input" class="form-control input-sm input-tags"
value="{{ instance.pw }}" spellcheck="false"/>
<span class="input-group-addon input-tags" id="vm-details-pw-show">
<i class="fa fa-eye" id="vm-details-pw-eye" title="Show password"></i>
<span class="input-group-addon input-tags" id="vm-details-pw-show"
title="{% trans "Show password" %}" data-container="body">
<i class="fa fa-eye" id="vm-details-pw-eye"></i>
</span>
</div>
</dd>
......@@ -111,16 +112,29 @@
</div>
</dd>
</dl>
<div class="input-group" id="dashboard-vm-details-connect-command">
{% for c in connect_commands %}
<div class="input-group dashboard-vm-details-connect-command">
<span class="input-group-addon input-tags">{% trans "Command" %}</span>
<input type="text" spellcheck="false"
value="{% if instance.get_connect_command %}{{ instance.get_connect_command }}{% else %}{% trans "Connection is not possible." %}{% endif %}"
value="{{ c }}"
id="vm-details-connection-string" class="form-control input-tags" />
<span class="input-group-addon input-tags vm-details-connection-string-copy"
title="{% trans "Select all" %}" data-container="body">
<i class="fa fa-copy"></i>
</span>
</div>
{% empty %}
<div class="input-group dashboard-vm-details-connect-command">
<span class="input-group-addon input-tags">{% trans "Command" %}</span>
<input type="text" spellcheck="false" value="{% trans "Connection is not possible." %}"
id="vm-details-connection-string" class="form-control input-tags" />
<span class="input-group-addon input-tags" id="vm-details-connection-string-copy">
<i class="fa fa-copy" title="{% trans "Select all" %}"></i>
</span>
</div>
{% endfor %}
</div>
<div class="col-md-8" id="vm-detail-pane">
<div class="panel panel-default" id="vm-detail-panel">
......
......@@ -94,7 +94,13 @@
<dt>{% trans "Template" %}:</dt>
<dd>
{% if instance.template %}
{{ instance.template.name }}
{% if can_link_template %}
<a href="{{ instance.template.get_absolute_url }}">
{{ instance.template.name }}
</a>
{% else %}
{{ instance.template.name }}
{% endif %}
{% else %}
-
{% endif %}
......
......@@ -39,6 +39,7 @@ from .views import (
get_vm_screenshot,
ProfileView, toggle_use_gravatar, UnsubscribeFormView,
UserKeyDelete, UserKeyDetail, UserKeyCreate,
ConnectCommandDelete, ConnectCommandDetail, ConnectCommandCreate,
StoreList, store_download, store_upload, store_get_upload_url, StoreRemove,
store_new_directory, store_refresh_toplist,
VmTraitsUpdate, VmRawDataUpdate,
......@@ -177,6 +178,16 @@ urlpatterns = patterns(
UserKeyCreate.as_view(),
name="dashboard.views.userkey-create"),
url(r'^conncmd/delete/(?P<pk>\d+)/$',
ConnectCommandDelete.as_view(),
name="dashboard.views.connect-command-delete"),
url(r'^conncmd/(?P<pk>\d+)/$',
ConnectCommandDetail.as_view(),
name="dashboard.views.connect-command-detail"),
url(r'^conncmd/create/$',
ConnectCommandCreate.as_view(),
name="dashboard.views.connect-command-create"),
url(r'^autocomplete/', include('autocomplete_light.urls')),
url(r"^store/list/$", StoreList.as_view(),
......
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from lxml import etree as ET
import logging
......@@ -29,3 +31,27 @@ def domain_validator(value):
relaxng.assertValid(parsed_xml)
except Exception as e:
raise ValidationError(e.message)
def connect_command_template_validator(value):
"""Validate value as a connect command template.
>>> try: connect_command_template_validator("%(host)s")
... except ValidationError as e: print e
...
>>> connect_command_template_validator("%(host)s")
>>> try: connect_command_template_validator("%(host)s %s")
... except ValidationError as e: print e
...
[u'Invalid template string.']
"""
try:
value % {
'username': "uname",
'password': "pw",
'host': "111.111.111.111",
'port': 12345,
}
except (KeyError, TypeError, ValueError):
raise ValidationError(_("Invalid template string."))
......@@ -71,12 +71,12 @@ from .forms import (
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm,
TemplateListSearchForm,
TemplateListSearchForm, ConnectCommandForm
)
from .tables import (
NodeListTable, TemplateListTable, LeaseListTable,
GroupListTable, UserKeyListTable
GroupListTable, UserKeyListTable, ConnectCommandListTable,
)
from common.models import (
HumanReadableObject, HumanReadableException, fetch_human_exception,
......@@ -88,7 +88,8 @@ from vm.models import (
)
from storage.models import Disk
from firewall.models import Vlan, Host, Rule
from .models import Favourite, Profile, GroupProfile, FutureMember
from .models import (Favourite, Profile, GroupProfile, FutureMember,
ConnectCommand)
from .store_api import Store, NoStoreException, NotOkException
......@@ -366,6 +367,7 @@ class VmDetailView(CheckedDetailView):
kwargs={'pk': self.object.pk}),
'ops': ops,
'op': {i.op: i for i in ops},
'connect_commands': user.profile.get_connect_commands(instance)
})
# activity data
......@@ -396,7 +398,7 @@ class VmDetailView(CheckedDetailView):
# resources forms
can_edit = (
instance in Instance.get_objects_with_level("owner", user)
instance.has_level(user, "owner")
and self.request.user.has_perm("vm.change_resources"))
context['resources_form'] = VmResourcesForm(
can_edit=can_edit, instance=instance)
......@@ -409,6 +411,11 @@ class VmDetailView(CheckedDetailView):
context['can_change_resources'] = self.request.user.has_perm(
"vm.change_resources")
# can link template
context['can_link_template'] = (
instance.template and instance.template.has_level(user, "operator")
)
return context
def post(self, request, *args, **kwargs):
......@@ -1538,7 +1545,7 @@ class TemplateChoose(LoginRequiredMixin, TemplateView):
self.request.user)
context.update({
'box_title': _('Choose template'),
'ajax_title': False,
'ajax_title': True,
'template': "dashboard/_template-choose.html",
'templates': templates.all(),
})
......@@ -2093,7 +2100,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
context.update({
'template': 'dashboard/_vm-create-2.html',
'box_title': _('Customize VM'),
'ajax_title': False,
'ajax_title': True,
'vm_create_form': form,
'template_o': templates.get(pk=template),
})
......@@ -2101,7 +2108,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
context.update({
'template': 'dashboard/_vm-create-1.html',
'box_title': _('Create a VM'),
'ajax_title': False,
'ajax_title': True,
'templates': templates.all(),
})
return self.render_to_response(context)
......@@ -2291,9 +2298,9 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/group-create.html',
'box_title': 'Create a Group',
'box_title': _('Create a Group'),
'form': form,
'ajax_title': True,
})
return self.render_to_response(context)
......@@ -2961,11 +2968,16 @@ class MyPreferencesView(UpdateView):
user=self.request.user),
'change_language': MyProfileForm(instance=self.get_object()),
}
table = UserKeyListTable(
key_table = UserKeyListTable(
UserKey.objects.filter(user=self.request.user),
request=self.request)
table.page = None
context['userkey_table'] = table
key_table.page = None
context['userkey_table'] = key_table
cmd_table = ConnectCommandListTable(
self.request.user.command_set.all(),
request=self.request)
cmd_table.page = None
context['connectcommand_table'] = cmd_table
return context
def get_object(self, queryset=None):
......@@ -3370,6 +3382,82 @@ class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
return kwargs
class ConnectCommandDetail(LoginRequiredMixin, SuccessMessageMixin,
UpdateView):
model = ConnectCommand
template_name = "dashboard/connect-command-edit.html"
form_class = ConnectCommandForm
success_message = _("Successfully modified command template.")
def get(self, request, *args, **kwargs):
object = self.get_object()
if object.user != request.user:
raise PermissionDenied()
return super(ConnectCommandDetail, self).get(request, *args, **kwargs)
def get_success_url(self):
return reverse_lazy("dashboard.views.connect-command-detail",
kwargs=self.kwargs)
def post(self, request, *args, **kwargs):
object = self.get_object()
if object.user != request.user:
raise PermissionDenied()
return super(ConnectCommandDetail, self).post(request, args, kwargs)
def get_form_kwargs(self):
kwargs = super(ConnectCommandDetail, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
class ConnectCommandDelete(LoginRequiredMixin, DeleteView):
model = ConnectCommand
def get_success_url(self):
return reverse("dashboard.views.profile-preferences")
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def delete(self, request, *args, **kwargs):
object = self.get_object()
if object.user != request.user:
raise PermissionDenied()
object.delete()
success_url = self.get_success_url()
success_message = _("Command template successfully deleted.")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
class ConnectCommandCreate(LoginRequiredMixin, SuccessMessageMixin,
CreateView):
model = ConnectCommand
form_class = ConnectCommandForm
template_name = "dashboard/connect-command-create.html"
success_message = _("Successfully created a new command template.")
def get_success_url(self):
return reverse_lazy("dashboard.views.profile-preferences")
def get_form_kwargs(self):
kwargs = super(ConnectCommandCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
class HelpView(TemplateView):
def get_context_data(self, *args, **kwargs):
......
......@@ -101,7 +101,7 @@ def pull(dir="~/circle/circle"):
@roles('portal')
def update_portal(test=False):
"Update and restart portal+manager"
with _stopped("portal", "mancelery"):
with _stopped("portal", "manager"):
pull()
pip("circle", "~/circle/requirements.txt")
migrate()
......@@ -113,7 +113,7 @@ def update_portal(test=False):
@roles('portal')
def stop_portal(test=False):
"Stop portal and manager"
_stop_services("portal", "mancelery")
_stop_services("portal", "manager")
@roles('node')
......
......@@ -215,7 +215,7 @@ class Rule(models.Model):
dst = None
if host:
ip = (host.ipv4, host.ipv6_with_prefixlen)
ip = (host.ipv4, host.ipv6_with_host_prefixlen)
if self.direction == 'in':
dst = ip
else:
......@@ -530,14 +530,30 @@ class Host(models.Model):
def incoming_rules(self):
return self.rules.filter(direction='in')
@property
def ipv6_with_prefixlen(self):
@staticmethod
def create_ipnetwork(ip, prefixlen):
try:
net = IPNetwork(self.ipv6)
net.prefixlen = self.vlan.host_ipv6_prefixlen
return net
net = IPNetwork(ip)
net.prefixlen = prefixlen
except TypeError:
return None
else:
return net
@property
def ipv4_with_vlan_prefixlen(self):
return Host.create_ipnetwork(
self.ipv4, self.vlan.network4.prefixlen)
@property
def ipv6_with_vlan_prefixlen(self):
return Host.create_ipnetwork(
self.ipv6, self.vlan.network6.prefixlen)
@property
def ipv6_with_host_prefixlen(self):
return Host.create_ipnetwork(
self.ipv6, self.vlan.host_ipv6_prefixlen)
def get_external_ipv4(self):
return self.external_ipv4 if self.external_ipv4 else self.ipv4
......@@ -600,6 +616,19 @@ class Host(models.Model):
description='created by host.save()',
type='AAAA').save()
def get_network_config(self):
interface = {'addresses': []}
if self.ipv4 and self.vlan.network4:
interface['addresses'].append(str(self.ipv4_with_vlan_prefixlen))
interface['gw4'] = str(self.vlan.network4.ip)
if self.ipv6 and self.vlan.network6:
interface['addresses'].append(str(self.ipv6_with_vlan_prefixlen))
interface['gw6'] = str(self.vlan.network6.ip)
return interface
def enable_net(self):
for i in settings.get('default_host_groups', []):
self.groups.add(Group.objects.get(name=i))
......
......@@ -31,7 +31,6 @@ celery = Celery('manager',
'storage.tasks.local_tasks',
'storage.tasks.periodic_tasks',
'firewall.tasks.local_tasks',
'monitor.tasks.local_periodic_tasks',
'dashboard.tasks.local_periodic_tasks',
])
......@@ -42,20 +41,8 @@ celery.conf.update(
CELERY_QUEUES=(
Queue(HOSTNAME + '.man', Exchange('manager', type='direct'),
routing_key="manager"),
Queue(HOSTNAME + '.monitor', Exchange('monitor', type='direct'),
routing_key="monitor"),
),
CELERYBEAT_SCHEDULE={
'vm.update_domain_states': {
'task': 'vm.tasks.local_periodic_tasks.update_domain_states',
'schedule': timedelta(seconds=10),
'options': {'queue': 'localhost.man'}
},
'vm.garbage_collector': {
'task': 'vm.tasks.local_periodic_tasks.garbage_collector',
'schedule': timedelta(minutes=10),
'options': {'queue': 'localhost.man'}
},
'storage.periodic_tasks': {
'task': 'storage.tasks.periodic_tasks.garbage_collector',
'schedule': timedelta(hours=1),
......@@ -67,24 +54,6 @@ celery.conf.update(
'schedule': timedelta(hours=24),
'options': {'queue': 'localhost.man'}
},
'monitor.measure_response_time': {
'task': 'monitor.tasks.local_periodic_tasks.'
'measure_response_time',
'schedule': timedelta(seconds=30),
'options': {'queue': 'localhost.man'}
},
'monitor.check_celery_queues': {
'task': 'monitor.tasks.local_periodic_tasks.'
'check_celery_queues',
'schedule': timedelta(seconds=60),
'options': {'queue': 'localhost.man'}
},
'monitor.instance_per_template': {
'task': 'monitor.tasks.local_periodic_tasks.'
'instance_per_template',
'schedule': timedelta(seconds=30),
'options': {'queue': 'localhost.man'}
},
}
)
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from celery import Celery
from datetime import timedelta
from kombu import Queue, Exchange
from os import getenv
HOSTNAME = "localhost"
CACHE_URI = getenv("CACHE_URI", "pylibmc://127.0.0.1:11211/")
celery = Celery('monitor',
broker=getenv("AMQP_URI"),
include=['vm.tasks.local_periodic_tasks',
'monitor.tasks.local_periodic_tasks',
])
celery.conf.update(
CELERY_RESULT_BACKEND='cache',
CELERY_CACHE_BACKEND=CACHE_URI,
CELERY_TASK_RESULT_EXPIRES=300,
CELERY_QUEUES=(
Queue(HOSTNAME + '.monitor', Exchange('monitor', type='direct'),
routing_key="monitor"),
),
CELERYBEAT_SCHEDULE={
'vm.update_domain_states': {
'task': 'vm.tasks.local_periodic_tasks.update_domain_states',
'schedule': timedelta(seconds=10),
'options': {'queue': 'localhost.monitor'}
},
'monitor.measure_response_time': {
'task': 'monitor.tasks.local_periodic_tasks.'
'measure_response_time',
'schedule': timedelta(seconds=30),
'options': {'queue': 'localhost.monitor'}
},
'monitor.check_celery_queues': {
'task': 'monitor.tasks.local_periodic_tasks.'
'check_celery_queues',
'schedule': timedelta(seconds=60),
'options': {'queue': 'localhost.monitor'}
},
'monitor.instance_per_template': {
'task': 'monitor.tasks.local_periodic_tasks.'
'instance_per_template',
'schedule': timedelta(seconds=30),
'options': {'queue': 'localhost.monitor'}
},
}
)
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from celery import Celery
from datetime import timedelta
from kombu import Queue, Exchange
from os import getenv
HOSTNAME = "localhost"
CACHE_URI = getenv("CACHE_URI", "pylibmc://127.0.0.1:11211/")
celery = Celery('manager.slow',
broker=getenv("AMQP_URI"),
include=['vm.tasks.local_tasks',
'vm.tasks.local_periodic_tasks',
'storage.tasks.local_tasks',
'storage.tasks.periodic_tasks',
])
celery.conf.update(
CELERY_RESULT_BACKEND='cache',
CELERY_CACHE_BACKEND=CACHE_URI,
CELERY_TASK_RESULT_EXPIRES=300,
CELERY_QUEUES=(
Queue(HOSTNAME + '.man.slow', Exchange('manager.slow', type='direct'),
routing_key="manager.slow"),
),
CELERYBEAT_SCHEDULE={
'vm.garbage_collector': {
'task': 'vm.tasks.local_periodic_tasks.garbage_collector',
'schedule': timedelta(minutes=10),
'options': {'queue': 'localhost.man.slow'}
},
}
)
......@@ -278,7 +278,7 @@ class Disk(TimeStampedModel):
return Disk.create(base=self, datastore=self.datastore,
name=self.name, size=self.size,
type=new_type)
type=new_type, dev_num=self.dev_num)
def get_vmdisk_desc(self):
"""Serialize disk object to the vmdriver.
......
......@@ -41,7 +41,7 @@ from .models import (
Instance, InstanceActivity, InstanceTemplate, Interface, Node,
NodeActivity, pwgen
)
from .tasks import agent_tasks
from .tasks import agent_tasks, local_agent_tasks
from dashboard.store_api import Store, NoStoreException
......@@ -153,6 +153,7 @@ class AddInterfaceOperation(InstanceOperation):
self.rollback(net, activity)
raise
net.deploy()
local_agent_tasks.send_networking_commands(self.instance, activity)
def get_activity_name(self, kwargs):
return create_readable(ugettext_noop("add %(vlan)s interface"),
......@@ -218,6 +219,7 @@ class DownloadDiskOperation(InstanceOperation):
has_percentage = True
required_perms = ('storage.download_disk', )
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
async_queue = "localhost.man.slow"
def _operation(self, user, url, task, activity, name=None):
activity.result = url
......@@ -368,6 +370,7 @@ class MigrateOperation(InstanceOperation):
required_perms = ()
superuser_required = True
accept_states = ('RUNNING', )
async_queue = "localhost.man.slow"
def rollback(self, activity):
with activity.sub_activity(
......@@ -512,6 +515,7 @@ class SaveAsTemplateOperation(InstanceOperation):
abortable = True
required_perms = ('vm.create_template', )
accept_states = ('RUNNING', 'PENDING', 'STOPPED')
async_queue = "localhost.man.slow"
def is_preferred(self):
return (self.instance.is_base and
......@@ -667,6 +671,7 @@ class SleepOperation(InstanceOperation):
required_perms = ()
accept_states = ('RUNNING', )
resultant_state = 'SUSPENDED'
async_queue = "localhost.man.slow"
def is_preferred(self):
return (not self.instance.is_base and
......@@ -839,6 +844,7 @@ class FlushOperation(NodeOperation):
description = _("Disable node and move all instances to other ones.")
required_perms = ()
superuser_required = True
async_queue = "localhost.man.slow"
def on_abort(self, activity, error):
from manager.scheduler import TraitsUnsatisfiableException
......@@ -998,12 +1004,12 @@ class MountStoreOperation(EnsureAgentMixin, InstanceOperation):
except NoStoreException:
raise PermissionDenied # not show the button at all
def _operation(self):
def _operation(self, user):
inst = self.instance
queue = self.instance.get_remote_queue_name("agent")
host = urlsplit(settings.STORE_URL).hostname
username = Store(inst.owner).username
password = inst.owner.profile.smb_password
username = Store(user).username
password = user.profile.smb_password
agent_tasks.mount_store.apply_async(
queue=queue, args=(inst.vm_name, host, username, password))
......
......@@ -76,3 +76,8 @@ def get_keys(vm):
@celery.task(name='agent.send_expiration')
def send_expiration(vm, url):
pass
@celery.task(name='agent.change_ip')
def change_ip(vm, interfaces, dns):
pass
......@@ -19,7 +19,9 @@ from common.models import create_readable
from manager.mancelery import celery
from vm.tasks.agent_tasks import (restart_networking, change_password,
set_time, set_hostname, start_access_server,
cleanup, update)
cleanup, update, change_ip)
from firewall.models import Host
import time
from base64 import encodestring
from StringIO import StringIO
......@@ -31,13 +33,11 @@ from celery.result import TimeoutError
from monitor.client import Client
def send_init_commands(instance, act, vm):
def send_init_commands(instance, act):
vm = instance.vm_name
queue = instance.get_remote_queue_name("agent")
with act.sub_activity('cleanup', readable_name=ugettext_noop('cleanup')):
cleanup.apply_async(queue=queue, args=(vm, ))
with act.sub_activity('restart_networking',
readable_name=ugettext_noop('restart networking')):
restart_networking.apply_async(queue=queue, args=(vm, ))
with act.sub_activity('change_password',
readable_name=ugettext_noop('change password')):
change_password.apply_async(queue=queue, args=(vm, instance.pw))
......@@ -49,6 +49,17 @@ def send_init_commands(instance, act, vm):
queue=queue, args=(vm, instance.primary_host.hostname))
def send_networking_commands(instance, act):
queue = instance.get_remote_queue_name("agent")
with act.sub_activity('change_ip',
readable_name=ugettext_noop('change ip')):
change_ip.apply_async(queue=queue, args=(
instance.vm_name, ) + get_network_configs(instance))
with act.sub_activity('restart_networking',
readable_name=ugettext_noop('restart networking')):
restart_networking.apply_async(queue=queue, args=(instance.vm_name, ))
def create_agent_tar():
def exclude(tarinfo):
if tarinfo.name.startswith('./.git'):
......@@ -94,8 +105,9 @@ def agent_started(vm, version=None):
if not initialized:
measure_boot_time(instance)
send_init_commands(instance, act, vm)
send_init_commands(instance, act)
send_networking_commands(instance, act)
with act.sub_activity(
'start_access_server',
readable_name=ugettext_noop('start access server')
......@@ -134,6 +146,13 @@ def agent_stopped(vm):
pass
def get_network_configs(instance):
interfaces = {}
for host in Host.objects.filter(interface__instance=instance):
interfaces[str(host.mac)] = host.get_network_config()
return (interfaces, settings.FIREWALL_SETTINGS['rdns_ip'])
def update_agent(instance, act=None):
if act:
act = act.sub_activity(
......
description "CIRCLE manager"
start on runlevel [2345]
stop on runlevel [!2345]
pre-start script
start moncelery
start mancelery
start slowcelery
end script
post-stop script
stop moncelery
stop mancelery
stop slowcelery
end script
description "CIRCLE mancelery"
start on runlevel [2345]
stop on runlevel [!2345]
description "CIRCLE mancelery for common jobs"
respawn
respawn limit 30 30
setgid cloud
setuid cloud
......@@ -12,6 +10,5 @@ script
cd /home/cloud/circle/circle
. /home/cloud/.virtualenvs/circle/bin/activate
. /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py celery --app=manager.mancelery worker --autoreload --loglevel=info --hostname=mancelery -B -c 1
exec ./manage.py celery --app=manager.mancelery worker --autoreload --loglevel=info --hostname=mancelery -B -c 10
end script
description "CIRCLE moncelery for monitoring jobs"
respawn
respawn limit 30 30
setgid cloud
setuid cloud
script
cd /home/cloud/circle/circle
. /home/cloud/.virtualenvs/circle/bin/activate
. /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py celery --app=manager.moncelery worker --autoreload --loglevel=info --hostname=moncelery -B -c 3
end script
......@@ -12,4 +12,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate
exec /home/cloud/.virtualenvs/circle/bin/uwsgi --chdir=/home/cloud/circle/circle -H /home/cloud/.virtualenvs/circle --socket /tmp/uwsgi.sock --wsgi-file circle/wsgi.py --chmod-socket=666
end script
......@@ -14,4 +14,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py runserver '[::]:8080'
end script
description "CIRCLE mancelery for slow jobs"
respawn
respawn limit 30 30
setgid cloud
setuid cloud
script
cd /home/cloud/circle/circle
. /home/cloud/.virtualenvs/circle/bin/activate
. /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py celery --app=manager.slowcelery worker --autoreload --loglevel=info --hostname=slowcelery -B -c 5
end script
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