Commit 96364f6a by Kálmán Viktor

Merge branch 'master' into issue-sliders

Conflicts:
	circle/common/models.py
parents 478df358 d6891337
......@@ -35,7 +35,11 @@ SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS += (
'acl.tests',
'django_nose',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = ['--with-doctest']
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = {
'default': {
......
......@@ -47,17 +47,24 @@ class WorkerNotFound(Exception):
def activitycontextimpl(act, on_abort=None, on_commit=None):
try:
try:
yield act
except HumanReadableException as e:
result = e
raise
except BaseException as e:
# BaseException is the common parent of Exception and
# system-exiting exceptions, e.g. KeyboardInterrupt
handler = None if on_abort is None else lambda a: on_abort(a, e)
result = create_readable(ugettext_noop("Failure."),
ugettext_noop("Unhandled exception: "
"%(error)s"),
result = create_readable(
ugettext_noop("Failure."),
ugettext_noop("Unhandled exception: %(error)s"),
error=unicode(e))
raise
except:
logger.exception("Failed activity %s" % unicode(act))
handler = None if on_abort is None else lambda a: on_abort(a, e)
act.finish(succeeded=False, result=result, event_handler=handler)
raise e
raise
else:
act.finish(succeeded=True, event_handler=on_commit)
......@@ -71,11 +78,11 @@ activity_code_separator = '.'
def has_prefix(activity_code, *prefixes):
"""Determine whether the activity code has the specified prefix.
E.g.: has_prefix('foo.bar.buz', 'foo.bar') == True
has_prefix('foo.bar.buz', 'foo', 'bar') == True
has_prefix('foo.bar.buz', 'foo.bar', 'buz') == True
has_prefix('foo.bar.buz', 'foo', 'bar', 'buz') == True
has_prefix('foo.bar.buz', 'foo', 'buz') == False
>>> assert has_prefix('foo.bar.buz', 'foo.bar')
>>> assert has_prefix('foo.bar.buz', 'foo', 'bar')
>>> assert has_prefix('foo.bar.buz', 'foo.bar', 'buz')
>>> assert has_prefix('foo.bar.buz', 'foo', 'bar', 'buz')
>>> assert not has_prefix('foo.bar.buz', 'foo', 'buz')
"""
equal = lambda a, b: a == b
act_code_parts = split_activity_code(activity_code)
......@@ -86,11 +93,11 @@ def has_prefix(activity_code, *prefixes):
def has_suffix(activity_code, *suffixes):
"""Determine whether the activity code has the specified suffix.
E.g.: has_suffix('foo.bar.buz', 'bar.buz') == True
has_suffix('foo.bar.buz', 'bar', 'buz') == True
has_suffix('foo.bar.buz', 'foo.bar', 'buz') == True
has_suffix('foo.bar.buz', 'foo', 'bar', 'buz') == True
has_suffix('foo.bar.buz', 'foo', 'buz') == False
>>> assert has_suffix('foo.bar.buz', 'bar.buz')
>>> assert has_suffix('foo.bar.buz', 'bar', 'buz')
>>> assert has_suffix('foo.bar.buz', 'foo.bar', 'buz')
>>> assert has_suffix('foo.bar.buz', 'foo', 'bar', 'buz')
>>> assert not has_suffix('foo.bar.buz', 'foo', 'buz')
"""
equal = lambda a, b: a == b
act_code_parts = split_activity_code(activity_code)
......@@ -197,6 +204,10 @@ class ActivityModel(TimeStampedModel):
DeprecationWarning, stacklevel=2)
value = create_readable(user_text_template="",
admin_text_template=value)
elif not hasattr(value, "to_dict"):
warn("Use HumanReadableObject.", DeprecationWarning, stacklevel=2)
value = create_readable(user_text_template="",
admin_text_template=unicode(value))
self.result_data = None if value is None else value.to_dict()
......@@ -362,8 +373,9 @@ class HumanReadableObject(object):
@classmethod
def create(cls, user_text_template, admin_text_template=None, **params):
return cls(user_text_template,
admin_text_template or user_text_template, params)
return cls(user_text_template=user_text_template,
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,
......@@ -408,14 +420,26 @@ create_readable = HumanReadableObject.create
class HumanReadableException(HumanReadableObject, Exception):
"""HumanReadableObject that is an Exception so can used in except clause.
"""
level = "error"
def send_message(self, request):
def __init__(self, level=None, *args, **kwargs):
super(HumanReadableException, self).__init__(*args, **kwargs)
if level is not None:
if hasattr(messages, level):
self.level = level
else:
raise ValueError(
"Level should be the name of an attribute of django."
"contrib.messages (and it should be callable with "
"(request, message)). Like 'error', 'warning'.")
else:
self.level = "error"
def send_message(self, request, level=None):
if request.user and request.user.is_superuser:
msg = self.get_admin_text()
else:
msg = self.get_user_text()
getattr(messages, self.level)(request, msg)
getattr(messages, level or self.level)(request, msg)
def humanize_exception(message, exception=None, level=None, **params):
......@@ -428,10 +452,10 @@ def humanize_exception(message, exception=None, level=None, **params):
Welcome!
"""
if level is not None:
exception.level = level
Ex = type("HumanReadable" + type(exception).__name__,
(HumanReadableException, type(exception)),
exception.__dict__)
return Ex.create(message, **params)
ex = Ex.create(message, **params)
if level:
ex.level = level
return ex
......@@ -1322,7 +1322,7 @@
"user_permissions": [
115
],
"password": "pbkdf2_sha256$10000$KIoeMs78MiOj$PnVXn3YJMehbOciBO32CMzqL0ZnQrzrdb7+b5dE13os=",
"password": "md5$qLN4mQMOrsUJ$f07129fd1a289a0afb4e09f7a6816a4f",
"email": "test@example.org",
"date_joined": "2013-09-04T15:29:49.914Z"
}
......
......@@ -529,24 +529,25 @@ class VmDetailTest(LoginMixin, TestCase):
def test_permitted_wake_up_wrong_state(self):
c = Client()
self.login(c, "user2")
with patch.object(WakeUpOperation, 'async') as mock_method:
with patch.object(WakeUpOperation, 'async') as mock_method, \
patch.object(Instance.WrongStateError, 'send_message') as wro:
inst = Instance.objects.get(pk=1)
mock_method.side_effect = inst.wake_up
inst.status = 'RUNNING'
inst.set_level(self.u2, 'owner')
with patch('dashboard.views.messages') as msg:
c.post("/dashboard/vm/1/op/wake_up/")
assert msg.error.called
inst = Instance.objects.get(pk=1)
self.assertEqual(inst.status, 'RUNNING') # mocked anyway
assert mock_method.called
assert wro.called
def test_permitted_wake_up(self):
c = Client()
self.login(c, "user2")
with patch.object(Instance, 'select_node', return_value=None):
with patch.object(WakeUpOperation, 'async') as new_wake_up:
with patch('vm.tasks.vm_tasks.wake_up.apply_async') as wuaa:
with patch.object(Instance, 'select_node', return_value=None), \
patch.object(WakeUpOperation, 'async') as new_wake_up, \
patch('vm.tasks.vm_tasks.wake_up.apply_async') as wuaa, \
patch.object(Instance.WrongStateError, 'send_message') as wro:
inst = Instance.objects.get(pk=1)
new_wake_up.side_effect = inst.wake_up
inst.get_remote_queue_name = Mock(return_value='test')
......@@ -559,6 +560,7 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(inst.status, 'RUNNING')
assert new_wake_up.called
assert wuaa.called
assert not wro.called
def test_unpermitted_wake_up(self):
c = Client()
......
......@@ -71,7 +71,7 @@ from .tables import (
NodeListTable, NodeVmListTable, TemplateListTable, LeaseListTable,
GroupListTable, UserKeyListTable
)
from common.models import HumanReadableObject
from common.models import HumanReadableObject, HumanReadableException
from vm.models import (
Instance, instance_activity, InstanceActivity, InstanceTemplate, Interface,
InterfaceTemplate, Lease, Node, NodeActivity, Trait,
......@@ -562,9 +562,13 @@ class OperationView(RedirectToLoginMixin, DetailView):
done = False
try:
task = self.get_op().async(user=request.user, **extra)
except HumanReadableException as e:
e.send_message(request)
logger.exception("Could not start operation")
result = e
except Exception as e:
messages.error(request, _('Could not start operation.'))
logger.exception(e)
logger.exception("Could not start operation")
result = e
else:
wait = self.wait_for_result
......@@ -575,6 +579,10 @@ class OperationView(RedirectToLoginMixin, DetailView):
except TimeoutError:
logger.debug("Result didn't arrive in %ss",
self.wait_for_result, exc_info=True)
except HumanReadableException as e:
e.send_message(request)
logger.exception(e)
result = e
except Exception as e:
messages.error(request, _('Operation failed.'))
logger.debug("Operation failed.", exc_info=True)
......
#!/bin/echo Usage: fab --list -f
import contextlib
import datetime
......@@ -9,14 +10,14 @@ from fabric.decorators import roles, parallel
env.roledefs['portal'] = ['localhost']
try:
from vm.models import Node
from storage.models import DataStore
from vm.models import Node as _Node
from storage.models import DataStore as _DataStore
except Exception as e:
print e
else:
env.roledefs['node'] = [unicode(n.host.ipv4)
for n in Node.objects.filter(enabled=True)]
env.roledefs['storage'] = [DataStore.objects.get().hostname]
for n in _Node.objects.filter(enabled=True)]
env.roledefs['storage'] = [_DataStore.objects.get().hostname]
def update_all():
......
[General]
LangCode=hu
MailingList=cloud@ik.bme.hu
PotBaseDir=./
ProjectID=circle-hu
TargetLangCode=hu
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-05-07 14:25+0200\n"
"POT-Creation-Date: 2014-07-29 12:56+0200\n"
"PO-Revision-Date: 2014-05-07 15:32+0200\n"
"Last-Translator: Mate Ory <orymate@ik.bme.hu>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n"
......@@ -17,105 +17,144 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 1.5\n"
#: dashboard/static/dashboard/dashboard.js:54
#: dashboard/static/dashboard/dashboard.js:68
#: static_collected/dashboard/dashboard.js:68
msgid "Select an option to proceed!"
msgstr "Válasszon a folytatáshoz."
#: dashboard/static/dashboard/dashboard.js:257
#: dashboard/static/dashboard/dashboard.js:304
#: dashboard/static/dashboard/dashboard.js:314
#: static_collected/dashboard/dashboard.js:257
#: static_collected/dashboard/dashboard.js:304
#: static_collected/dashboard/dashboard.js:314
msgid "No result"
msgstr ""
#: dashboard/static/dashboard/profile.js:18
#: static_collected/dashboard/profile.js:18
msgid "You have no permission to change this profile."
msgstr ""
#: dashboard/static/dashboard/profile.js:20
#: static_collected/dashboard/profile.js:20
msgid "Unknown error."
msgstr ""
#: dashboard/static/dashboard/vm-tour.js:20
#: static_collected/dashboard/vm-tour.js:20
msgid "Prev"
msgstr "Vissza"
#: dashboard/static/dashboard/vm-tour.js:22
#: static_collected/dashboard/vm-tour.js:22
msgid "Next"
msgstr "Tovább"
#: dashboard/static/dashboard/vm-tour.js:26
#: static_collected/dashboard/vm-tour.js:26
msgid "End tour"
msgstr "Befejezés"
#: dashboard/static/dashboard/vm-tour.js:33
#: static_collected/dashboard/vm-tour.js:33
msgid "Template Tutorial Tour"
msgstr "Sablon-kalauz"
#: dashboard/static/dashboard/vm-tour.js:34
#: static_collected/dashboard/vm-tour.js:34
msgid ""
"Welcome to the template tutorial. In this quick tour, we gonna show you how "
"to do the steps described above."
msgstr ""
"Üdvözöli a sablon-kalauz. A túra során bemutatjuk, hogyan végezze el "
"a fenti lépéseket."
"Üdvözöli a sablon-kalauz. A túra során bemutatjuk, hogyan végezze el a fenti "
"lépéseket."
#: dashboard/static/dashboard/vm-tour.js:35
#: static_collected/dashboard/vm-tour.js:35
msgid ""
"For the next tour step press the \"Next\" button or the right arrow (or "
"\"Back\" button/left arrow for the previous step)."
msgstr ""
"A következő lépéshez kattintson a \"Tovább\" gombra vagy használja "
"a nyílbillentyűket."
"A következő lépéshez kattintson a \"Tovább\" gombra vagy használja a "
"nyílbillentyűket."
#: dashboard/static/dashboard/vm-tour.js:36
#: static_collected/dashboard/vm-tour.js:36
msgid ""
"During the tour please don't try the functions because it may lead to "
"graphical glitches, however "
msgstr "A túra során még ne próbálja ki a bemutatott funkciókat."
#: dashboard/static/dashboard/vm-tour.js:45
#: static_collected/dashboard/vm-tour.js:45
msgid "Home tab"
msgstr "Kezdőoldal"
#: dashboard/static/dashboard/vm-tour.js:46
#: static_collected/dashboard/vm-tour.js:46
msgid ""
"In this tab you can tag your virtual machine and modify the name and "
"description."
msgstr ""
"Ezen a lapon címkéket adhat a virtuális géphez, vagy módosíthatja "
"a nevét, leírását."
"Ezen a lapon címkéket adhat a virtuális géphez, vagy módosíthatja a nevét, "
"leírását."
#: dashboard/static/dashboard/vm-tour.js:55
#: static_collected/dashboard/vm-tour.js:55
msgid "Resources tab"
msgstr "Erőforrások lap"
#: dashboard/static/dashboard/vm-tour.js:58
#: static_collected/dashboard/vm-tour.js:58
msgid ""
"On the resources tab you can edit the CPU/RAM options and add/remove disks!"
msgstr ""
"Az erőforrások lapon szerkesztheti a CPU/memória-beállításokat, valamint "
"hozzáadhat "
"és törölhet lemezeket."
"hozzáadhat és törölhet lemezeket."
#: dashboard/static/dashboard/vm-tour.js:68
#: static_collected/dashboard/vm-tour.js:68
msgid "Resources"
msgstr "Erőforrások"
#: dashboard/static/dashboard/vm-tour.js:69
#: static_collected/dashboard/vm-tour.js:69
msgid "CPU priority"
msgstr "CPU prioritás"
#: dashboard/static/dashboard/vm-tour.js:69
#: static_collected/dashboard/vm-tour.js:69
msgid "higher is better"
msgstr "a nagyobb érték a jobb"
#: dashboard/static/dashboard/vm-tour.js:70
#: static_collected/dashboard/vm-tour.js:70
msgid "CPU count"
msgstr "CPU-k száma"
#: dashboard/static/dashboard/vm-tour.js:70
#: static_collected/dashboard/vm-tour.js:70
msgid "number of CPU cores."
msgstr "A CPU-magok száma."
#: dashboard/static/dashboard/vm-tour.js:71
#: static_collected/dashboard/vm-tour.js:71
msgid "RAM amount"
msgstr "RAM mennyiség"
#: dashboard/static/dashboard/vm-tour.js:71
#: static_collected/dashboard/vm-tour.js:71
msgid "amount of RAM."
msgstr "a memória mennyisége."
#: dashboard/static/dashboard/vm-tour.js:81
#: static_collected/dashboard/vm-tour.js:81
msgid "Disks"
msgstr "Lemezek"
#: dashboard/static/dashboard/vm-tour.js:82
#: static_collected/dashboard/vm-tour.js:82
msgid ""
"You can add empty disks, download new ones and remove existing ones here."
msgstr ""
......@@ -123,65 +162,75 @@ msgstr ""
"meglévőket."
#: dashboard/static/dashboard/vm-tour.js:92
#: static_collected/dashboard/vm-tour.js:92
msgid "Network tab"
msgstr "Hálózat lap"
#: dashboard/static/dashboard/vm-tour.js:93
#: static_collected/dashboard/vm-tour.js:93
msgid "You can add new network interfaces or remove existing ones here."
msgstr "Hozzáadhat új hálózati interfészeket, vagy törölheti a meglévőket."
#: dashboard/static/dashboard/vm-tour.js:102
#: static_collected/dashboard/vm-tour.js:102
msgid "Deploy"
msgstr "Indítás"
#: dashboard/static/dashboard/vm-tour.js:105
#: static_collected/dashboard/vm-tour.js:105
msgid "Deploy the virtual machine."
msgstr "A virtuális gép elindítása."
#: dashboard/static/dashboard/vm-tour.js:110
#: static_collected/dashboard/vm-tour.js:110
msgid "Connect"
msgstr "Csatlakozás"
#: dashboard/static/dashboard/vm-tour.js:113
#: static_collected/dashboard/vm-tour.js:113
msgid "Use the connection string or connect with your choice of client!"
msgstr "Használja a megadott parancsot, vagy kedvenc kliensét."
#: dashboard/static/dashboard/vm-tour.js:120
#: static_collected/dashboard/vm-tour.js:120
msgid "Customize the virtual machine"
msgstr "Szabja testre a gépet"
#: dashboard/static/dashboard/vm-tour.js:121
#: static_collected/dashboard/vm-tour.js:121
msgid ""
"After you have connected to the virtual machine do your modifications then "
"log off."
msgstr ""
"Miután csatlakozott, végezze el a szükséges módosításokat, majd "
"jelentkezzen ki."
"Miután csatlakozott, végezze el a szükséges módosításokat, majd jelentkezzen "
"ki."
#: dashboard/static/dashboard/vm-tour.js:126
#: static_collected/dashboard/vm-tour.js:126
msgid "Save as"
msgstr "Mentés sablonként"
#: dashboard/static/dashboard/vm-tour.js:129
#: static_collected/dashboard/vm-tour.js:129
msgid ""
"Press the \"Save as template\" button and wait until the activity finishes."
msgstr ""
"Kattintson a „mentés sablonként” gombra, majd várjon, amíg a lemez "
"mentése elkészül."
"Kattintson a „mentés sablonként” gombra, majd várjon, amíg a lemez mentése "
"elkészül."
#: dashboard/static/dashboard/vm-tour.js:135
#: static_collected/dashboard/vm-tour.js:135
msgid "Finish"
msgstr "Befejezés"
#: dashboard/static/dashboard/vm-tour.js:138
#: static_collected/dashboard/vm-tour.js:138
msgid ""
"This is the last message, if something is not clear you can do the the tour "
"again!"
msgstr ""
"A túra véget ért. Ha valami nem érthető, újrakezdheti az "
"útmutatót."
msgstr "A túra véget ért. Ha valami nem érthető, újrakezdheti az útmutatót."
#: network/static/js/host.js:10
#: network/static/js/host.js:10 static_collected/js/host.js:10
msgid ""
"Are you sure you want to remove host group <strong>\"%(group)s\"</strong> "
"from <strong>\"%(host)s\"</strong>?"
......@@ -189,19 +238,172 @@ msgstr ""
"Biztosan törli a(z)<strong>„%(host)s”</strong> gépet a(z) "
"<strong>„%(group)s”</strong> gépcsoportból?"
#: network/static/js/host.js:13
#: network/static/js/host.js:13 static_collected/js/host.js:13
msgid "Are you sure you want to delete this rule?"
msgstr "Biztosan törli ezt a szabályt?"
#: network/static/js/host.js:20 network/static/js/switch-port.js:14
#: static_collected/admin/js/admin/DateTimeShortcuts.js:95
#: static_collected/admin/js/admin/DateTimeShortcuts.js:208
#: static_collected/js/host.js:20 static_collected/js/switch-port.js:14
msgid "Cancel"
msgstr "Mégsem"
#: network/static/js/host.js:25 network/static/js/switch-port.js:19
#: static_collected/admin/js/SelectFilter2.js:69
#: static_collected/js/host.js:25 static_collected/js/switch-port.js:19
msgid "Remove"
msgstr "Eltávolítás"
#: network/static/js/switch-port.js:8
#: network/static/js/switch-port.js:8 static_collected/js/switch-port.js:8
msgid "Are you sure you want to delete this device?"
msgstr "Biztosan törli ezt az eszközt?"
#: static_collected/admin/js/SelectFilter2.js:45
#, c-format
msgid "Available %s"
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:46
#, c-format
msgid ""
"This is the list of available %s. You may choose some by selecting them in "
"the box below and then clicking the \"Choose\" arrow between the two boxes."
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:53
#, c-format
msgid "Type into this box to filter down the list of available %s."
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:57
msgid "Filter"
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:61
msgid "Choose all"
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:61
#, c-format
msgid "Click to choose all %s at once."
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:67
msgid "Choose"
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:75
#, c-format
msgid "Chosen %s"
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:76
#, c-format
msgid ""
"This is the list of chosen %s. You may remove some by selecting them in the "
"box below and then clicking the \"Remove\" arrow between the two boxes."
msgstr ""
#: static_collected/admin/js/SelectFilter2.js:80
#, fuzzy
msgid "Remove all"
msgstr "Eltávolítás"
#: static_collected/admin/js/SelectFilter2.js:80
#, c-format
msgid "Click to remove all chosen %s at once."
msgstr ""
#: static_collected/admin/js/actions.js:18
#: static_collected/admin/js/actions.min.js:1
msgid "%(sel)s of %(cnt)s selected"
msgid_plural "%(sel)s of %(cnt)s selected"
msgstr[0] ""
msgstr[1] ""
#: static_collected/admin/js/actions.js:109
#: static_collected/admin/js/actions.min.js:5
msgid ""
"You have unsaved changes on individual editable fields. If you run an "
"action, your unsaved changes will be lost."
msgstr ""
#: static_collected/admin/js/actions.js:121
#: static_collected/admin/js/actions.min.js:5
msgid ""
"You have selected an action, but you haven't saved your changes to "
"individual fields yet. Please click OK to save. You'll need to re-run the "
"action."
msgstr ""
#: static_collected/admin/js/actions.js:123
#: static_collected/admin/js/actions.min.js:6
msgid ""
"You have selected an action, and you haven't made any changes on individual "
"fields. You're probably looking for the Go button rather than the Save "
"button."
msgstr ""
#: static_collected/admin/js/calendar.js:8
msgid ""
"January February March April May June July August September October November "
"December"
msgstr ""
#: static_collected/admin/js/calendar.js:9
msgid "S M T W T F S"
msgstr ""
#: static_collected/admin/js/collapse.js:8
#: static_collected/admin/js/collapse.js:19
#: static_collected/admin/js/collapse.min.js:1
msgid "Show"
msgstr ""
#: static_collected/admin/js/collapse.js:16
#: static_collected/admin/js/collapse.min.js:1
msgid "Hide"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:52
#: static_collected/admin/js/admin/DateTimeShortcuts.js:88
msgid "Now"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:56
msgid "Clock"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:84
msgid "Choose a time"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:89
msgid "Midnight"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:90
msgid "6 a.m."
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:91
msgid "Noon"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:148
#: static_collected/admin/js/admin/DateTimeShortcuts.js:201
msgid "Today"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:152
msgid "Calendar"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:199
msgid "Yesterday"
msgstr ""
#: static_collected/admin/js/admin/DateTimeShortcuts.js:203
msgid "Tomorrow"
msgstr ""
<!--
Collection name attribute represents the name of the menu, e.g., to use menu "File" use "file" or "Help" use "help". You can add new menus.
If you type a relative script file beware the this script is located in $KDEHOME/share/apps/applicationname/
The following example adds an action with the text "Export..." into the "File" menu
<KrossScripting>
<collection name="file" text="File" comment="File menu">
<script name="export" text="Export..." comment="Export content" file="export.py" />
</collection>
</KrossScripting>
-->
......@@ -41,7 +41,7 @@ from model_utils.models import TimeStampedModel, StatusModel
from taggit.managers import TaggableManager
from acl.models import AclBase
from common.models import create_readable
from common.models import create_readable, HumanReadableException
from common.operations import OperatedMixin
from ..tasks import vm_tasks, agent_tasks
from .activity import (ActivityInProgressError, instance_activity,
......@@ -276,28 +276,26 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
verbose_name = _('instance')
verbose_name_plural = _('instances')
class InstanceDestroyedError(Exception):
class InstanceError(HumanReadableException):
def __init__(self, instance, message=None):
if message is None:
message = ("The instance (%s) has already been destroyed."
% instance)
def __init__(self, instance, params=None, level=None, **kwargs):
kwargs.update(params or {})
self.instance = kwargs["instance"] = instance
super(Instance.InstanceError, self).__init__(
level, self.message, self.message, kwargs)
Exception.__init__(self, message)
class InstanceDestroyedError(InstanceError):
message = ugettext_noop(
"Instance %(instance)s has already been destroyed.")
self.instance = instance
class WrongStateError(InstanceError):
message = ugettext_noop(
"Current state (%(state)s) of instance %(instance)s is "
"inappropriate for the invoked operation.")
class WrongStateError(Exception):
def __init__(self, instance, message=None):
if message is None:
message = ("The instance's current state (%s) is "
"inappropriate for the invoked operation."
% instance.status)
Exception.__init__(self, message)
self.instance = instance
def __init__(self, instance, params=None, **kwargs):
super(Instance.WrongStateError, self).__init__(
instance, params, state=instance.status)
def __unicode__(self):
parts = (self.name, "(" + str(self.id) + ")")
......
......@@ -26,7 +26,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from celery.exceptions import TimeLimitExceeded
from common.models import create_readable
from common.models import create_readable, humanize_exception
from common.operations import Operation, register_operation
from .tasks.local_tasks import (
abortable_async_instance_operation, abortable_async_node_operation,
......@@ -45,6 +45,8 @@ class InstanceOperation(Operation):
async_operation = abortable_async_instance_operation
host_cls = Instance
concurrency_check = True
accept_states = None
deny_states = None
def __init__(self, instance):
super(InstanceOperation, self).__init__(subject=instance)
......@@ -53,11 +55,26 @@ class InstanceOperation(Operation):
def check_precond(self):
if self.instance.destroyed_at:
raise self.instance.InstanceDestroyedError(self.instance)
if self.accept_states:
if self.instance.status not in self.accept_states:
logger.debug("precond failed for %s: %s not in %s",
unicode(self.__class__),
unicode(self.instance.status),
unicode(self.accept_states))
raise self.instance.WrongStateError(self.instance)
if self.deny_states:
if self.instance.status in self.deny_states:
logger.debug("precond failed for %s: %s in %s",
unicode(self.__class__),
unicode(self.instance.status),
unicode(self.accept_states))
raise self.instance.WrongStateError(self.instance)
def check_auth(self, user):
if not self.instance.has_level(user, self.acl_level):
raise PermissionDenied("%s doesn't have the required ACL level." %
user)
raise humanize_exception(ugettext_noop(
"%(acl_level)s level is required for this operation."),
PermissionDenied(), acl_level=self.acl_level)
super(InstanceOperation, self).check_auth(user=user)
......@@ -94,6 +111,7 @@ class AddInterfaceOperation(InstanceOperation):
description = _("Add a new network interface for the specified VLAN to "
"the VM.")
required_perms = ()
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
def rollback(self, net, activity):
with activity.sub_activity(
......@@ -102,14 +120,11 @@ class AddInterfaceOperation(InstanceOperation):
net.destroy()
net.delete()
def check_precond(self):
super(AddInterfaceOperation, self).check_precond()
if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
raise self.instance.WrongStateError(self.instance)
def _operation(self, activity, user, system, vlan, managed=None):
if not vlan.has_level(user, 'user'):
raise PermissionDenied()
raise humanize_exception(ugettext_noop(
"User acces to vlan %(vlan)s is required."),
PermissionDenied(), vlan=vlan)
if managed is None:
managed = vlan.managed
......@@ -141,11 +156,7 @@ class CreateDiskOperation(InstanceOperation):
name = _("create disk")
description = _("Create empty disk for the VM.")
required_perms = ('storage.create_empty_disk', )
def check_precond(self):
super(CreateDiskOperation, self).check_precond()
if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
def _operation(self, user, size, activity, name=None):
from storage.models import Disk
......@@ -183,11 +194,7 @@ class DownloadDiskOperation(InstanceOperation):
abortable = True
has_percentage = True
required_perms = ('storage.download_disk', )
def check_precond(self):
super(DownloadDiskOperation, self).check_precond()
if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
def _operation(self, user, url, task, activity, name=None):
activity.result = url
......@@ -218,11 +225,7 @@ class DeployOperation(InstanceOperation):
name = _("deploy")
description = _("Deploy new virtual machine with network.")
required_perms = ()
def check_precond(self):
super(DeployOperation, self).check_precond()
if self.instance.status in ['RUNNING', 'SUSPENDED']:
raise self.instance.WrongStateError(self.instance)
deny_states = ('SUSPENDED', 'RUNNING')
def is_preferred(self):
return self.instance.status in (self.instance.STATUS.STOPPED,
......@@ -323,6 +326,7 @@ class MigrateOperation(InstanceOperation):
name = _("migrate")
description = _("Live migrate running VM to another node.")
required_perms = ()
accept_states = ('RUNNING', )
def rollback(self, activity):
with activity.sub_activity(
......@@ -330,11 +334,6 @@ class MigrateOperation(InstanceOperation):
"redeploy network (rollback)")):
self.instance.deploy_net()
def check_precond(self):
super(MigrateOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
def check_auth(self, user):
if not user.is_superuser:
raise PermissionDenied()
......@@ -384,11 +383,7 @@ class RebootOperation(InstanceOperation):
name = _("reboot")
description = _("Reboot virtual machine with Ctrl+Alt+Del signal.")
required_perms = ()
def check_precond(self):
super(RebootOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('RUNNING', )
def _operation(self, timeout=5):
self.instance.reboot_vm(timeout=timeout)
......@@ -403,11 +398,7 @@ class RemoveInterfaceOperation(InstanceOperation):
name = _("remove interface")
description = _("Remove the specified network interface from the VM.")
required_perms = ()
def check_precond(self):
super(RemoveInterfaceOperation, self).check_precond()
if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
def _operation(self, activity, user, system, interface):
if self.instance.is_running:
......@@ -428,11 +419,7 @@ class RemoveDiskOperation(InstanceOperation):
name = _("remove disk")
description = _("Remove the specified disk from the VM.")
required_perms = ()
def check_precond(self):
super(RemoveDiskOperation, self).check_precond()
if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
def _operation(self, activity, user, system, disk):
if self.instance.is_running and disk.type not in ["iso"]:
......@@ -450,11 +437,7 @@ class ResetOperation(InstanceOperation):
name = _("reset")
description = _("Reset virtual machine (reset button).")
required_perms = ()
def check_precond(self):
super(ResetOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('RUNNING', )
def _operation(self, timeout=5):
self.instance.reset_vm(timeout=timeout)
......@@ -473,6 +456,7 @@ class SaveAsTemplateOperation(InstanceOperation):
""")
abortable = True
required_perms = ('vm.create_template', )
accept_states = ('RUNNING', 'PENDING', 'STOPPED')
def is_preferred(self):
return (self.instance.is_base and
......@@ -493,11 +477,6 @@ class SaveAsTemplateOperation(InstanceOperation):
for disk in self.disks:
disk.destroy()
def check_precond(self):
super(SaveAsTemplateOperation, self).check_precond()
if self.instance.status not in ['RUNNING', 'PENDING', 'STOPPED']:
raise self.instance.WrongStateError(self.instance)
def _operation(self, activity, user, system, timeout=300, name=None,
with_shutdown=True, task=None, **kwargs):
if with_shutdown:
......@@ -567,11 +546,7 @@ class ShutdownOperation(InstanceOperation):
description = _("Shutdown virtual machine with ACPI signal.")
abortable = True
required_perms = ()
def check_precond(self):
super(ShutdownOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('RUNNING', )
def on_commit(self, activity):
activity.resultant_state = 'STOPPED'
......@@ -591,11 +566,7 @@ class ShutOffOperation(InstanceOperation):
name = _("shut off")
description = _("Shut off VM (plug-out).")
required_perms = ()
def check_precond(self):
super(ShutOffOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('RUNNING', )
def on_commit(self, activity):
activity.resultant_state = 'STOPPED'
......@@ -623,16 +594,12 @@ class SleepOperation(InstanceOperation):
name = _("sleep")
description = _("Suspend virtual machine with memory dump.")
required_perms = ()
accept_states = ('RUNNING', )
def is_preferred(self):
return (not self.instance.is_base and
self.instance.status == self.instance.STATUS.RUNNING)
def check_precond(self):
super(SleepOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
def on_abort(self, activity, error):
if isinstance(error, TimeLimitExceeded):
activity.resultant_state = None
......@@ -670,15 +637,11 @@ class WakeUpOperation(InstanceOperation):
Power on Virtual Machine and load its memory from dump.
""")
required_perms = ()
accept_states = ('SUSPENDED', )
def is_preferred(self):
return self.instance.status == self.instance.STATUS.SUSPENDED
def check_precond(self):
super(WakeUpOperation, self).check_precond()
if self.instance.status not in ['SUSPENDED']:
raise self.instance.WrongStateError(self.instance)
def on_abort(self, activity, error):
activity.resultant_state = 'ERROR'
......@@ -718,11 +681,6 @@ class RenewOperation(InstanceOperation):
required_perms = ()
concurrency_check = False
def check_precond(self):
super(RenewOperation, self).check_precond()
if self.instance.status == 'DESTROYED':
raise self.instance.WrongStateError(self.instance)
def _operation(self, lease=None):
(self.instance.time_of_suspend,
self.instance.time_of_delete) = self.instance.get_renew_times(lease)
......@@ -790,7 +748,8 @@ class FlushOperation(NodeOperation):
def check_auth(self, user):
if not user.is_superuser:
raise PermissionDenied()
raise humanize_exception(ugettext_noop(
"Superuser privileges are required."), PermissionDenied())
super(FlushOperation, self).check_auth(user=user)
......@@ -815,11 +774,7 @@ class ScreenshotOperation(InstanceOperation):
description = _("Get screenshot")
acl_level = "owner"
required_perms = ()
def check_precond(self):
super(ScreenshotOperation, self).check_precond()
if self.instance.status not in ['RUNNING']:
raise self.instance.WrongStateError(self.instance)
accept_states = ('RUNNING', )
def _operation(self):
return self.instance.get_screenshot(timeout=20)
......@@ -835,10 +790,13 @@ class RecoverOperation(InstanceOperation):
description = _("Recover virtual machine from destroyed state.")
acl_level = "owner"
required_perms = ('vm.recover', )
accept_states = ('DESTROYED', )
def check_precond(self):
if not self.instance.destroyed_at:
raise self.instance.WrongStateError(self.instance)
try:
super(RecoverOperation, self).check_precond()
except Instance.InstanceDestroyedError:
pass
def on_commit(self, activity):
activity.resultant_state = 'PENDING'
......@@ -862,11 +820,7 @@ class ResourcesOperation(InstanceOperation):
description = _("Change resources")
acl_level = "owner"
required_perms = ('vm.change_resources', )
def check_precond(self):
super(ResourcesOperation, self).check_precond()
if self.instance.status not in ["STOPPED", "PENDING"]:
raise self.instance.WrongStateError(self.instance)
accept_states = ('STOPPED', 'PENDING', )
def _operation(self, user, activity,
num_cores, ram_size, max_ram_size, priority):
......@@ -896,11 +850,7 @@ class PasswordResetOperation(InstanceOperation):
description = _("Password reset")
acl_level = "owner"
required_perms = ()
def check_precond(self):
super(PasswordResetOperation, self).check_precond()
if self.instance.status not in ["RUNNING"]:
raise self.instance.WrongStateError(self.instance)
accept_states = ('RUNNING', )
def _operation(self):
self.instance.pw = pwgen()
......
......@@ -210,7 +210,7 @@ class NodeTestCase(TestCase):
node.enabled = True
node.STATES = Node.STATES
self.assertEqual(Node.get_state(node), "ONLINE")
assert isinstance(Node.get_status_display(node), _("").__class__)
assert isinstance(Node.get_status_display(node), _("x").__class__)
class InstanceActivityTestCase(TestCase):
......
......@@ -21,7 +21,6 @@ kombu==3.0.15
logutils==0.3.3
MarkupSafe==0.21
netaddr==0.7.11
nose==1.3.1
pip-tools==0.3.4
psycopg2==2.5.2
Pygments==1.6
......
......@@ -3,3 +3,5 @@
coverage==3.7.1
factory-boy==2.3.1
mock==1.0.1
django-nose==1.2
nose==1.3.3
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