Commit 10261b9a by Czémán Arnold

Merge branch 'master' of https://git.ik.bme.hu/circle/cloud into add_rule

parents c7fd2925 dad93220
Pipeline #7 passed with stage
in 0 seconds
......@@ -246,8 +246,8 @@ class AclBase(Model):
def save(self, *args, **kwargs):
super(AclBase, self).save(*args, **kwargs)
if 'owner' in dict(self.ACL_LEVELS) and (hasattr(self, 'owner')
and self.owner):
if 'owner' in dict(self.ACL_LEVELS) and (hasattr(self, 'owner') and
self.owner):
self.set_user_level(self.owner, 'owner')
class Meta:
......
......@@ -505,6 +505,8 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
if get_env_variable('DJANGO_SAML_ORG_ID_ATTRIBUTE', False) is not False:
SAML_ORG_ID_ATTRIBUTE = get_env_variable(
'DJANGO_SAML_ORG_ID_ATTRIBUTE')
SAML_MAIN_ATTRIBUTE_MAX_LENGTH = int(get_env_variable(
"DJANGO_SAML_MAIN_ATTRIBUTE_MAX_LENGTH", 0))
LOGIN_REDIRECT_URL = "/"
......
......@@ -71,3 +71,5 @@ STORE_URL = ""
# buildbot doesn't love pipeline
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
SAML_MAIN_ATTRIBUTE_MAX_LENGTH=0 # doctest on SAML2 backend runs either way
......@@ -25,7 +25,7 @@ from django.shortcuts import redirect
from circle.settings.base import get_env_variable
from dashboard.views import circle_login, HelpView
from dashboard.views import circle_login, HelpView, ResizeHelpView
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item
......@@ -65,6 +65,8 @@ urlpatterns = patterns(
url(r'^info/support/$',
TemplateView.as_view(template_name="info/support.html"),
name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
name="info.resize"),
)
......
......@@ -46,7 +46,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "circle.settings.production")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
from django.core.wsgi import get_wsgi_application # noqa
_application = get_wsgi_application()
......
......@@ -17,9 +17,14 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import re
import logging
import sha
from django.conf import settings
from djangosaml2.backends import Saml2Backend as Saml2BackendBase
logger = logging.getLogger(__name__)
class Saml2Backend(Saml2BackendBase):
u"""
......@@ -41,7 +46,14 @@ class Saml2Backend(Saml2BackendBase):
if isinstance(main_attribute, str):
main_attribute = main_attribute.decode('UTF-8')
assert isinstance(main_attribute, unicode)
return re.sub(r'[^\w.@-]', replace, main_attribute)
attr = re.sub(r'[^\w.@-]', replace, main_attribute)
max_length = settings.SAML_MAIN_ATTRIBUTE_MAX_LENGTH
if max_length > 0 and len(attr) > max_length:
logger.info("Main attribute '%s' is too long." % attr)
hashed = sha.new(attr).hexdigest()
attr = hashed[:max_length]
logger.info("New main attribute: %s" % attr)
return attr
def _set_attribute(self, obj, attr, value):
if attr == 'username':
......
......@@ -97,7 +97,7 @@ def has_prefix(activity_code, *prefixes):
>>> assert has_prefix('foo.bar.buz', 'foo', 'bar', 'buz')
>>> assert not has_prefix('foo.bar.buz', 'foo', 'buz')
"""
equal = lambda a, b: a == b
def equal(a, b): return a == b
act_code_parts = split_activity_code(activity_code)
prefixes = chain(*imap(split_activity_code, prefixes))
return all(imap(equal, act_code_parts, prefixes))
......@@ -112,7 +112,7 @@ def has_suffix(activity_code, *suffixes):
>>> assert has_suffix('foo.bar.buz', 'foo', 'bar', 'buz')
>>> assert not has_suffix('foo.bar.buz', 'foo', 'buz')
"""
equal = lambda a, b: a == b
def equal(a, b): return a == b
act_code_parts = split_activity_code(activity_code)
suffixes = list(chain(*imap(split_activity_code, suffixes)))
return all(imap(equal, reversed(act_code_parts), reversed(suffixes)))
......@@ -441,8 +441,8 @@ class HumanReadableObject(object):
@classmethod
def create(cls, user_text_template, admin_text_template=None, **params):
return cls(user_text_template=user_text_template,
admin_text_template=(admin_text_template
or user_text_template), params=params)
admin_text_template=(admin_text_template or
user_text_template), params=params)
def set(self, user_text_template, admin_text_template=None, **params):
self._set_values(user_text_template,
......
# -*- coding: utf-8 -*-
# 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 arrow import locales
class HungarianLocale(locales.Locale):
names = ['hu', 'HU']
past = '{0} ezelőtt'
future = '{0} múlva'
timeframes = {
'now': 'éppen most',
'seconds': {
'past': 'másodpercekkel',
'future': 'pár másodperc'},
'minute': {'past': 'egy perccel', 'future': 'egy perc'},
'minutes': {'past': '{0} perccel', 'future': '{0} perc'},
'hour': {'past': 'egy órával', 'future': 'egy óra'},
'hours': {'past': '{0} órával', 'future': '{0} óra'},
'day': {
'past': 'egy nappal',
'future': 'egy nap'
},
'days': {
'past': '{0} nappal',
'future': '{0} nap'
},
'month': {'past': 'egy hónappal', 'future': 'egy hónap'},
'months': {'past': '{0} hónappal', 'future': '{0} hónap'},
'year': {'past': 'egy évvel', 'future': 'egy év'},
'years': {'past': '{0} évvel', 'future': '{0} év'},
}
month_names = ['', 'Január', 'Február', 'Március', 'Április', 'Május',
'Június', 'Július', 'Augusztus', 'Szeptember',
'Október', 'November', 'December']
month_abbreviations = ['', 'Jan', 'Febr', 'Márc', 'Ápr', 'Máj', 'Jún',
'Júl', 'Aug', 'Szept', 'Okt', 'Nov', 'Dec']
day_names = ['', 'Hétfő', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek',
'Szombat', 'Vasárnap']
day_abbreviations = ['', 'Hét', 'Kedd', 'Szer', 'Csüt', 'Pént',
'Szom', 'Vas']
meridians = {
'am': 'de',
'pm': 'du',
'AM': 'DE',
'PM': 'DU',
}
def _format_timeframe(self, timeframe, delta):
form = self.timeframes[timeframe]
if isinstance(form, dict):
if delta > 0:
form = form['future']
else:
form = form['past']
delta = abs(delta)
return form.format(delta)
......@@ -38,10 +38,10 @@ def highlight(field, q, none_wo_match=True):
match = None
if q and match is not None:
match_end = match + len(q)
return (escape(field[:match])
+ '<span class="autocomplete-hl">'
+ escape(field[match:match_end])
+ '</span>' + escape(field[match_end:]))
return (escape(field[:match]) +
'<span class="autocomplete-hl">' +
escape(field[match:match_end]) +
'</span>' + escape(field[match_end:]))
elif none_wo_match:
return None
else:
......
......@@ -506,8 +506,8 @@ class TemplateForm(forms.ModelForm):
self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags',
'arch', 'lease', 'has_agent')
if (self.user.has_perm('vm.change_template_resources')
or not self.instance.pk):
if (self.user.has_perm('vm.change_template_resources') or
not self.instance.pk):
self.allowed_fields += tuple(set(self.fields.keys()) -
set(['raw_data']))
if self.user.is_superuser:
......@@ -523,8 +523,8 @@ class TemplateForm(forms.ModelForm):
self.initial['max_ram_size'] = 512
lease_queryset = (
Lease.get_objects_with_level("operator", self.user).distinct()
| Lease.objects.filter(pk=self.instance.lease_id).distinct())
Lease.get_objects_with_level("operator", self.user).distinct() |
Lease.objects.filter(pk=self.instance.lease_id).distinct())
self.fields["lease"].queryset = lease_queryset
......
......@@ -64,8 +64,8 @@ class Command(BaseCommand):
def handle(self, *args, **options):
self.changed = False
if (DataStore.objects.exists() and Vlan.objects.exists()
and not options['force']):
if (DataStore.objects.exists() and Vlan.objects.exists() and
not options['force']):
return self.print_state()
admin = self.create(User, 'username', username=options['admin_user'],
......
......@@ -1488,3 +1488,38 @@ textarea[name="new_members"] {
.acl-table td:first-child {
text-align: center;
}
#resize-help {
table {
background-color: #f5f5f5;
}
.panel {
padding: 2px 20px;
background-color: #f5f5f5;
margin: 20px 0px;
}
ol li {
margin-top: 15px;
}
img {
display: block;
margin: 15px 0 5px 0;
}
pre {
margin-top: 5px;
}
hr {
margin: 50px 0;
}
}
#vm-details-resize-how-to {
font-size: 1.5em;
text-align: center;
width: 100%;
}
......@@ -4,24 +4,33 @@
<i class="fa fa-file"></i>
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if op.remove_disk %}
<span class="operation-wrapper">
<span class="operation-wrapper pull-right">
{% if d.is_resizable %}
{% if op.resize_disk %}
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.resize_disk.effect }} operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %}
</a>
{% else %}
<a href="{% url "request.views.request-resize" vm_pk=instance.pk disk_pk=d.pk %}" class="btn btn-xs btn-primary operation">
<i class="fa fa-arrows-alt fa-fw-12"></i> {% trans "Request resize" %}
</a>
{% endif %}
{% else %}
<small class="btn-xs">
{% trans "Not resizable" %}
</small>
{% endif %}
{% if op.remove_disk %}
<a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn
class="btn btn-xs btn-{{ op.remove_disk.effect}} operation disk-remove-btn
{% if op.remove_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %}
</a>
</span>
{% endif %}
{% if op.resize_disk %}
<span class="operation-wrapper">
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %}
</a>
</span>
{% endif %}
{% endif %}
</span>
<div style="clear: both;"></div>
{% if request.user.is_superuser %}
......
......@@ -166,6 +166,28 @@
</ul>
</div>
</div>
{% if show_graph %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="fa fa-area-chart"></i> {% trans "Graphs" %}</h3>
</div>
<div class="text-center panel-body">
<div class="graph-buttons">
{% include "dashboard/_graph-time-buttons.html" %}
</div>
<div class="text-center graph-images">
<img src="{% url "dashboard.views.template-graph" object.pk "instances" graph_time %}"/>
</div>
{% if request.user.is_superuser %}
<a href="{% url "dashboard.views.vm-list" %}?s=template:{{object.pk}}&stype=all">
{% trans "List all template instances" %}
</a>
{% endif %}
</div>
</div>
{% endif %}
</div><!-- .col-md-4 -->
</div><!-- .row -->
......
......@@ -66,6 +66,17 @@
{% endfor %}
</div>
<hr />
{% if instance.disks.all %}
<div id="vm-details-resize-how-to">
<i class="fa fa-question"></i>
{% url "info.resize" as resize_url %}
{% blocktrans with url=resize_url %}
If you need help resizing the disks check out our <a href="{{ url }}">resize how-to.</a>
{% endblocktrans %}
</div>
{% endif %}
{% if user.is_superuser %}
<hr/>
......
......@@ -18,9 +18,6 @@
from django.template import Library
import arrow
from dashboard.arrow_local import HungarianLocale
for name in HungarianLocale.names:
arrow.locales._locales[name] = HungarianLocale
register = Library()
......
......@@ -601,8 +601,8 @@ class CircleSeleniumMixin(SeleniumMixin):
choices = self.driver.find_elements_by_css_selector(
"input[type='radio']")
choice_list = [item for item in choices if (
'test' not in item.get_attribute('value')
and item.get_attribute('value') != 'base_vm')]
'test' not in item.get_attribute('value') and
item.get_attribute('value') != 'base_vm')]
chosen = random.randint(0, len(choice_list) - 1)
choice_list[chosen].click()
self.driver.find_element_by_id(
......
......@@ -47,7 +47,7 @@ from .views import (
LeaseAclUpdateView,
toggle_template_tutorial,
ClientCheck, TokenLogin,
VmGraphView, NodeGraphView, NodeListGraphView,
VmGraphView, NodeGraphView, NodeListGraphView, TemplateGraphView,
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView,
......@@ -152,6 +152,10 @@ urlpatterns = patterns(
r'(?P<time>[0-9]{1,2}[hdwy])$'),
NodeListGraphView.as_view(),
name='dashboard.views.node-list-graph'),
url((r'^template/(?P<pk>\d+)/graph/(?P<metric>[a-z]+)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'),
TemplateGraphView.as_view(),
name='dashboard.views.template-graph'),
url(r'^group/(?P<pk>\d+)/$', GroupDetailView.as_view(),
name='dashboard.views.group-detail'),
url(r'^group/(?P<pk>\d+)/update/$', GroupProfileUpdate.as_view(),
......
......@@ -28,7 +28,7 @@ from django.views.generic import View
from braces.views import LoginRequiredMixin
from vm.models import Instance, Node
from vm.models import Instance, Node, InstanceTemplate
logger = logging.getLogger(__name__)
......@@ -152,6 +152,28 @@ class NodeGraphView(GraphViewBase):
return self.model.objects.get(id=pk)
class TemplateGraphView(GraphViewBase):
model = InstanceTemplate
base = Metric
def get_object(self, request, pk):
instance = super(TemplateGraphView, self).get_object(request, pk)
if not instance.has_level(request.user, 'operator'):
raise PermissionDenied()
return instance
class TemplateVms(object):
metric_name = "instances.running"
title = _("Instance count")
label = _("instance count")
def get_minmax(self):
return (0, None)
register_graph(TemplateVms, 'instances', TemplateGraphView)
class NodeListGraphView(GraphViewBase):
model = Node
base = Metric
......
......@@ -136,6 +136,10 @@ class HelpView(TemplateView):
return ctx
class ResizeHelpView(TemplateView):
template_name = "info/resize.html"
class OpenSearchDescriptionView(TemplateView):
template_name = "dashboard/vm-opensearch.xml"
content_type = "application/opensearchdescription+xml"
......
......@@ -47,7 +47,8 @@ from ..tables import TemplateListTable, LeaseListTable
from .util import (
AclUpdateView, FilterMixin,
TransferOwnershipConfirmView, TransferOwnershipView,
DeleteViewBase
DeleteViewBase,
GraphMixin
)
logger = logging.getLogger(__name__)
......@@ -258,7 +259,8 @@ class TemplateDelete(DeleteViewBase):
object.delete()
class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
class TemplateDetail(LoginRequiredMixin, GraphMixin,
SuccessMessageMixin, UpdateView):
model = InstanceTemplate
template_name = "dashboard/template-edit.html"
form_class = TemplateForm
......@@ -300,6 +302,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
context['is_owner'] = obj.has_level(self.request.user, 'owner')
context['aclform'] = AclUserOrGroupAddForm()
context['parent'] = obj.parent
context['show_graph'] = obj.has_level(self.request.user, 'operator')
return context
def get_success_url(self):
......
......@@ -545,8 +545,8 @@ class UserList(LoginRequiredMixin, PermissionRequiredMixin, SingleTableView):
q = self.search_form.cleaned_data.get('s')
if q:
filters = (Q(username__icontains=q) | Q(email__icontains=q)
| Q(profile__org_id__icontains=q))
filters = (Q(username__icontains=q) | Q(email__icontains=q) |
Q(profile__org_id__icontains=q))
for w in q.split()[:3]:
filters |= (
Q(first_name__icontains=w) | Q(last_name__icontains=w))
......
......@@ -150,8 +150,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
# resources forms
can_edit = (
instance.has_level(user, "owner")
and self.request.user.has_perm("vm.change_resources"))
instance.has_level(user, "owner") and
self.request.user.has_perm("vm.change_resources"))
context['resources_form'] = VmResourcesForm(
can_edit=can_edit, instance=instance)
......@@ -174,8 +174,10 @@ class VmDetailView(GraphMixin, CheckedDetailView):
context['is_owner'] = is_owner
# operation also allows RUNNING (if with_shutdown is present)
context['save_resources_enabled'] = instance.status not in ("RUNNING",
"PENDING")
context['save_resources_enabled'] = instance.status in (
"STOPPED",
"PENDING",
)
return context
......@@ -567,8 +569,8 @@ class VmResourcesChangeView(VmOperationView):
content_type="application=json"
)
else:
return HttpResponseRedirect(instance.get_absolute_url()
+ "#resources")
return HttpResponseRedirect(instance.get_absolute_url() +
"#resources")
else:
extra = form.cleaned_data
extra['max_ram_size'] = extra['ram_size']
......@@ -1259,8 +1261,9 @@ def vm_activity(request, pk):
response['status'] = instance.status
response['icon'] = instance.get_status_icon()
latest = instance.get_latest_activity_in_progress()
response['is_new_state'] = (latest and latest.resultant_state is not None
and instance.status != latest.resultant_state)
response['is_new_state'] = (latest and
latest.resultant_state is not None and
instance.status != latest.resultant_state)
context = {
'instance': instance,
......
......@@ -188,11 +188,11 @@ class IPNetworkField(models.Field):
if isinstance(value, IPNetwork):
if self.version == 4:
return ('.'.join("%03d" % x for x in value.ip.words)
+ '/%02d' % value.prefixlen)
return ('.'.join("%03d" % x for x in value.ip.words) +
'/%02d' % value.prefixlen)
else:
return (':'.join("%04X" % x for x in value.ip.words)
+ '/%03d' % value.prefixlen)
return (':'.join("%04X" % x for x in value.ip.words) +
'/%03d' % value.prefixlen)
return value
def formfield(self, **kwargs):
......
......@@ -21,6 +21,8 @@ from django.core.management.base import BaseCommand
from firewall.tasks.local_tasks import reloadtask
from argparse import ArgumentTypeError
class Command(BaseCommand):
......@@ -33,6 +35,20 @@ class Command(BaseCommand):
default=False,
help='synchronous reload')
parser.add_argument('--timeout',
action='store',
dest='timeout',
default=15,
type=self.positive_int,
help='timeout for synchronous reload')
def handle(self, *args, **options):
reloadtask('Vlan', sync=options["sync"])
reloadtask('Vlan', sync=options["sync"], timeout=options["timeout"])
def positive_int(self, val):
if not val.isdigit():
raise ArgumentTypeError("'%s' is not a valid positive int" % val)
return int(val)
......@@ -700,8 +700,8 @@ class Host(models.Model):
return self.vlan.network_type != 'public'
def clean(self):
if (self.external_ipv4 and not self.shared_ip and self.behind_nat
and Host.objects.exclude(id=self.id).filter(
if (self.external_ipv4 and not self.shared_ip and self.behind_nat and
Host.objects.exclude(id=self.id).filter(
external_ipv4=self.external_ipv4)):
raise ValidationError(_("If shared_ip has been checked, "
"external_ipv4 has to be unique."))
......
......@@ -109,4 +109,4 @@ def reloadtask(type='Host', timeout=15, sync=False):
if all([cache.add("%s_lock" % i, 'true', 30) for i in reload]):
res = reloadtask_worker.apply_async(queue='localhost.man', countdown=5)
if sync:
res.get(15)
res.get(timeout)
......@@ -55,8 +55,8 @@ def select_node(instance, nodes):
'''
# check required traits
nodes = [n for n in nodes
if n.schedule_enabled and n.online
and has_traits(instance.req_traits.all(), n)]
if n.schedule_enabled and n.online and
has_traits(instance.req_traits.all(), n)]
if not nodes:
logger.warning('select_node: no usable node for %s', unicode(instance))
raise TraitsUnsatisfiableException()
......
......@@ -54,7 +54,7 @@ def measure_response_time():
@celery.task(ignore_result=True)
def check_celery_queues():
graphite_string = lambda component, hostname, celery, is_alive, time: (
def graphite_string(component, hostname, celery, is_alive, time): return (
"%s.%s.celery-queues.%s %d %s" % (
component, hostname, celery, 1 if is_alive else 0, time)
)
......@@ -92,7 +92,7 @@ def check_celery_queues():
@celery.task(ignore_result=True)
def instance_per_template():
graphite_string = lambda pk, state, val, time: (
def graphite_string(pk, state, val, time): return (
"template.%d.instances.%s %d %s" % (
pk, state, val, time)
)
......@@ -111,7 +111,7 @@ def instance_per_template():
@celery.task(ignore_result=True)
def allocated_memory():
graphite_string = lambda hostname, val, time: (
def graphite_string(hostname, val, time): return (
"circle.%s.memory.allocated %d %s" % (
hostname, val, time)
)
......
......@@ -979,8 +979,8 @@ def remove_switch_port_device(request, **kwargs):
def add_switch_port_device(request, **kwargs):
device_name = request.POST.get('device_name')
if (request.method == "POST" and device_name and len(device_name) > 0
and EthernetDevice.objects.filter(name=device_name).count() == 0):
if (request.method == "POST" and device_name and len(device_name) > 0 and
EthernetDevice.objects.filter(name=device_name).count() == 0):
switch_port = SwitchPort.objects.get(pk=kwargs['pk'])
new_device = EthernetDevice(name=device_name, switch_port=switch_port)
......
......@@ -22,6 +22,8 @@ from django.utils.translation import ugettext_lazy as _
from django.template import RequestContext
from django.template.loader import render_to_string
from sizefield.widgets import FileSizeWidget
from sizefield.utils import filesizeformat
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
......@@ -70,34 +72,32 @@ class InitialFromFileMixin(object):
RequestContext(request, {}),
)
def clean(self):
cleaned_data = super(InitialFromFileMixin, self).clean()
if cleaned_data['message'].strip() == self.initial['message'].strip():
raise ValidationError(
_("Fill in the message."),
code="invalid")
return cleaned_data
def clean_message(self):
message = self.cleaned_data['message']
if message.strip() == self.initial['message'].strip():
raise ValidationError(_("Fill in the message."), code="invalid")
return message.strip()
class TemplateRequestForm(InitialFromFileMixin, Form):
message = CharField(widget=Textarea, label=_("Message"))
template = ModelChoiceField(TemplateAccessType.objects.all(),
label=_("Template share"))
level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect,
initial=TemplateAccessAction.LEVELS.user)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/template.html"
class LeaseRequestForm(InitialFromFileMixin, Form):
lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease"))
message = CharField(widget=Textarea)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/lease.html"
class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
message = CharField(widget=Textarea)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/resources.html"
......@@ -110,3 +110,28 @@ class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
raise ValidationError(
_("You haven't changed any of the resources."),
code="invalid")
class ResizeRequestForm(InitialFromFileMixin, Form):
message = CharField(widget=Textarea, label=_("Message"))
size = CharField(widget=FileSizeWidget, label=_('Size'),
help_text=_('Size to resize the disk in bytes or with'
' units like MB or GB.'))
initial_template = "request/initials/resize.html"
def __init__(self, *args, **kwargs):
self.disk = kwargs.pop("disk")
super(ResizeRequestForm, self).__init__(*args, **kwargs)
def clean_size(self):
cleaned_data = super(ResizeRequestForm, self).clean()
disk = self.disk
size_in_bytes = cleaned_data.get("size")
if not size_in_bytes.isdigit() and len(size_in_bytes) > 0:
raise ValidationError(_("Invalid format, you can use GB or MB!"))
if int(size_in_bytes) < int(disk.size):
raise ValidationError(_("Disk size must be greater than the actual"
"size (%s).") % filesizeformat(disk.size))
return size_in_bytes
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import sizefield.models
class Migration(migrations.Migration):
dependencies = [
('vm', '0002_interface_model'),
('storage', '0002_disk_bus'),
('request', '0003_auto_20150410_1917'),
]
operations = [
migrations.CreateModel(
name='DiskResizeAction',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('size', sizefield.models.FileSizeField(default=None, null=True)),
('disk', models.ForeignKey(to='storage.Disk')),
('instance', models.ForeignKey(to='vm.Instance')),
],
options={
'abstract': False,
},
),
migrations.AlterField(
model_name='request',
name='type',
field=models.CharField(max_length=10, choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access request'), (b'resize', 'disk resize request')]),
),
]
......@@ -32,10 +32,14 @@ from django.utils.translation import (
from django.core.urlresolvers import reverse
import requests
from sizefield.models import FileSizeField
from model_utils.models import TimeStampedModel
from model_utils import Choices
from sizefield.utils import filesizeformat
from vm.models import Instance, InstanceTemplate, Lease
from vm.operations import ResourcesOperation, ResizeDiskOperation
from storage.models import Disk
logger = logging.getLogger(__name__)
......@@ -49,6 +53,9 @@ class RequestAction(Model):
def accept_msg(self):
raise NotImplementedError
def is_acceptable(self):
return True
class Meta:
abstract = True
......@@ -77,6 +84,7 @@ class Request(TimeStampedModel):
('resource', _('resource request')),
('lease', _("lease request")),
('template', _("template access request")),
('resize', _("disk resize request")),
)
type = CharField(choices=TYPES, max_length=10)
message = TextField(verbose_name=_("Message"))
......@@ -99,7 +107,8 @@ class Request(TimeStampedModel):
return {
'resource': "tasks",
'lease': "clock-o",
'template': "puzzle-piece"
'template': "puzzle-piece",
'resize': "arrows-alt",
}.get(self.type)
def get_effect(self):
......@@ -143,6 +152,10 @@ class Request(TimeStampedModel):
decline_msg, url=self.get_absolute_url(), reason=self.reason,
)
@property
def is_acceptable(self):
return self.action.is_acceptable()
class LeaseType(RequestType):
lease = ForeignKey(Lease, verbose_name=_("Lease"))
......@@ -200,6 +213,9 @@ class ResourceChangeAction(RequestAction):
'priority': self.priority,
}
def is_acceptable(self):
return self.instance.status in ResourcesOperation.accept_states
class ExtendLeaseAction(RequestAction):
instance = ForeignKey(Instance)
......@@ -246,6 +262,30 @@ class TemplateAccessAction(RequestAction):
) % ", ".join([x.name for x in self.template_type.templates.all()])
class DiskResizeAction(RequestAction):
instance = ForeignKey(Instance)
disk = ForeignKey(Disk)
size = FileSizeField(null=True, default=None)
def accept(self, user):
self.instance.resize_disk(disk=self.disk, size=self.size, user=user)
@property
def accept_msg(self):
return _(
'The disk <em class="text-muted">%(disk_name)s (#%(id)d)</em> of '
'<a href="%(url)s">%(vm_name)s</a> got resized. '
'The new size is: %(bytes)d bytes (%(size)s).'
) % {'disk_name': self.disk.name, 'id': self.disk.id,
'url': self.instance.get_absolute_url(),
'vm_name': self.instance.name,
'bytes': self.size, 'size': filesizeformat(self.size),
}
def is_acceptable(self):
return self.instance.status in ResizeDiskOperation.accept_states
def send_notifications(sender, instance, created, **kwargs):
if not created:
return
......
{% load i18n %}
{% load crispy_forms_tags %}
{% load sizefieldtags %}
<dl>
<dt>{% trans "Virtual machine" %}</dt>
<dd><a href="{{ vm.get_absolute_url }}">{{ vm.name }}</a></dd>
<dt>{% trans "Disk" %}</dt>
<dd>
{% if request.user.is_superuser %}
<a href="{{ disk.get_absolute_url }}">{{ disk.name }} (#{{ disk.id }})</a>
{% else %}
{{ disk.name }} (#{{ disk.id }})
{% endif %}
- {{ disk.size|filesize }}
</dd>
</dl>
<form action="{% url "request.views.request-resize" vm_pk=vm.pk disk_pk=disk.pk %}" method="POST">
{% include "display-form-errors.html" %}
{% csrf_token %}
{{ form.size|as_crispy_field }}
{{ form.message|as_crispy_field }}
<input type="submit" class="btn btn-primary" id="op-form-send"/>
</form>
......@@ -3,6 +3,7 @@
{% load i18n %}
{% load render_table from django_tables2 %}
{% load arrowfilter %}
{% load sizefieldtags %}
{% block title-page %}{% trans "Request" %}{% endblock %}
......@@ -65,6 +66,15 @@
<dd>{{ action.get_readable_level }}</dd>
</dl>
{% elif object.type == "resource" %}
{% if not is_acceptable %}
<div class="alert alert-warning">
{% blocktrans %}
To change the resources the virtual machine must be in one of the following states:
STOPPED, PENDING, RUNNING. If the virtual machine is running it will be
automatically stopped when accepting the request.
{% endblocktrans %}
</div>
{% endif %}
<dl>
<dt>{% trans "VM name" %}</dt>
<dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd>
......@@ -74,7 +84,7 @@
{{ action.instance.get_status_display|upper }}
</dd>
<dt>{% trans "VM description" %}</dt>
<dd>{{ action.instance.description }}</dd>
<dd>{{ action.instance.description|default:"-" }}</dd>
<dt>
{% trans "Priority" %}
<span class="text-muted" style="font-weight: normal;">{% trans "(old values in parentheses)" %}</span>
......@@ -85,8 +95,39 @@
<dt>{% trans "Ram size" %}</dt>
<dd>{{ action.ram_size }} ({{ action.instance.ram_size }}) MiB</dd>
</dl>
{% elif object.type == "resize" %}
{% if not is_acceptable %}
<div class="alert alert-warning">
{% trans "To resize the disk the virtual machine must be in RUNNING state." %}
</div>
{% endif %}
<dl>
<dt>{% trans "VM name" %}</dt>
<dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd>
<dt>{% trans "Status" %}</dt>
<dd>
<i class="fa {{ action.instance.get_status_icon }}"></i>
{{ action.instance.get_status_display|upper }}
</dd>
<dt>{% trans "VM description" %}</dt>
<dd>{{ action.instance.description|default:"-" }}</dd>
<dt>{% trans "Disk" %}</dt>
<dd>
{% if request.user.is_superuser %}
<a href="{{ action.disk.get_absolute_url }}">
{{ action.disk.name }} (#{{ action.disk.id}})
</a>
{% else %}
{{ action.disk.name }} (#{{ action.disk.id}})</dd>
{% endif %}
</dd>
<dt>{% trans "Current size" %}</dt>
<dd>{{ action.disk.size|filesize}} ({{ action.disk.size }} bytes)</dd>
<dt>{% trans "Requested size" %}</dt>
<dd>{{ action.size|filesize}} ({{ action.size }} bytes)</dd>
</dl>
{% else %}
hacks!!!
Are you adding a new action type?
{% endif %}
{% if object.status == "PENDING" and request.user.is_superuser %}
......@@ -103,7 +144,7 @@
{% trans "Decline" %}
</button>
</form>
{% if object.type == "resource" and action.instance.status not in accept_states %}
{% if not is_acceptable %}
{% trans "You can't accept this request because of the VM's state." %}
{% else %}
<form method="POST">
......
{% spaceless %}
{% if LANGUAGE_CODE == "en" %}
Why do you need a bigger disk?
{% else %} {# place your translations here #}
Why do you need a bigger disk?
{% endif %}
{% endspaceless %}
......@@ -23,7 +23,7 @@ from .views import (
LeaseTypeCreate, LeaseTypeDetail,
TemplateAccessTypeCreate, TemplateAccessTypeDetail,
TemplateRequestView, LeaseRequestView, ResourceRequestView,
LeaseTypeDelete, TemplateAccessTypeDelete,
LeaseTypeDelete, TemplateAccessTypeDelete, ResizeRequestView,
)
urlpatterns = patterns(
......@@ -60,4 +60,6 @@ urlpatterns = patterns(
name="request.views.request-lease"),
url(r'resource/(?P<vm_pk>\d+)/$', ResourceRequestView.as_view(),
name="request.views.request-resource"),
url(r'resize/(?P<vm_pk>\d+)/(?P<disk_pk>\d+)/$',
ResizeRequestView.as_view(), name="request.views.request-resize"),
)
......@@ -19,27 +19,29 @@ from __future__ import unicode_literals, absolute_import
from django.views.generic import (
UpdateView, TemplateView, DetailView, CreateView, FormView, DeleteView,
)
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect, get_object_or_404
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.http import JsonResponse
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django_tables2 import SingleTableView
from request.models import (
Request, TemplateAccessType, LeaseType, TemplateAccessAction,
ExtendLeaseAction, ResourceChangeAction,
ExtendLeaseAction, ResourceChangeAction, DiskResizeAction
)
from storage.models import Disk
from vm.models import Instance
from vm.operations import ResourcesOperation
from request.tables import (
RequestTable, TemplateAccessTypeTable, LeaseTypeTable,
)
from request.forms import (
LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm,
LeaseRequestForm, ResourceRequestForm,
LeaseRequestForm, ResourceRequestForm, ResizeRequestForm,
)
......@@ -93,7 +95,7 @@ class RequestDetail(LoginRequiredMixin, DetailView):
context = super(RequestDetail, self).get_context_data(**kwargs)
context['action'] = request.action
context['accept_states'] = ResourcesOperation.accept_states
context['is_acceptable'] = request.is_acceptable
# workaround for http://git.io/vIIYi
context['request'] = self.request
......@@ -167,6 +169,7 @@ class RequestTypeList(LoginRequiredMixin, SuperuserRequiredMixin,
class TemplateRequestView(LoginRequiredMixin, FormView):
form_class = TemplateRequestForm
template_name = "request/request-template.html"
success_message = _("Request successfully sent.")
def get_form_kwargs(self):
kwargs = super(TemplateRequestView, self).get_form_kwargs()
......@@ -192,7 +195,8 @@ class TemplateRequestView(LoginRequiredMixin, FormView):
)
req.save()
return redirect("/")
messages.success(self.request, self.success_message)
return redirect(reverse("dashboard.index"))
class VmRequestMixin(LoginRequiredMixin, object):
......@@ -224,6 +228,7 @@ class LeaseRequestView(VmRequestMixin, FormView):
form_class = LeaseRequestForm
template_name = "request/request-lease.html"
user_level = "operator"
success_message = _("Request successfully sent.")
def form_valid(self, form):
data = form.cleaned_data
......@@ -244,6 +249,7 @@ class LeaseRequestView(VmRequestMixin, FormView):
)
req.save()
messages.success(self.request, self.success_message)
return redirect(vm.get_absolute_url())
......@@ -251,6 +257,7 @@ class ResourceRequestView(VmRequestMixin, FormView):
form_class = ResourceRequestForm
template_name = "request/request-resource.html"
user_level = "user"
success_message = _("Request successfully sent.")
def get_form_kwargs(self):
kwargs = super(ResourceRequestView, self).get_form_kwargs()
......@@ -287,4 +294,60 @@ class ResourceRequestView(VmRequestMixin, FormView):
)
req.save()
messages.success(self.request, self.success_message)
return redirect(vm.get_absolute_url())
class ResizeRequestView(VmRequestMixin, FormView):
form_class = ResizeRequestForm
template_name = "request/_request-resize-form.html"
user_level = "owner"
success_message = _("Request successfully sent.")
def get_disk(self, *args, **kwargs):
disk = get_object_or_404(Disk, pk=self.kwargs['disk_pk'])
if disk not in self.get_vm().disks.all():
raise SuspiciousOperation
return disk
def get_form_kwargs(self):
kwargs = super(ResizeRequestView, self).get_form_kwargs()
kwargs['disk'] = self.get_disk()
return kwargs
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_modal.html']
else:
return ['dashboard/_base.html']
def get_context_data(self, **kwargs):
context = super(ResizeRequestView, self).get_context_data(**kwargs)
context['disk'] = self.get_disk()
context['template'] = self.template_name
context['box_title'] = context['title'] = _("Disk resize request")
context['ajax_title'] = True
return context
def form_valid(self, form):
disk = self.get_disk()
if not disk.is_resizable:
raise SuspiciousOperation
vm = self.get_vm()
data = form.cleaned_data
user = self.request.user
dra = DiskResizeAction(instance=vm, disk=disk, size=data['size'])
dra.save()
req = Request(user=user, message=data['message'], action=dra,
type=Request.TYPES.resize)
req.save()
if self.request.is_ajax():
return JsonResponse({'success': True,
'messages': [self.success_message]})
else:
messages.success(self.request, self.success_message)
return redirect(vm.get_absolute_url())
......@@ -28,6 +28,7 @@ from celery.contrib.abortable import AbortableAsyncResult
from django.db.models import (Model, BooleanField, CharField, DateTimeField,
ForeignKey)
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from model_utils.models import TimeStampedModel
......@@ -535,3 +536,10 @@ class Disk(TimeStampedModel):
disk.is_ready = True
disk.save()
return disk
def get_absolute_url(self):
return reverse('dashboard.views.disk-detail', kwargs={'pk': self.pk})
@property
def is_resizable(self):
return self.type in ('qcow2-norm', 'raw-rw')
......@@ -29,7 +29,7 @@
</blockquote>
<h3 id="how-can-i-portforward">{% trans "How can I open ports?" %}</h3>
<h3 id="how-can-i-open-ports">{% trans "How can I open ports?" %}</h3>
<blockquote>
<ol>
......
......@@ -4,7 +4,7 @@
<li><a href="#how-can-i-create-a-vm-and-give-to-another-user">{% trans "How can I create a VM and give to another user?" %}</a></li>
<li><a href="#how-can-i-portforward">{% trans "How can I portforward?" %}</a></li>
<li><a href="#how-can-i-open-ports">{% trans "How can I open ports?" %}</a></li>
<li><a href="#my-machines-lease-is-short-how-can-i-extend-it">{% trans "My machine’s lease is short. How can I extend it?" %}</a></li>
<li><a href="#how-can-i-have-more-cpumemory">{% trans "How can I have more CPU/memory?" %}</a></li>
......
......@@ -348,32 +348,34 @@
<blockquote>
<p>
<h4>{% trans "Architecture" %}</h4>
The user can choose the template's architecture (x86 or x86-64).
{% trans "The user can choose the template's architecture (x86 or x86-64)." %}
</p>
<p>
<h4>{% trans "Access method" %}</h4>
The default access method is modifiable. Currently SSH, RDP and NX are supported.
{% trans "The default access method is modifiable. Currently SSH, RDP and NX are supported." %}
</p>
<p>
<h4>{% trans "Boot menu" %} </h4>
Check it to turn on the boot menu.
{% trans "Check it to turn on the boot menu." %}
</p>
<p>
<h4>{% trans "Traits" %}</h4>
By adding or removing traits we can guarantee specific features the host node will have (like <em>GPU</em>) for the virtual machine.
{% trans "By adding or removing traits we can guarantee specific features the host node will have (like <em>GPU</em>) for the virtual machine." %}
</p>
<p>
<h4>{% trans "Operating system" %}</h4>
The name of the operating system.
{% trans "The name of the operating system." %}
</p>
<p>
<h4>{% trans "Agent" %}</h4>
Check this if the machine has agent installed and the manager should wait for its start.
{% trans "Check this if the machine has agent installed and the manager should wait for its start." %}
</p>
<p>
<h4>{% trans "Raw data" %}</h4>
The CIRCLE Cloud is using libvirt, so the owner can customize the running VM's options here by
<a href="https://libvirt.org/formatdomain.html">libvirt domain parameters</a>.
{% blocktrans %}
The CIRCLE Cloud is using libvirt, so the owner can customize the running VM's options here by
<a href="https://libvirt.org/formatdomain.html">libvirt domain parameters</a>.
{% endblocktrans %}
</p>
</blockquote>
......@@ -387,7 +389,7 @@
<h4 id="how-can-i-give-access-to-users-or-groups-to-the-template">{% trans "How can I grant access for users or groups to the template?" %}</h4>
<h4 id="how-can-i-grant-access-for-users-or-groups-to-the-template">{% trans "How can I grant access for users or groups to the template?" %}</h4>
<blockquote>
<p>
{% blocktrans %}
......
......@@ -10,10 +10,8 @@
<ul>
<li><a href="#how-can-i-create-a-vm">{% trans "How can I create a VM?" %}</a></li>
<li><a href="#how-can-i-mark-frequently-used-vms">{% trans "How can I mark frequently used VMs?" %}</a></li>
<li><a href="#how-can-i-search-for-vms">{% trans "How can I search for VMs?" %}</a></li>
</ul>
</li>
<li><a href="#templates-box">{% trans "Templates box" %}</a></li>
</ul>
</li>
......@@ -37,8 +35,7 @@
<li>
<ul><a href="#home">{% trans "Home" %}</a>
<li><a href="#expiration">{% trans "Expiration" %}</a></li>
<li><a href="#how-can-i-expand-the-vms-expiration-date">{% trans "How can I expand the VM’s expiration date?" %}</a></li>
<li><a href="#file-management">{% trans "File management" %}</a></li>
<li><a href="#how-can-i-extend-the-vms-expiration-date">{% trans "How can I extend the VM's expiration date?" %}</a></li>
<li><a href="#how-can-i-share-previously-uploaded-files-with-the-vm">{% trans "How can I share previously uploaded files with the VM?" %}</a></li>
</ul>
</li>
......@@ -68,9 +65,7 @@
<ul><a href="#templates">{% trans "Templates" %}</a>
<li><a href="#how-can-i-create-templates">{% trans "How can I create templates?" %}</a></li>
<li><a href="#what-kind-of-options-are-customizable-in-the-template">{% trans "What kind of options are customizable in the template?" %}</a></li>
<li><a href="#how-can-i-change-the-expiration-of-the-templates-vms">{% trans "How can I change the expiration of the tempalte's VMs" %}</a></li>
<li><a href="#how-can-i-give-the-template-to-other-user">{% trans "How can I give the template to other user?" %}</a></li>
<li><a href="#how-can-i-give-access-to-users-or-groups-to-the-template">{% trans "How can I give access to users or groups to the template?"%}</a></li>
<li><a href="#how-can-i-grant-access-for-users-or-groups-to-the-template">{% trans "How can I grant access for users or groups to the template?"%}</a></li>
</ul>
</li>
......@@ -92,7 +87,6 @@
<ul><a href="#profile">{% trans "Profile" %}</a>
<li><a href="#how-can-i-change-my-password">{% trans "How can I change my password?" %}</a></li>
<li><a href="#how-can-i-store-public-keys-on-the-vms">{% trans "How can I store public keys on the VMs?" %}</a></li>
<li><a href="#how-can-i-change-connection-template">{% trans "How can I change connection template?" %}</a></li>
</ul>
</li>
......
......@@ -145,8 +145,8 @@ class InstanceActivity(ActivityModel):
def has_percentage(self):
op = self.instance.get_operation_from_activity_code(self.activity_code)
return (self.task_uuid and op and op.has_percentage
and not self.finished)
return (self.task_uuid and op and op.has_percentage and
not self.finished)
def get_percentage(self):
"""Returns the percentage of the running operation if available.
......
......@@ -174,6 +174,6 @@ class Trait(Model):
@property
def in_use(self):
return (
self.instance_set.exists() or self.node_set.exists()
or self.instancetemplate_set.exists()
self.instance_set.exists() or self.node_set.exists() or
self.instancetemplate_set.exists()
)
......@@ -200,6 +200,10 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
def get_running_instances(self):
return Instance.active.filter(template=self, status="RUNNING")
@property
def metric_prefix(self):
return 'template.%d' % self.pk
class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
TimeStampedModel):
......@@ -848,8 +852,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
def is_in_status_change(self):
latest = self.get_latest_activity_in_progress()
return (latest and latest.resultant_state is not None
and self.status != latest.resultant_state)
return (latest and latest.resultant_state is not None and
self.status != latest.resultant_state)
@property
def metric_prefix(self):
......
......@@ -133,8 +133,8 @@ class InstanceOperation(Operation):
super(InstanceOperation, self).check_auth(user=user)
if (self.instance.node and not self.instance.node.online
and not user.is_superuser):
if (self.instance.node and not self.instance.node.online and
not user.is_superuser):
raise self.instance.WrongStateError(self.instance)
def create_activity(self, parent, user, kwargs):
......@@ -306,6 +306,9 @@ class ResizeDiskOperation(RemoteInstanceOperation):
size=filesizeformat(kwargs['size']), name=kwargs['disk'].name)
def _operation(self, disk, size):
if not disk.is_resizable:
raise HumanReadableException.create(ugettext_noop(
'Disk type "%(type)s" is not resizable.'), type=disk.type)
super(ResizeDiskOperation, self)._operation(disk=disk, size=size)
disk.size = size
disk.save()
......@@ -534,8 +537,8 @@ class MigrateOperation(RemoteInstanceOperation):
remote_timeout = 1000
def _get_remote_args(self, to_node, live_migration, **kwargs):
return (super(MigrateOperation, self)._get_remote_args(**kwargs)
+ [to_node.host.hostname, live_migration])
return (super(MigrateOperation, self)._get_remote_args(**kwargs) +
[to_node.host.hostname, live_migration])
def rollback(self, activity):
with activity.sub_activity(
......@@ -908,8 +911,8 @@ class SleepOperation(InstanceOperation):
def _get_remote_args(self, **kwargs):
return (super(SleepOperation.SuspendVmOperation, self)
._get_remote_args(**kwargs)
+ [self.instance.mem_dump['path']])
._get_remote_args(**kwargs) +
[self.instance.mem_dump['path']])
@register_operation
......@@ -962,8 +965,8 @@ class WakeUpOperation(InstanceOperation):
def _get_remote_args(self, **kwargs):
return (super(WakeUpOperation.WakeUpVmOperation, self)
._get_remote_args(**kwargs)
+ [self.instance.mem_dump['path']])
._get_remote_args(**kwargs) +
[self.instance.mem_dump['path']])
@register_operation
......@@ -1408,9 +1411,9 @@ class PasswordResetOperation(RemoteAgentOperation):
task = agent_tasks.change_password
required_perms = ()
def _get_remote_args(self, password, **kwargs):
return (super(PasswordResetOperation, self)._get_remote_args(**kwargs)
+ [password])
def _get_remote_args(self, password, **kwrgs):
return (super(PasswordResetOperation, self)._get_remote_args(**kwrgs) +
[password])
def _operation(self, password=None):
if not password:
......@@ -1433,8 +1436,8 @@ class InstallKeysOperation(RemoteAgentOperation):
def _get_remote_args(self, user, keys=None, **kwargs):
if keys is None:
keys = list(user.userkey_set.values_list('key', flat=True))
return (super(InstallKeysOperation, self)._get_remote_args(**kwargs)
+ [keys])
return (super(InstallKeysOperation, self)._get_remote_args(**kwargs) +
[keys])
@register_operation
......@@ -1446,8 +1449,8 @@ class RemoveKeysOperation(RemoteAgentOperation):
required_perms = ()
def _get_remote_args(self, user, keys, **kwargs):
return (super(RemoveKeysOperation, self)._get_remote_args(**kwargs)
+ [keys])
return (super(RemoveKeysOperation, self)._get_remote_args(**kwargs) +
[keys])
@register_operation
......@@ -1541,8 +1544,8 @@ class AgentStartedOperation(InstanceOperation):
def _get_remote_args(self, **kwargs):
cls = AgentStartedOperation.SetTimeOperation
return (super(cls, self)._get_remote_args(**kwargs)
+ [time.time()])
return (super(cls, self)._get_remote_args(**kwargs) +
[time.time()])
@register_operation
class SetHostnameOperation(SubOperationMixin, RemoteAgentOperation):
......@@ -1552,8 +1555,8 @@ class AgentStartedOperation(InstanceOperation):
def _get_remote_args(self, **kwargs):
cls = AgentStartedOperation.SetHostnameOperation
return (super(cls, self)._get_remote_args(**kwargs)
+ [self.instance.short_hostname])
return (super(cls, self)._get_remote_args(**kwargs) +
[self.instance.short_hostname])
@register_operation
class RestartNetworkingOperation(SubOperationMixin, RemoteAgentOperation):
......@@ -1572,8 +1575,8 @@ class AgentStartedOperation(InstanceOperation):
interfaces = {str(host.mac): host.get_network_config()
for host in hosts}
cls = AgentStartedOperation.ChangeIpOperation
return (super(cls, self)._get_remote_args(**kwargs)
+ [interfaces, settings.FIREWALL_SETTINGS['rdns_ip']])
return (super(cls, self)._get_remote_args(**kwargs) +
[interfaces, settings.FIREWALL_SETTINGS['rdns_ip']])
@register_operation
......@@ -1697,8 +1700,8 @@ class AbstractDiskOperation(SubOperationMixin, RemoteInstanceOperation):
required_perms = ()
def _get_remote_args(self, disk, **kwargs):
return (super(AbstractDiskOperation, self)._get_remote_args(**kwargs)
+ [disk.get_vmdisk_desc()])
return (super(AbstractDiskOperation, self)._get_remote_args(**kwargs) +
[disk.get_vmdisk_desc()])
@register_operation
......
Deploy
======
Deploying CIRCLE
================
This tutorial describes the installation of a production environment. To
have a fully working environment, you have to set up the other components
......@@ -145,4 +145,4 @@ the portal application server::
sudo cp miscellaneous/mancelery.conf /etc/init/
sudo start mancelery
sudo cp miscellaneous/portal-uwsgi.conf /etc/init/
sudo start portal-uwsgi
sudo start portal-uwsgi
\ No newline at end of file
amqp==1.4.6
anyjson==0.3.3
arrow==0.5.4
arrow==0.6.0
billiard==3.3.0.20
bpython==0.14.1
celery==3.1.18
......
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