Commit 207f7413 by Kálmán Viktor

Merge branch 'master' into feature-mass-ops

Conflicts:
	circle/dashboard/templates/dashboard/vm-list.html
	circle/dashboard/views.py
parents 3d55c446 da2b020d
......@@ -161,7 +161,7 @@ STATICFILES_FINDERS = (
)
########## END STATIC FILE CONFIGURATION
p = join(dirname(SITE_ROOT), 'site-circle/static')
p = normpath(join(SITE_ROOT, '../../site-circle/static'))
if exists(p):
STATICFILES_DIRS = (p, )
......@@ -211,8 +211,8 @@ TEMPLATE_LOADERS = (
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
TEMPLATE_DIRS = (
normpath(join(SITE_ROOT, '../../site-circle/templates')),
normpath(join(SITE_ROOT, 'templates')),
join(dirname(SITE_ROOT), 'site-circle/templates'),
)
########## END TEMPLATE CONFIGURATION
......
......@@ -24,18 +24,10 @@ from os import environ
from base import * # noqa
def get_env_setting(setting):
""" Get the environment setting or return exception """
try:
return environ[setting]
except KeyError:
error_msg = "Set the %s env variable" % setting
raise ImproperlyConfigured(error_msg)
########## HOST CONFIGURATION
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/
# #allowed-hosts-required-in-production
ALLOWED_HOSTS = get_env_setting('DJANGO_ALLOWED_HOSTS').split(',')
ALLOWED_HOSTS = get_env_variable('DJANGO_ALLOWED_HOSTS').split(',')
########## END HOST CONFIGURATION
########## EMAIL CONFIGURATION
......@@ -44,18 +36,18 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
try:
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = environ.get('EMAIL_HOST')
EMAIL_HOST = get_env_variable('EMAIL_HOST')
except ImproperlyConfigured:
pass
EMAIL_HOST = 'localhost'
else:
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host-password
EMAIL_HOST_PASSWORD = environ.get('EMAIL_HOST_PASSWORD', '')
EMAIL_HOST_PASSWORD = get_env_variable('EMAIL_HOST_PASSWORD', '')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host-user
EMAIL_HOST_USER = environ.get('EMAIL_HOST_USER', 'your_email@example.com')
EMAIL_HOST_USER = get_env_variable('EMAIL_HOST_USER', 'your_email@example.com')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = environ.get('EMAIL_PORT', 587)
EMAIL_PORT = get_env_variable('EMAIL_PORT', 587)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-use-tls
EMAIL_USE_TLS = True
......@@ -64,7 +56,8 @@ else:
EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME
# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email
SERVER_EMAIL = EMAIL_HOST_USER
DEFAULT_FROM_EMAIL = get_env_variable('DEFAULT_FROM_EMAIL')
SERVER_EMAIL = get_env_variable('SERVER_EMAIL', DEFAULT_FROM_EMAIL)
########## END EMAIL CONFIGURATION
......@@ -83,5 +76,12 @@ CACHES = {
########## SECRET CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
SECRET_KEY = get_env_setting('SECRET_KEY')
SECRET_KEY = get_env_variable('SECRET_KEY')
########## END SECRET CONFIGURATION
level = environ.get('LOGLEVEL', 'INFO')
LOGGING['handlers']['syslog']['level'] = level
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['syslog'], 'level': level}
LOGGING['loggers']['djangosaml2'] = {'handlers': ['syslog'], 'level': level}
LOGGING['loggers']['django'] = {'handlers': ['syslog'], 'level': level}
......@@ -17,6 +17,7 @@
from collections import deque
from contextlib import contextmanager
from functools import update_wrapper
from hashlib import sha224
from itertools import chain, imap
from logging import getLogger
......@@ -36,6 +37,7 @@ from django.utils.functional import Promise
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from jsonfield import JSONField
from manager.mancelery import celery
from model_utils.models import TimeStampedModel
logger = getLogger(__name__)
......@@ -212,6 +214,38 @@ class ActivityModel(TimeStampedModel):
self.result_data = None if value is None else value.to_dict()
@celery.task()
def compute_cached(method, instance, memcached_seconds,
key, start, *args, **kwargs):
"""Compute and store actual value of cached method."""
if isinstance(method, basestring):
model, id = instance
instance = model.objects.get(id=id)
try:
method = getattr(model, method)
while hasattr(method, '_original') or hasattr(method, 'fget'):
try:
method = method._original
except AttributeError:
method = method.fget
except AttributeError:
logger.exception("Couldnt get original method of %s",
unicode(method))
raise
# call the actual method
result = method(instance, *args, **kwargs)
# save to memcache
cache.set(key, result, memcached_seconds)
elapsed = time() - start
cache.set("%s.cached" % key, 2, max(memcached_seconds * 0.5,
memcached_seconds * 0.75 - elapsed))
logger.debug('Value of <%s>.%s(%s)=<%s> saved to cache (%s elapsed).',
unicode(instance), method.__name__, unicode(args),
unicode(result), elapsed)
return result
def method_cache(memcached_seconds=60, instance_seconds=5): # noqa
"""Cache return value of decorated method to memcached and memory.
......@@ -233,9 +267,11 @@ def method_cache(memcached_seconds=60, instance_seconds=5): # noqa
def inner_cache(method):
method_name = method.__name__
def get_key(instance, *args, **kwargs):
return sha224(unicode(method.__module__) +
unicode(method.__name__) +
method_name +
unicode(instance.id) +
unicode(args) +
unicode(kwargs)).hexdigest()
......@@ -254,21 +290,31 @@ def method_cache(memcached_seconds=60, instance_seconds=5): # noqa
if vals['time'] + instance_seconds > now:
# has valid on class cache, return that
result = vals['value']
setattr(instance, key, {'time': now, 'value': result})
if result is None:
result = cache.get(key)
if invalidate or (result is None):
# all caches failed, call the actual method
result = method(instance, *args, **kwargs)
# save to memcache and class attr
cache.set(key, result, memcached_seconds)
logger.debug("all caches failed, compute now")
result = compute_cached(method, instance, memcached_seconds,
key, time(), *args, **kwargs)
setattr(instance, key, {'time': now, 'value': result})
logger.debug('Value of <%s>.%s(%s)=<%s> saved to cache.',
unicode(instance), method.__name__,
unicode(args), unicode(result))
elif not cache.get("%s.cached" % key):
logger.debug("caches expiring, compute async")
cache.set("%s.cached" % key, 1, memcached_seconds * 0.5)
try:
compute_cached.apply_async(
queue='localhost.man', kwargs=kwargs, args=[
method_name, (instance.__class__, instance.id),
memcached_seconds, key, time()] + list(args))
except:
logger.exception("Couldnt compute async %s", method_name)
return result
update_wrapper(x, method)
x._original = method
return x
return inner_cache
......
......@@ -39,7 +39,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
from django.forms.widgets import TextInput, HiddenInput
from django.template import Context
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from sizefield.widgets import FileSizeWidget
from django.core.urlresolvers import reverse_lazy
......@@ -1138,3 +1138,29 @@ class VmResourcesForm(forms.ModelForm):
class Meta:
model = Instance
fields = ('num_cores', 'priority', 'ram_size', )
vm_search_choices = (
(0, _("owned")),
(1, _("shared")),
(2, _("all")),
)
class VmListSearchForm(forms.Form):
s = forms.CharField(widget=forms.TextInput(attrs={
'class': "form-control input-tags",
'placeholder': _("Search...")
}))
stype = forms.ChoiceField(vm_search_choices, widget=forms.Select(attrs={
'class': "btn btn-default input-tags",
}))
def __init__(self, *args, **kwargs):
super(VmListSearchForm, self).__init__(*args, **kwargs)
# set initial value, otherwise it would be overwritten by request.GET
if not self.data.get("stype"):
data = self.data.copy()
data['stype'] = 2
self.data = data
......@@ -258,14 +258,15 @@ $(function () {
html += '<div class="list-group-item list-group-item-last">' + gettext("No result") + '</div>';
$("#dashboard-vm-list").html(html);
$('.title-favourite').tooltip({'placement': 'right'});
});
// if there is only one result and ENTER is pressed redirect
if(e.keyCode == 13 && search_result.length == 1) {
window.location.href = "/dashboard/vm/" + search_result[0].pk + "/";
}
if(e.keyCode == 13 && search_result.length > 1 && input.length > 0) {
window.location.href = "/dashboard/vm/list/?s=" + input;
$("#dashboard-vm-search-form").submit(function() {
var vm_list_items = $("#dashboard-vm-list .list-group-item");
if(vm_list_items.length == 1) {
window.location.href = vm_list_items.first().prop("href");
return false;
}
return true;
});
/* search for nodes */
......
......@@ -3,7 +3,7 @@
$(function() {
/* vm operations */
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface').on('click', '.operation', function(e) {
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin');
$.ajax({
......
......@@ -186,18 +186,7 @@ $(function() {
success: function(re, textStatus, xhr) {
/* remove the html element */
$('a[data-interface-pk="' + data.pk + '"]').closest("div").fadeOut();
/* add the removed element to the list */
network_select = $('select[name="new_network_vlan"]');
name_html = (re.removed_network.managed ? "&#xf0ac;": "&#xf0c1;") + " " + re.removed_network.vlan;
option_html = '<option value="' + re.removed_network.vlan_pk + '">' + name_html + '</option>';
// if it's -1 then it's a dummy placeholder so we can use .html
if($("option", network_select)[0].value === "-1") {
network_select.html(option_html);
network_select.next("div").children("button").prop("disabled", false);
} else {
network_select.append(option_html);
}
location.reload();
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
......
......@@ -41,6 +41,7 @@ def send_email_notifications():
for i in Notification.objects.filter(q):
recipients.setdefault(i.to, [])
recipients[i.to].append(i)
logger.info("Delivering notifications to %d users", len(recipients))
for user, msgs in recipients.iteritems():
if (not user.profile or not user.email or not
......
......@@ -46,12 +46,15 @@
</style>
<div href="#" class="list-group-item list-group-footer">
<div class="row">
<div class="col-sm-6 col-xs-6 input-group input-group-sm">
<input id="dashboard-vm-search-input" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button>
<form action="{% url "dashboard.views.vm-list" %}" method="GET" id="dashboard-vm-search-form">
<div class="col-sm-6 col-xs-6 input-group input-group-sm">
<input id="dashboard-vm-search-input" type="text" class="form-control" name="s"
placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</form>
<div class="col-sm-6 text-right">
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.vm-list" %}">
<i class="fa fa-chevron-circle-right"></i>
......
......@@ -100,6 +100,20 @@
{% endif %}
</dd>
</dl>
{% if op.mount_store %}
<strong>{% trans "Store" %}</strong>
<p>
{{ op.mount_store.description }}
</p>
<div class="operation-wrapper">
<a href="{{ op.mount_store.get_url }}" class="btn btn-info btn-xs operation"
{% if op.mount_store.disabled %}disabled{% endif %}>
<i class="fa fa-{{op.mount_store.icon}}"></i>
{{ op.mount_store.name }}
</a>
</div>
{% endif %}
</div>
<div class="col-md-8">
{% if graphite_enabled %}
......
......@@ -15,27 +15,31 @@
</div>
<h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Virtual machines" %}</h3>
</div>
<div class="pull-right" style="max-width: 250px; margin-top: 15px; margin-right: 15px;">
<form action="" method="GET" class="input-group">
<input type="text" name="s"{% if request.GET.s %} value="{{ request.GET.s }}"{% endif %} class="form-control input-tags" placeholder="{% trans "Search..."%}" />
<div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary input-tags" title="search"><i class="fa fa-search"></i></button>
<div class="pull-right" style="max-width: 300px; margin-top: 15px; margin-right: 15px;">
<form action="" method="GET">
<div class="input-group">
{{ search_form.s }}
<div class="input-group-btn">
{{ search_form.stype }}
<button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</form>
</div>
<div class="panel-body vm-list-group-control">
<div id="vm-mass-ops">
<strong>{% trans "Group actions" %}</strong>
<button id="vm-list-group-select-all" class="btn btn-info btn-xs">{% trans "Select all" %}</button>
{% for o in ops %}
<a href="{{ o.get_url }}" class="btn btn-xs btn-{{ o.effect }} mass-operation"
title="{{ o.name|capfirst }}" disabled>
<i class="fa fa-{{ o.icon }}"></i>
</a>
{% endfor %}
</div>
</div>
<div id="vm-mass-ops">
<strong>{% trans "Group actions" %}</strong>
<button id="vm-list-group-select-all" class="btn btn-info btn-xs">{% trans "Select all" %}</button>
{% for o in ops %}
<a href="{{ o.get_url }}" class="btn btn-xs btn-{{ o.effect }} mass-operation"
title="{{ o.name|capfirst }}" disabled>
<i class="fa fa-{{ o.icon }}"></i>
</a>
{% endfor %}
</div><!-- #vm-mass-ops -->
</div><!-- .panel-body .vm-list-group-control -->
<div class="panel-body">
<table class="table table-bordered table-striped table-hover vm-list-table"
id="vm-list-table">
......@@ -100,6 +104,9 @@
</div>
</div>
<div class="alert alert-info">
You can filter the list by certain attributes (owner, name, status, tags) in the following way: "owner:John Doe name:my little server". If you don't specify any attribute names the filtering will be done by name.
</div>
<div class="alert alert-info">
{% trans "You can select multiple vm instances while holding down the <strong>CTRL</strong> key." %}
......
......@@ -70,7 +70,7 @@ from .forms import (
VmSaveForm, UserKeyForm, VmRenewForm,
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm,
VmResourcesForm, VmAddInterfaceForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm
)
from .tables import (
......@@ -225,7 +225,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
# template
if user.has_perm('vm.create_template'):
context['templates'] = InstanceTemplate.get_objects_with_level(
'operator', user).all()[:5]
'operator', user, disregard_superuser=True).all()[:5]
# toplist
if settings.STORE_URL:
......@@ -518,6 +518,7 @@ class OperationView(RedirectToLoginMixin, DetailView):
show_in_toolbar = True
effect = None
wait_for_result = None
with_reload = False
@property
def name(self):
......@@ -660,11 +661,14 @@ class AjaxOperationMixin(object):
resp = super(AjaxOperationMixin, self).post(
request, extra, *args, **kwargs)
if request.is_ajax():
store = messages.get_messages(request)
store.used = True
if not self.with_reload:
store = messages.get_messages(request)
store.used = True
else:
store = []
return HttpResponse(
json.dumps({'success': True,
'with_reload': getattr(self, 'with_reload', False),
'with_reload': self.with_reload,
'messages': [unicode(m) for m in store]}),
content_type="application=json"
)
......@@ -706,9 +710,8 @@ class FormOperationMixin(object):
return HttpResponse(
json.dumps({
'success': True,
'with_reload': getattr(self, 'with_reload', False)}),
content_type="application=json"
)
'with_reload': self.with_reload}),
content_type="application=json")
else:
return resp
else:
......@@ -718,7 +721,7 @@ class FormOperationMixin(object):
class RequestFormOperationMixin(FormOperationMixin):
def get_form_kwargs(self):
val = super(FormOperationMixin, self).get_form_kwargs()
val = super(RequestFormOperationMixin, self).get_form_kwargs()
val.update({'request': self.request})
return val
......@@ -966,6 +969,10 @@ vm_ops = OrderedDict([
('password_reset', VmOperationView.factory(
op='password_reset', icon='unlock', effect='warning',
show_in_toolbar=False, wait_for_result=0.5, with_reload=True)),
('mount_store', VmOperationView.factory(
op='mount_store', icon='briefcase', effect='info',
show_in_toolbar=False,
)),
])
......@@ -1681,12 +1688,15 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
def get_context_data(self, *args, **kwargs):
context = super(VmList, self).get_context_data(*args, **kwargs)
context['ops'] = [v for k, v in vm_mass_ops.iteritems()]
context['search_form'] = self.search_form
return context
def get(self, *args, **kwargs):
if self.request.is_ajax():
return self._create_ajax_request()
else:
self.search_form = VmListSearchForm(self.request.GET)
self.search_form.full_clean()
return super(VmList, self).get(*args, **kwargs)
def _create_ajax_request(self):
......@@ -1726,8 +1736,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
def get_queryset(self):
logger.debug('VmList.get_queryset() called. User: %s',
unicode(self.request.user))
queryset = Instance.get_objects_with_level(
'user', self.request.user).filter(destroyed_at=None)
queryset = self.create_default_queryset()
self.create_fake_get()
sort = self.request.GET.get("sort")
......@@ -1742,6 +1751,18 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
**self.get_queryset_filters()).select_related('owner', 'node'
).distinct()
def create_default_queryset(self):
cleaned_data = self.search_form.cleaned_data
stype = cleaned_data.get('stype', 2)
superuser = stype == 2
shared = stype == 1
level = "owner" if stype == 0 else "user"
queryset = Instance.get_objects_with_level(
level, self.request.user,
group_also=shared, disregard_superuser=not superuser,
).filter(destroyed_at=None)
return queryset
def create_fake_get(self):
"""
Updates the request's GET dict to filter the vm list
......@@ -1983,8 +2004,8 @@ class VmCreate(LoginRequiredMixin, TemplateView):
form_error = form is not None
template = (form.template.pk if form_error
else request.GET.get("template"))
templates = InstanceTemplate.get_objects_with_level('user',
request.user)
templates = InstanceTemplate.get_objects_with_level(
'user', request.user, disregard_superuser=True)
if form is None and template:
form = self.form_class(user=request.user,
template=templates.get(pk=template))
......
......@@ -62,6 +62,15 @@ class BuildFirewall:
extra='-j DNAT --to-destination %s:%s' % (rule.host.ipv4,
rule.dport)))
# SNAT rules for machines with public IPv4
for host in Host.objects.exclude(external_ipv4=None).select_related(
'vlan').prefetch_related('vlan__snat_to'):
for vl_out in host.vlan.snat_to.all():
self.add_rules(POSTROUTING=IptRule(
priority=1500, src=(host.ipv4, None),
extra='-o %s -j SNAT --to-source %s' % (
vl_out.name, host.external_ipv4)))
# default outbound NAT rules for VLANs
for vl_in in Vlan.objects.exclude(
snat_ip=None).prefetch_related('snat_to'):
......@@ -183,9 +192,12 @@ def generate_ptr_records():
for host in Host.objects.order_by('vlan').all():
template = host.vlan.reverse_domain
i = host.get_external_ipv4().words
reverse = (host.reverse if host.reverse not in [None, '']
else host.get_fqdn())
if not host.shared_ip and host.external_ipv4: # DMZ
i = host.external_ipv4.words
reverse = host.get_hostname('ipv4', public=True)
else:
i = host.ipv4.words
reverse = host.get_hostname('ipv4', public=False)
# ipv4
if host.ipv4:
......@@ -194,7 +206,7 @@ def generate_ptr_records():
# ipv6
if host.ipv6:
DNS.append("^%s:%s:%s" % (host.ipv6.reverse_dns,
DNS.append("^%s:%s:%s" % (host.ipv6.reverse_dns.rstrip('.'),
reverse, settings['dns_ttl']))
return DNS
......@@ -211,14 +223,14 @@ def generate_records():
'CNAME': 'C%(fqdn)s:%(address)s:%(ttl)s',
'MX': '@%(fqdn)s::%(address)s:%(dist)s:%(ttl)s',
'PTR': '^%(fqdn)s:%(address)s:%(ttl)s',
'TXT': '%(fqdn)s:%(octal)s:%(ttl)s'}
'TXT': "'%(fqdn)s:%(octal)s:%(ttl)s"}
retval = []
for r in Record.objects.all():
params = {'fqdn': r.fqdn, 'address': r.address, 'ttl': r.ttl}
if r.type == 'MX':
params['address'], params['dist'] = r.address.split(':', 2)
params['dist'], params['address'] = r.address.split(':', 2)
if r.type == 'AAAA':
try:
params['octal'] = ipv6_to_octal(r.address)
......
......@@ -22,7 +22,7 @@ from collections import OrderedDict
logger = logging.getLogger()
ipv4_re = re.compile(
r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}')
r'(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}')
class InvalidRuleExcepion(Exception):
......
......@@ -575,10 +575,14 @@ class Host(models.Model):
# IPv4
if self.ipv4 is not None:
if not self.shared_ip and self.external_ipv4: # DMZ
ipv4 = self.external_ipv4
else:
ipv4 = self.ipv4
# update existing records
affected_records = Record.objects.filter(
host=self, name=self.hostname,
type='A').update(address=self.ipv4)
type='A').update(address=ipv4)
# create new record
if affected_records == 0:
Record(host=self,
......@@ -714,6 +718,8 @@ class Host(models.Model):
:type proto: str.
"""
assert proto in ('ipv6', 'ipv4', )
if self.reverse:
return self.reverse
try:
if proto == 'ipv6':
res = self.record_set.filter(type='AAAA',
......@@ -736,7 +742,7 @@ class Host(models.Model):
Return a list of ports with forwarding rules set.
"""
retval = []
for rule in self.rules.all():
for rule in self.rules.filter(dport__isnull=False, direction='in'):
forward = {
'proto': rule.proto,
'private': rule.dport,
......
......@@ -35,7 +35,7 @@ COMMIT
{% if proto == "ipv4" %}
-A FORWARD -p icmp --icmp-type echo-request -g LOG_ACC
{% else %}
-A FORWARD -p icmpv6 --icmpv6-type echo-request -g LOG_ACC
-A FORWARD -p icmpv6 -g LOG_ACC
{% endif %}
# initialize INPUT chain
......@@ -45,6 +45,11 @@ COMMIT
-A INPUT -m state --state INVALID -g LOG_DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
{% if proto == "ipv4" %}
-A INPUT -p icmp --icmp-type echo-request -g LOG_ACC
{% else %}
-A INPUT -p icmpv6 -g LOG_ACC
{% endif %}
# initialize OUTPUT chain
-A OUTPUT -m state --state INVALID -g LOG_DROP
......
......@@ -32,6 +32,7 @@ celery = Celery('manager',
'storage.tasks.periodic_tasks',
'firewall.tasks.local_tasks',
'monitor.tasks.local_periodic_tasks',
'dashboard.tasks.local_periodic_tasks',
])
celery.conf.update(
......@@ -60,7 +61,7 @@ celery.conf.update(
'schedule': timedelta(hours=1),
'options': {'queue': 'localhost.man'}
},
'dashboard.local_periodic_tasks': {
'dashboard.send_email_notifications': {
'task': 'dashboard.tasks.local_periodic_tasks.'
'send_email_notifications',
'schedule': timedelta(hours=24),
......
......@@ -28,7 +28,7 @@ def check_queue(storage, queue_id, priority):
if priority is not None:
queue_name = queue_name + "." + priority
inspect = celery.control.inspect()
inspect.timeout = 0.1
inspect.timeout = 0.5
active_queues = inspect.active_queues()
if active_queues is None:
return False
......
......@@ -293,6 +293,10 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
message = ugettext_noop(
"Instance %(instance)s has already been destroyed.")
class NoAgentError(InstanceError):
message = ugettext_noop(
"No agent software is running on instance %(instance)s.")
class WrongStateError(InstanceError):
message = ugettext_noop(
"Current state (%(state)s) of instance %(instance)s is "
......@@ -483,11 +487,12 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
"""
try:
datastore = self.disks.all()[0].datastore
except:
return None
else:
path = datastore.path + '/' + self.vm_name + '.dump'
return {'datastore': datastore, 'path': path}
except IndexError:
from storage.models import DataStore
datastore = DataStore.objects.get()
path = datastore.path + '/' + self.vm_name + '.dump'
return {'datastore': datastore, 'path': path}
@property
def primary_host(self):
......
......@@ -16,6 +16,7 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, unicode_literals
from functools import update_wrapper
from logging import getLogger
from warnings import warn
import requests
......@@ -51,6 +52,8 @@ def node_available(function):
return function(self, *args, **kwargs)
else:
return None
update_wrapper(decorate, function)
decorate._original = function
return decorate
......
......@@ -19,10 +19,12 @@ from __future__ import absolute_import, unicode_literals
from logging import getLogger
from re import search
from string import ascii_lowercase
from urlparse import urlsplit
from django.core.exceptions import PermissionDenied
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django.conf import settings
from sizefield.utils import filesizeformat
......@@ -41,6 +43,8 @@ from .models import (
)
from .tasks import agent_tasks
from dashboard.store_api import Store, NoStoreException
logger = getLogger(__name__)
......@@ -923,8 +927,27 @@ class ResourcesOperation(InstanceOperation):
register_operation(ResourcesOperation)
class PasswordResetOperation(InstanceOperation):
activity_code_suffix = 'Password reset'
class EnsureAgentMixin(object):
accept_states = ('RUNNING', )
def check_precond(self):
super(EnsureAgentMixin, self).check_precond()
last_boot_time = self.instance.activity_log.filter(
succeeded=True, activity_code__in=(
"vm.Instance.deploy", "vm.Instance.reset",
"vm.Instance.reboot")).latest("finished").finished
try:
InstanceActivity.objects.filter(
activity_code="vm.Instance.agent.starting",
started__gt=last_boot_time).latest("started")
except InstanceActivity.DoesNotExist: # no agent since last boot
raise self.instance.NoAgentError(self.instance)
class PasswordResetOperation(EnsureAgentMixin, InstanceOperation):
activity_code_suffix = 'password_reset'
id = 'password_reset'
name = _("password reset")
description = _("Generate and set a new login password on the virtual "
......@@ -934,7 +957,6 @@ class PasswordResetOperation(InstanceOperation):
"it.")
acl_level = "owner"
required_perms = ()
accept_states = ('RUNNING', )
def _operation(self):
self.instance.pw = pwgen()
......@@ -945,3 +967,34 @@ class PasswordResetOperation(InstanceOperation):
register_operation(PasswordResetOperation)
class MountStoreOperation(EnsureAgentMixin, InstanceOperation):
activity_code_suffix = 'mount_store'
id = 'mount_store'
name = _("mount store")
description = _(
"This operation attaches your personal file store. Other users who "
"have access to this machine can see these files as well."
)
acl_level = "owner"
required_perms = ()
def check_auth(self, user):
super(MountStoreOperation, self).check_auth(user)
try:
Store(user)
except NoStoreException:
raise PermissionDenied # not show the button at all
def _operation(self):
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
agent_tasks.mount_store.apply_async(
queue=queue, args=(inst.vm_name, host, username, password))
register_operation(MountStoreOperation)
......@@ -86,18 +86,11 @@ def agent_started(vm, version=None):
if version and version != settings.AGENT_VERSION:
try:
with act.sub_activity(
'update',
readable_name=create_readable(
ugettext_noop('update to %(version)s'),
version=settings.AGENT_VERSION)
):
update.apply_async(
queue=queue,
args=(vm, create_agent_tar())).get(timeout=10)
return
update_agent(vm, instance, act)
except TimeoutError:
pass
else:
return # agent is going to restart
if not initialized:
measure_boot_time(instance)
......@@ -139,3 +132,23 @@ def agent_stopped(vm):
act = qs.latest('id')
with act.sub_activity('stopping', readable_name=ugettext_noop('stopping')):
pass
def update_agent(instance, vm, act=None):
if act:
act.sub_activity(
'update',
readable_name=create_readable(
ugettext_noop('update to %(version)s'),
version=settings.AGENT_VERSION))
else:
from vm.models import instance_activity
act = instance_activity(
code_suffix='agent.update', instance=instance,
readable_name=create_readable(
ugettext_noop('update agent to %(version)s'),
version=settings.AGENT_VERSION))
with act:
queue = instance.get_remote_queue_name("agent")
update.apply_async(queue=queue,
args=(vm, create_agent_tar())).get(timeout=10)
......@@ -55,7 +55,7 @@ def get_queues():
result = cache.get(key)
if result is None:
inspect = celery.control.inspect()
inspect.timeout = 0.1
inspect.timeout = 0.5
result = inspect.active_queues()
logger.debug('Queue list of length %d cached.', len(result))
cache.set(key, result, 10)
......
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