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 @@ ...@@ -20,9 +20,12 @@
from os import environ from os import environ
from sys import argv
from base import * # noqa from base import * # noqa
if 'runserver' in argv:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
########## HOST CONFIGURATION ########## HOST CONFIGURATION
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/ # See: https://docs.djangoproject.com/en/1.5/releases/1.5/
......
...@@ -21,18 +21,22 @@ from django import contrib ...@@ -21,18 +21,22 @@ from django import contrib
from django.contrib.auth.admin import UserAdmin, GroupAdmin from django.contrib.auth.admin import UserAdmin, GroupAdmin
from django.contrib.auth.models import User, Group 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): class ProfileInline(contrib.admin.TabularInline):
model = Profile model = Profile
class CommandInline(contrib.admin.TabularInline):
model = ConnectCommand
class GroupProfileInline(contrib.admin.TabularInline): class GroupProfileInline(contrib.admin.TabularInline):
model = GroupProfile model = GroupProfile
UserAdmin.inlines = (ProfileInline, ) UserAdmin.inlines = (ProfileInline, CommandInline, )
GroupAdmin.inlines = (GroupProfileInline, ) GroupAdmin.inlines = (GroupProfileInline, )
contrib.admin.site.unregister(User) contrib.admin.site.unregister(User)
......
...@@ -54,7 +54,9 @@ from .models import Profile, GroupProfile ...@@ -54,7 +54,9 @@ from .models import Profile, GroupProfile
from circle.settings.base import LANGUAGES, MAX_NODE_RAM from circle.settings.base import LANGUAGES, MAX_NODE_RAM
from django.utils.translation import string_concat 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], ")")) LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")"))
for l in LANGUAGES) for l in LANGUAGES)
...@@ -176,7 +178,14 @@ class GroupCreateForm(forms.ModelForm): ...@@ -176,7 +178,14 @@ class GroupCreateForm(forms.ModelForm):
self.fields['org_id'] = forms.ChoiceField( self.fields['org_id'] = forms.ChoiceField(
# TRANSLATORS: directory like in LDAP # TRANSLATORS: directory like in LDAP
choices=choices, required=False, label=_('Directory identifier')) 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() self.fields['org_id'].widget = HiddenInput()
def save(self, commit=True): def save(self, commit=True):
...@@ -1057,6 +1066,22 @@ class UserKeyForm(forms.ModelForm): ...@@ -1057,6 +1066,22 @@ class UserKeyForm(forms.ModelForm):
return super(UserKeyForm, self).clean() 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 TraitsForm(forms.ModelForm):
class Meta: class Meta:
......
...@@ -46,8 +46,10 @@ from acl.models import AclBase ...@@ -46,8 +46,10 @@ from acl.models import AclBase
from common.models import HumanReadableObject, create_readable, Encoder from common.models import HumanReadableObject, create_readable, Encoder
from vm.tasks.agent_tasks import add_keys, del_keys 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 .store_api import Store, NoStoreException, NotOkException
from .validators import connect_command_template_validator
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -100,6 +102,25 @@ class Notification(TimeStampedModel): ...@@ -100,6 +102,25 @@ class Notification(TimeStampedModel):
self.message_data = None if value is None else value.to_dict() 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): class Profile(Model):
user = OneToOneField(User) user = OneToOneField(User)
preferred_language = CharField(verbose_name=_('preferred language'), preferred_language = CharField(verbose_name=_('preferred language'),
...@@ -129,6 +150,25 @@ class Profile(Model): ...@@ -129,6 +150,25 @@ class Profile(Model):
default=2048 * 1024 * 1024, default=2048 * 1024 * 1024,
help_text=_('Disk quota in mebibytes.')) 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, def notify(self, subject, template, context=None, valid_until=None,
**kwargs): **kwargs):
if context is not None: if context is not None:
......
...@@ -654,7 +654,8 @@ textarea[name="list-new-namelist"] { ...@@ -654,7 +654,8 @@ textarea[name="list-new-namelist"] {
width: 130px; width: 130px;
} }
#vm-details-connection-string-copy { .vm-details-connection-string-copy,
#vm-details-pw-show {
cursor: pointer; cursor: pointer;
} }
...@@ -681,10 +682,9 @@ textarea[name="list-new-namelist"] { ...@@ -681,10 +682,9 @@ textarea[name="list-new-namelist"] {
max-width: 200px; max-width: 200px;
} }
#dashboard-vm-details-connect-command { .dashboard-vm-details-connect-command {
/* for mobile view */ /* for mobile view */
margin-bottom: 20px; margin-bottom: 20px;
} }
#store-list-list { #store-list-list {
...@@ -868,6 +868,12 @@ textarea[name="list-new-namelist"] { ...@@ -868,6 +868,12 @@ textarea[name="list-new-namelist"] {
padding: 5px 0px; 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 { #vm-list-table .migrating-icon {
-webkit-animation: passing 2s linear infinite; -webkit-animation: passing 2s linear infinite;
......
...@@ -105,19 +105,20 @@ $(function() { ...@@ -105,19 +105,20 @@ $(function() {
$("#vm-details-pw-show").click(function() { $("#vm-details-pw-show").click(function() {
var input = $(this).parent("div").children("input"); var input = $(this).parent("div").children("input");
var eye = $(this).children("#vm-details-pw-eye"); var eye = $(this).children("#vm-details-pw-eye");
var span = $(this);
eye.tooltip("destroy") span.tooltip("destroy")
if(eye.hasClass("fa-eye")) { if(eye.hasClass("fa-eye")) {
eye.removeClass("fa-eye").addClass("fa-eye-slash"); eye.removeClass("fa-eye").addClass("fa-eye-slash");
input.prop("type", "text"); input.prop("type", "text");
input.focus(); input.select();
eye.prop("title", "Hide password"); span.prop("title", gettext("Hide password"));
} else { } else {
eye.removeClass("fa-eye-slash").addClass("fa-eye"); eye.removeClass("fa-eye-slash").addClass("fa-eye");
input.prop("type", "password"); input.prop("type", "password");
eye.prop("title", "Show password"); span.prop("title", gettext("Show password"));
} }
eye.tooltip(); span.tooltip();
}); });
/* change password confirmation */ /* change password confirmation */
...@@ -198,7 +199,7 @@ $(function() { ...@@ -198,7 +199,7 @@ $(function() {
$("#vm-details-h1-name, .vm-details-rename-button").click(function() { $("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide(); $("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline'); $("#vm-details-rename").css('display', 'inline');
$("#vm-details-rename-name").focus(); $("#vm-details-rename-name").select();
return false; return false;
}); });
...@@ -206,7 +207,7 @@ $(function() { ...@@ -206,7 +207,7 @@ $(function() {
$(".vm-details-home-edit-name-click").click(function() { $(".vm-details-home-edit-name-click").click(function() {
$(".vm-details-home-edit-name-click").hide(); $(".vm-details-home-edit-name-click").hide();
$("#vm-details-home-rename").show(); $("#vm-details-home-rename").show();
$("input", $("#vm-details-home-rename")).focus(); $("input", $("#vm-details-home-rename")).select();
return false; return false;
}); });
...@@ -306,8 +307,8 @@ $(function() { ...@@ -306,8 +307,8 @@ $(function() {
}); });
// select connection string // select connection string
$("#vm-details-connection-string-copy").click(function() { $(".vm-details-connection-string-copy").click(function() {
$("#vm-details-connection-string").focus(); $(this).parent("div").find("input").select();
}); });
$("a.operation-password_reset").click(function() { $("a.operation-password_reset").click(function() {
......
...@@ -25,6 +25,7 @@ from django_tables2.columns import (TemplateColumn, Column, BooleanColumn, ...@@ -25,6 +25,7 @@ from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
from vm.models import Node, InstanceTemplate, Lease from vm.models import Node, InstanceTemplate, Lease
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
from dashboard.models import ConnectCommand
class NodeListTable(Table): class NodeListTable(Table):
...@@ -249,5 +250,41 @@ class UserKeyListTable(Table): ...@@ -249,5 +250,41 @@ class UserKeyListTable(Table):
class Meta: class Meta:
model = UserKey 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') 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 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" %}"> <form method="POST" action="{% url "dashboard.views.group-create" %}">
{% csrf_token %} {% csrf_token %}
......
...@@ -66,4 +66,20 @@ ...@@ -66,4 +66,20 @@
</div> </div>
</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 %} {% endblock %}
...@@ -98,8 +98,9 @@ ...@@ -98,8 +98,9 @@
<div class="input-group"> <div class="input-group">
<input type="text" id="vm-details-pw-input" class="form-control input-sm input-tags" <input type="text" id="vm-details-pw-input" class="form-control input-sm input-tags"
value="{{ instance.pw }}" spellcheck="false"/> value="{{ instance.pw }}" spellcheck="false"/>
<span class="input-group-addon input-tags" id="vm-details-pw-show"> <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> title="{% trans "Show password" %}" data-container="body">
<i class="fa fa-eye" id="vm-details-pw-eye"></i>
</span> </span>
</div> </div>
</dd> </dd>
...@@ -111,16 +112,29 @@ ...@@ -111,16 +112,29 @@
</div> </div>
</dd> </dd>
</dl> </dl>
{% for c in connect_commands %}
<div class="input-group" id="dashboard-vm-details-connect-command"> <div class="input-group dashboard-vm-details-connect-command">
<span class="input-group-addon input-tags">{% trans "Command" %}</span> <span class="input-group-addon input-tags">{% trans "Command" %}</span>
<input type="text" spellcheck="false" <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" /> id="vm-details-connection-string" class="form-control input-tags" />
<span class="input-group-addon input-tags" id="vm-details-connection-string-copy"> <span class="input-group-addon input-tags" id="vm-details-connection-string-copy">
<i class="fa fa-copy" title="{% trans "Select all" %}"></i> <i class="fa fa-copy" title="{% trans "Select all" %}"></i>
</span> </span>
</div> </div>
{% endfor %}
</div> </div>
<div class="col-md-8" id="vm-detail-pane"> <div class="col-md-8" id="vm-detail-pane">
<div class="panel panel-default" id="vm-detail-panel"> <div class="panel panel-default" id="vm-detail-panel">
......
...@@ -94,7 +94,13 @@ ...@@ -94,7 +94,13 @@
<dt>{% trans "Template" %}:</dt> <dt>{% trans "Template" %}:</dt>
<dd> <dd>
{% if instance.template %} {% if instance.template %}
{% if can_link_template %}
<a href="{{ instance.template.get_absolute_url }}">
{{ instance.template.name }} {{ instance.template.name }}
</a>
{% else %}
{{ instance.template.name }}
{% endif %}
{% else %} {% else %}
- -
{% endif %} {% endif %}
......
...@@ -39,6 +39,7 @@ from .views import ( ...@@ -39,6 +39,7 @@ from .views import (
get_vm_screenshot, get_vm_screenshot,
ProfileView, toggle_use_gravatar, UnsubscribeFormView, ProfileView, toggle_use_gravatar, UnsubscribeFormView,
UserKeyDelete, UserKeyDetail, UserKeyCreate, UserKeyDelete, UserKeyDetail, UserKeyCreate,
ConnectCommandDelete, ConnectCommandDetail, ConnectCommandCreate,
StoreList, store_download, store_upload, store_get_upload_url, StoreRemove, StoreList, store_download, store_upload, store_get_upload_url, StoreRemove,
store_new_directory, store_refresh_toplist, store_new_directory, store_refresh_toplist,
VmTraitsUpdate, VmRawDataUpdate, VmTraitsUpdate, VmRawDataUpdate,
...@@ -177,6 +178,16 @@ urlpatterns = patterns( ...@@ -177,6 +178,16 @@ urlpatterns = patterns(
UserKeyCreate.as_view(), UserKeyCreate.as_view(),
name="dashboard.views.userkey-create"), 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'^autocomplete/', include('autocomplete_light.urls')),
url(r"^store/list/$", StoreList.as_view(), url(r"^store/list/$", StoreList.as_view(),
......
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from lxml import etree as ET from lxml import etree as ET
import logging import logging
...@@ -29,3 +31,27 @@ def domain_validator(value): ...@@ -29,3 +31,27 @@ def domain_validator(value):
relaxng.assertValid(parsed_xml) relaxng.assertValid(parsed_xml)
except Exception as e: except Exception as e:
raise ValidationError(e.message) 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 ( ...@@ -71,12 +71,12 @@ from .forms import (
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm, CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm, TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm, VmResourcesForm, VmAddInterfaceForm, VmListSearchForm,
TemplateListSearchForm, TemplateListSearchForm, ConnectCommandForm
) )
from .tables import ( from .tables import (
NodeListTable, TemplateListTable, LeaseListTable, NodeListTable, TemplateListTable, LeaseListTable,
GroupListTable, UserKeyListTable GroupListTable, UserKeyListTable, ConnectCommandListTable,
) )
from common.models import ( from common.models import (
HumanReadableObject, HumanReadableException, fetch_human_exception, HumanReadableObject, HumanReadableException, fetch_human_exception,
...@@ -88,7 +88,8 @@ from vm.models import ( ...@@ -88,7 +88,8 @@ from vm.models import (
) )
from storage.models import Disk from storage.models import Disk
from firewall.models import Vlan, Host, Rule 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 from .store_api import Store, NoStoreException, NotOkException
...@@ -366,6 +367,7 @@ class VmDetailView(CheckedDetailView): ...@@ -366,6 +367,7 @@ class VmDetailView(CheckedDetailView):
kwargs={'pk': self.object.pk}), kwargs={'pk': self.object.pk}),
'ops': ops, 'ops': ops,
'op': {i.op: i for i in ops}, 'op': {i.op: i for i in ops},
'connect_commands': user.profile.get_connect_commands(instance)
}) })
# activity data # activity data
...@@ -396,7 +398,7 @@ class VmDetailView(CheckedDetailView): ...@@ -396,7 +398,7 @@ class VmDetailView(CheckedDetailView):
# resources forms # resources forms
can_edit = ( 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")) and self.request.user.has_perm("vm.change_resources"))
context['resources_form'] = VmResourcesForm( context['resources_form'] = VmResourcesForm(
can_edit=can_edit, instance=instance) can_edit=can_edit, instance=instance)
...@@ -409,6 +411,11 @@ class VmDetailView(CheckedDetailView): ...@@ -409,6 +411,11 @@ class VmDetailView(CheckedDetailView):
context['can_change_resources'] = self.request.user.has_perm( context['can_change_resources'] = self.request.user.has_perm(
"vm.change_resources") "vm.change_resources")
# can link template
context['can_link_template'] = (
instance.template and instance.template.has_level(user, "operator")
)
return context return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
...@@ -1538,7 +1545,7 @@ class TemplateChoose(LoginRequiredMixin, TemplateView): ...@@ -1538,7 +1545,7 @@ class TemplateChoose(LoginRequiredMixin, TemplateView):
self.request.user) self.request.user)
context.update({ context.update({
'box_title': _('Choose template'), 'box_title': _('Choose template'),
'ajax_title': False, 'ajax_title': True,
'template': "dashboard/_template-choose.html", 'template': "dashboard/_template-choose.html",
'templates': templates.all(), 'templates': templates.all(),
}) })
...@@ -2093,7 +2100,7 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -2093,7 +2100,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
context.update({ context.update({
'template': 'dashboard/_vm-create-2.html', 'template': 'dashboard/_vm-create-2.html',
'box_title': _('Customize VM'), 'box_title': _('Customize VM'),
'ajax_title': False, 'ajax_title': True,
'vm_create_form': form, 'vm_create_form': form,
'template_o': templates.get(pk=template), 'template_o': templates.get(pk=template),
}) })
...@@ -2101,7 +2108,7 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -2101,7 +2108,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
context.update({ context.update({
'template': 'dashboard/_vm-create-1.html', 'template': 'dashboard/_vm-create-1.html',
'box_title': _('Create a VM'), 'box_title': _('Create a VM'),
'ajax_title': False, 'ajax_title': True,
'templates': templates.all(), 'templates': templates.all(),
}) })
return self.render_to_response(context) return self.render_to_response(context)
...@@ -2291,9 +2298,9 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView): ...@@ -2291,9 +2298,9 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
context.update({ context.update({
'template': 'dashboard/group-create.html', 'template': 'dashboard/group-create.html',
'box_title': 'Create a Group', 'box_title': _('Create a Group'),
'form': form, 'form': form,
'ajax_title': True,
}) })
return self.render_to_response(context) return self.render_to_response(context)
...@@ -2961,11 +2968,16 @@ class MyPreferencesView(UpdateView): ...@@ -2961,11 +2968,16 @@ class MyPreferencesView(UpdateView):
user=self.request.user), user=self.request.user),
'change_language': MyProfileForm(instance=self.get_object()), 'change_language': MyProfileForm(instance=self.get_object()),
} }
table = UserKeyListTable( key_table = UserKeyListTable(
UserKey.objects.filter(user=self.request.user), UserKey.objects.filter(user=self.request.user),
request=self.request) request=self.request)
table.page = None key_table.page = None
context['userkey_table'] = table 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 return context
def get_object(self, queryset=None): def get_object(self, queryset=None):
...@@ -3370,6 +3382,82 @@ class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView): ...@@ -3370,6 +3382,82 @@ class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
return kwargs 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): class HelpView(TemplateView):
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
......
...@@ -101,7 +101,7 @@ def pull(dir="~/circle/circle"): ...@@ -101,7 +101,7 @@ def pull(dir="~/circle/circle"):
@roles('portal') @roles('portal')
def update_portal(test=False): def update_portal(test=False):
"Update and restart portal+manager" "Update and restart portal+manager"
with _stopped("portal", "mancelery"): with _stopped("portal", "manager"):
pull() pull()
pip("circle", "~/circle/requirements.txt") pip("circle", "~/circle/requirements.txt")
migrate() migrate()
...@@ -113,7 +113,7 @@ def update_portal(test=False): ...@@ -113,7 +113,7 @@ def update_portal(test=False):
@roles('portal') @roles('portal')
def stop_portal(test=False): def stop_portal(test=False):
"Stop portal and manager" "Stop portal and manager"
_stop_services("portal", "mancelery") _stop_services("portal", "manager")
@roles('node') @roles('node')
......
...@@ -215,7 +215,7 @@ class Rule(models.Model): ...@@ -215,7 +215,7 @@ class Rule(models.Model):
dst = None dst = None
if host: if host:
ip = (host.ipv4, host.ipv6_with_prefixlen) ip = (host.ipv4, host.ipv6_with_host_prefixlen)
if self.direction == 'in': if self.direction == 'in':
dst = ip dst = ip
else: else:
...@@ -530,14 +530,30 @@ class Host(models.Model): ...@@ -530,14 +530,30 @@ class Host(models.Model):
def incoming_rules(self): def incoming_rules(self):
return self.rules.filter(direction='in') return self.rules.filter(direction='in')
@property @staticmethod
def ipv6_with_prefixlen(self): def create_ipnetwork(ip, prefixlen):
try: try:
net = IPNetwork(self.ipv6) net = IPNetwork(ip)
net.prefixlen = self.vlan.host_ipv6_prefixlen net.prefixlen = prefixlen
return net
except TypeError: except TypeError:
return None 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): def get_external_ipv4(self):
return self.external_ipv4 if self.external_ipv4 else self.ipv4 return self.external_ipv4 if self.external_ipv4 else self.ipv4
...@@ -600,6 +616,19 @@ class Host(models.Model): ...@@ -600,6 +616,19 @@ class Host(models.Model):
description='created by host.save()', description='created by host.save()',
type='AAAA').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): def enable_net(self):
for i in settings.get('default_host_groups', []): for i in settings.get('default_host_groups', []):
self.groups.add(Group.objects.get(name=i)) self.groups.add(Group.objects.get(name=i))
......
...@@ -31,7 +31,6 @@ celery = Celery('manager', ...@@ -31,7 +31,6 @@ celery = Celery('manager',
'storage.tasks.local_tasks', 'storage.tasks.local_tasks',
'storage.tasks.periodic_tasks', 'storage.tasks.periodic_tasks',
'firewall.tasks.local_tasks', 'firewall.tasks.local_tasks',
'monitor.tasks.local_periodic_tasks',
'dashboard.tasks.local_periodic_tasks', 'dashboard.tasks.local_periodic_tasks',
]) ])
...@@ -42,20 +41,8 @@ celery.conf.update( ...@@ -42,20 +41,8 @@ celery.conf.update(
CELERY_QUEUES=( CELERY_QUEUES=(
Queue(HOSTNAME + '.man', Exchange('manager', type='direct'), Queue(HOSTNAME + '.man', Exchange('manager', type='direct'),
routing_key="manager"), routing_key="manager"),
Queue(HOSTNAME + '.monitor', Exchange('monitor', type='direct'),
routing_key="monitor"),
), ),
CELERYBEAT_SCHEDULE={ 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': { 'storage.periodic_tasks': {
'task': 'storage.tasks.periodic_tasks.garbage_collector', 'task': 'storage.tasks.periodic_tasks.garbage_collector',
'schedule': timedelta(hours=1), 'schedule': timedelta(hours=1),
...@@ -67,24 +54,6 @@ celery.conf.update( ...@@ -67,24 +54,6 @@ celery.conf.update(
'schedule': timedelta(hours=24), 'schedule': timedelta(hours=24),
'options': {'queue': 'localhost.man'} '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): ...@@ -278,7 +278,7 @@ class Disk(TimeStampedModel):
return Disk.create(base=self, datastore=self.datastore, return Disk.create(base=self, datastore=self.datastore,
name=self.name, size=self.size, name=self.name, size=self.size,
type=new_type) type=new_type, dev_num=self.dev_num)
def get_vmdisk_desc(self): def get_vmdisk_desc(self):
"""Serialize disk object to the vmdriver. """Serialize disk object to the vmdriver.
......
...@@ -41,7 +41,7 @@ from .models import ( ...@@ -41,7 +41,7 @@ from .models import (
Instance, InstanceActivity, InstanceTemplate, Interface, Node, Instance, InstanceActivity, InstanceTemplate, Interface, Node,
NodeActivity, pwgen NodeActivity, pwgen
) )
from .tasks import agent_tasks from .tasks import agent_tasks, local_agent_tasks
from dashboard.store_api import Store, NoStoreException from dashboard.store_api import Store, NoStoreException
...@@ -153,6 +153,7 @@ class AddInterfaceOperation(InstanceOperation): ...@@ -153,6 +153,7 @@ class AddInterfaceOperation(InstanceOperation):
self.rollback(net, activity) self.rollback(net, activity)
raise raise
net.deploy() net.deploy()
local_agent_tasks.send_networking_commands(self.instance, activity)
def get_activity_name(self, kwargs): def get_activity_name(self, kwargs):
return create_readable(ugettext_noop("add %(vlan)s interface"), return create_readable(ugettext_noop("add %(vlan)s interface"),
...@@ -218,6 +219,7 @@ class DownloadDiskOperation(InstanceOperation): ...@@ -218,6 +219,7 @@ class DownloadDiskOperation(InstanceOperation):
has_percentage = True has_percentage = True
required_perms = ('storage.download_disk', ) required_perms = ('storage.download_disk', )
accept_states = ('STOPPED', 'PENDING', 'RUNNING') accept_states = ('STOPPED', 'PENDING', 'RUNNING')
async_queue = "localhost.man.slow"
def _operation(self, user, url, task, activity, name=None): def _operation(self, user, url, task, activity, name=None):
activity.result = url activity.result = url
...@@ -368,6 +370,7 @@ class MigrateOperation(InstanceOperation): ...@@ -368,6 +370,7 @@ class MigrateOperation(InstanceOperation):
required_perms = () required_perms = ()
superuser_required = True superuser_required = True
accept_states = ('RUNNING', ) accept_states = ('RUNNING', )
async_queue = "localhost.man.slow"
def rollback(self, activity): def rollback(self, activity):
with activity.sub_activity( with activity.sub_activity(
...@@ -512,6 +515,7 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -512,6 +515,7 @@ class SaveAsTemplateOperation(InstanceOperation):
abortable = True abortable = True
required_perms = ('vm.create_template', ) required_perms = ('vm.create_template', )
accept_states = ('RUNNING', 'PENDING', 'STOPPED') accept_states = ('RUNNING', 'PENDING', 'STOPPED')
async_queue = "localhost.man.slow"
def is_preferred(self): def is_preferred(self):
return (self.instance.is_base and return (self.instance.is_base and
...@@ -667,6 +671,7 @@ class SleepOperation(InstanceOperation): ...@@ -667,6 +671,7 @@ class SleepOperation(InstanceOperation):
required_perms = () required_perms = ()
accept_states = ('RUNNING', ) accept_states = ('RUNNING', )
resultant_state = 'SUSPENDED' resultant_state = 'SUSPENDED'
async_queue = "localhost.man.slow"
def is_preferred(self): def is_preferred(self):
return (not self.instance.is_base and return (not self.instance.is_base and
...@@ -839,6 +844,7 @@ class FlushOperation(NodeOperation): ...@@ -839,6 +844,7 @@ class FlushOperation(NodeOperation):
description = _("Disable node and move all instances to other ones.") description = _("Disable node and move all instances to other ones.")
required_perms = () required_perms = ()
superuser_required = True superuser_required = True
async_queue = "localhost.man.slow"
def on_abort(self, activity, error): def on_abort(self, activity, error):
from manager.scheduler import TraitsUnsatisfiableException from manager.scheduler import TraitsUnsatisfiableException
...@@ -998,12 +1004,12 @@ class MountStoreOperation(EnsureAgentMixin, InstanceOperation): ...@@ -998,12 +1004,12 @@ class MountStoreOperation(EnsureAgentMixin, InstanceOperation):
except NoStoreException: except NoStoreException:
raise PermissionDenied # not show the button at all raise PermissionDenied # not show the button at all
def _operation(self): def _operation(self, user):
inst = self.instance inst = self.instance
queue = self.instance.get_remote_queue_name("agent") queue = self.instance.get_remote_queue_name("agent")
host = urlsplit(settings.STORE_URL).hostname host = urlsplit(settings.STORE_URL).hostname
username = Store(inst.owner).username username = Store(user).username
password = inst.owner.profile.smb_password password = user.profile.smb_password
agent_tasks.mount_store.apply_async( agent_tasks.mount_store.apply_async(
queue=queue, args=(inst.vm_name, host, username, password)) queue=queue, args=(inst.vm_name, host, username, password))
......
...@@ -76,3 +76,8 @@ def get_keys(vm): ...@@ -76,3 +76,8 @@ def get_keys(vm):
@celery.task(name='agent.send_expiration') @celery.task(name='agent.send_expiration')
def send_expiration(vm, url): def send_expiration(vm, url):
pass pass
@celery.task(name='agent.change_ip')
def change_ip(vm, interfaces, dns):
pass
...@@ -19,7 +19,9 @@ from common.models import create_readable ...@@ -19,7 +19,9 @@ from common.models import create_readable
from manager.mancelery import celery from manager.mancelery import celery
from vm.tasks.agent_tasks import (restart_networking, change_password, from vm.tasks.agent_tasks import (restart_networking, change_password,
set_time, set_hostname, start_access_server, set_time, set_hostname, start_access_server,
cleanup, update) cleanup, update, change_ip)
from firewall.models import Host
import time import time
from base64 import encodestring from base64 import encodestring
from StringIO import StringIO from StringIO import StringIO
...@@ -31,13 +33,11 @@ from celery.result import TimeoutError ...@@ -31,13 +33,11 @@ from celery.result import TimeoutError
from monitor.client import Client 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") queue = instance.get_remote_queue_name("agent")
with act.sub_activity('cleanup', readable_name=ugettext_noop('cleanup')): with act.sub_activity('cleanup', readable_name=ugettext_noop('cleanup')):
cleanup.apply_async(queue=queue, args=(vm, )) 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', with act.sub_activity('change_password',
readable_name=ugettext_noop('change password')): readable_name=ugettext_noop('change password')):
change_password.apply_async(queue=queue, args=(vm, instance.pw)) change_password.apply_async(queue=queue, args=(vm, instance.pw))
...@@ -49,6 +49,17 @@ def send_init_commands(instance, act, vm): ...@@ -49,6 +49,17 @@ def send_init_commands(instance, act, vm):
queue=queue, args=(vm, instance.primary_host.hostname)) 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 create_agent_tar():
def exclude(tarinfo): def exclude(tarinfo):
if tarinfo.name.startswith('./.git'): if tarinfo.name.startswith('./.git'):
...@@ -94,8 +105,9 @@ def agent_started(vm, version=None): ...@@ -94,8 +105,9 @@ def agent_started(vm, version=None):
if not initialized: if not initialized:
measure_boot_time(instance) measure_boot_time(instance)
send_init_commands(instance, act, vm) send_init_commands(instance, act)
send_networking_commands(instance, act)
with act.sub_activity( with act.sub_activity(
'start_access_server', 'start_access_server',
readable_name=ugettext_noop('start access server') readable_name=ugettext_noop('start access server')
...@@ -134,6 +146,13 @@ def agent_stopped(vm): ...@@ -134,6 +146,13 @@ def agent_stopped(vm):
pass 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): def update_agent(instance, act=None):
if act: if act:
act = act.sub_activity( 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" description "CIRCLE mancelery for common jobs"
start on runlevel [2345]
stop on runlevel [!2345]
respawn respawn
respawn limit 30 30 respawn limit 30 30
setgid cloud setgid cloud
setuid cloud setuid cloud
...@@ -12,6 +10,5 @@ script ...@@ -12,6 +10,5 @@ script
cd /home/cloud/circle/circle cd /home/cloud/circle/circle
. /home/cloud/.virtualenvs/circle/bin/activate . /home/cloud/.virtualenvs/circle/bin/activate
. /home/cloud/.virtualenvs/circle/bin/postactivate . /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 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 ...@@ -12,4 +12,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate . /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 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 end script
...@@ -14,4 +14,3 @@ script ...@@ -14,4 +14,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate . /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py runserver '[::]:8080' exec ./manage.py runserver '[::]:8080'
end script 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