Commit 86c16fe8 by Kálmán Viktor

Merge branch 'master' into feature-new-tour

parents 96fe17ce ea3039d6
...@@ -214,6 +214,14 @@ class ActivityModel(TimeStampedModel): ...@@ -214,6 +214,14 @@ class ActivityModel(TimeStampedModel):
self.result_data = None if value is None else value.to_dict() self.result_data = None if value is None else value.to_dict()
@classmethod
def construct_activity_code(cls, code_suffix, sub_suffix=None):
code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix)
if sub_suffix:
return join_activity_code(code, sub_suffix)
else:
return code
@celery.task() @celery.task()
def compute_cached(method, instance, memcached_seconds, def compute_cached(method, instance, memcached_seconds,
......
import autocomplete_light import autocomplete_light
from django.contrib.auth.models import User
from django.utils.html import escape
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from .views import AclUpdateView from .views import AclUpdateView
from .models import Profile
class AclUserAutocomplete(autocomplete_light.AutocompleteGenericBase): def highlight(field, q, none_wo_match=True):
"""
>>> highlight('<b>Akkount Krokodil', 'kro', False)
u'&lt;b&gt;Akkount <span class="autocomplete-hl">Kro</span>kodil'
"""
if not field:
return None
try:
match = field.lower().index(q.lower())
except ValueError:
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:]))
elif none_wo_match:
return None
else:
return escape(field)
class AclUserGroupAutocomplete(autocomplete_light.AutocompleteGenericBase):
search_fields = ( search_fields = (
('^first_name', 'last_name', 'username', '^email', 'profile__org_id'), ('first_name', 'last_name', 'username', 'email', 'profile__org_id'),
('^name', 'groupprofile__org_id'), ('name', 'groupprofile__org_id'),
) )
autocomplete_js_attributes = {'placeholder': _("Name of group or user")} choice_html_format = (u'<span data-value="%s"><span style="display:none"'
choice_html_format = u'<span data-value="%s"><span>%s</span> %s</span>' u'>%s</span>%s</span>')
def choice_html(self, choice): def choice_displayed_text(self, choice):
try: q = unicode(self.request.GET.get('q', ''))
name = choice.get_full_name() name = highlight(unicode(choice), q, False)
except AttributeError: if isinstance(choice, User):
name = _('group') extra_fields = [highlight(choice.get_full_name(), q, False),
if name: highlight(choice.email, q)]
name = u'(%s)' % name try:
extra_fields.append(highlight(choice.profile.org_id, q))
except Profile.DoesNotExist:
pass
return '%s (%s)' % (name, ', '.join(f for f in extra_fields
if f))
else:
return _('%s (group)') % name
def choice_html(self, choice):
return self.choice_html_format % ( return self.choice_html_format % (
self.choice_value(choice), self.choice_label(choice), name) self.choice_value(choice), self.choice_label(choice),
self.choice_displayed_text(choice))
def choices_for_request(self): def choices_for_request(self):
user = self.request.user user = self.request.user
self.choices = (AclUpdateView.get_allowed_users(user), self.choices = (AclUpdateView.get_allowed_users(user),
AclUpdateView.get_allowed_groups(user)) AclUpdateView.get_allowed_groups(user))
return super(AclUserAutocomplete, self).choices_for_request() return super(AclUserGroupAutocomplete, self).choices_for_request()
def autocomplete_html(self):
html = []
for choice in self.choices_for_request():
html.append(self.choice_html(choice))
if not html:
html = self.empty_html_format % _('no matches found').capitalize()
return self.autocomplete_html_format % ''.join(html)
class AclUserAutocomplete(AclUserGroupAutocomplete):
def choices_for_request(self):
user = self.request.user
self.choices = (AclUpdateView.get_allowed_users(user), )
return super(AclUserGroupAutocomplete, self).choices_for_request()
autocomplete_light.register(AclUserGroupAutocomplete)
autocomplete_light.register(AclUserAutocomplete) autocomplete_light.register(AclUserAutocomplete)
...@@ -143,7 +143,7 @@ class VmCustomizeForm(forms.Form): ...@@ -143,7 +143,7 @@ class VmCustomizeForm(forms.Form):
self.template = kwargs.pop("template", None) self.template = kwargs.pop("template", None)
super(VmCustomizeForm, self).__init__(*args, **kwargs) super(VmCustomizeForm, self).__init__(*args, **kwargs)
if self.user.has_perm("vm_set_resouces"): if self.user.has_perm("vm.set_resources"):
self.allowed_fields = tuple(self.fields.keys()) self.allowed_fields = tuple(self.fields.keys())
# set displayed disk and network list # set displayed disk and network list
self.fields['disks'].queryset = self.template.disks.all() self.fields['disks'].queryset = self.template.disks.all()
...@@ -481,7 +481,7 @@ class TemplateForm(forms.ModelForm): ...@@ -481,7 +481,7 @@ class TemplateForm(forms.ModelForm):
else: else:
self.allowed_fields = ( self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags', 'name', 'access_method', 'description', 'system', 'tags',
'arch', 'lease') 'arch', 'lease', 'has_agent')
if (self.user.has_perm('vm.change_template_resources') if (self.user.has_perm('vm.change_template_resources')
or not self.instance.pk): or not self.instance.pk):
self.allowed_fields += tuple(set(self.fields.keys()) - self.allowed_fields += tuple(set(self.fields.keys()) -
...@@ -1055,9 +1055,29 @@ class UserCreationForm(OrgUserCreationForm): ...@@ -1055,9 +1055,29 @@ class UserCreationForm(OrgUserCreationForm):
return user return user
class AclUserAddForm(forms.Form): class AclUserOrGroupAddForm(forms.Form):
name = forms.CharField(widget=autocomplete_light.TextWidget( name = forms.CharField(widget=autocomplete_light.TextWidget(
'AclUserAutocomplete', attrs={'class': 'form-control'})) 'AclUserGroupAutocomplete',
autocomplete_js_attributes={'placeholder': _("Name of group or user")},
attrs={'class': 'form-control'}))
class TransferOwnershipForm(forms.Form):
name = forms.CharField(
widget=autocomplete_light.TextWidget(
'AclUserAutocomplete',
autocomplete_js_attributes={"placeholder": _("Name of user")},
attrs={'class': 'form-control'}),
label=_("E-mail address or identifier of user"))
class AddGroupMemberForm(forms.Form):
new_member = forms.CharField(
widget=autocomplete_light.TextWidget(
'AclUserAutocomplete',
autocomplete_js_attributes={"placeholder": _("Name of user")},
attrs={'class': 'form-control'}),
label=_("E-mail address or identifier of user"))
class UserKeyForm(forms.ModelForm): class UserKeyForm(forms.ModelForm):
......
...@@ -261,7 +261,7 @@ def get_or_create_profile(self): ...@@ -261,7 +261,7 @@ def get_or_create_profile(self):
Group.profile = property(get_or_create_profile) Group.profile = property(get_or_create_profile)
def create_profile(sender, user, request, **kwargs): def create_profile(user):
if not user.pk: if not user.pk:
return False return False
profile, created = Profile.objects.get_or_create(user=user) profile, created = Profile.objects.get_or_create(user=user)
...@@ -272,7 +272,11 @@ def create_profile(sender, user, request, **kwargs): ...@@ -272,7 +272,11 @@ def create_profile(sender, user, request, **kwargs):
logger.exception("Can't create user %s", unicode(user)) logger.exception("Can't create user %s", unicode(user))
return created return created
user_logged_in.connect(create_profile)
def create_profile_hook(sender, user, request, **kwargs):
return create_profile(user)
user_logged_in.connect(create_profile_hook)
if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'): if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
logger.debug("Register save_org_id to djangosaml2 pre_user_save") logger.debug("Register save_org_id to djangosaml2 pre_user_save")
......
...@@ -591,11 +591,15 @@ footer a, footer a:hover, footer a:visited { ...@@ -591,11 +591,15 @@ footer a, footer a:hover, footer a:visited {
width: 100px; width: 100px;
} }
#group-detail-user-table tr:last-child td:nth-child(2) {
text-align: left;
}
#group-detail-perm-header { #group-detail-perm-header {
margin-top: 25px; margin-top: 25px;
} }
textarea[name="list-new-namelist"] { textarea[name="new_members"] {
max-width: 500px; max-width: 500px;
min-height: 80px; min-height: 80px;
margin-bottom: 10px; margin-bottom: 10px;
...@@ -968,3 +972,12 @@ textarea[name="list-new-namelist"] { ...@@ -968,3 +972,12 @@ textarea[name="list-new-namelist"] {
#vm-activity-state { #vm-activity-state {
margin-bottom: 15px; margin-bottom: 15px;
} }
.autocomplete-hl {
color: #b20000;
font-weight: bold;
}
.hilight .autocomplete-hl {
color: orange;
}
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog"> <div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
{% if box_title and ajax_title %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{{ box_title }}</h4>
</div>
{% endif %}
<div class="modal-body"> <div class="modal-body">
{% if template %} {% if template %}
{% include template %} {% include template %}
......
...@@ -89,13 +89,12 @@ ...@@ -89,13 +89,12 @@
<tr> <tr>
<td><i class="fa fa-plus"></i></td> <td><i class="fa fa-plus"></i></td>
<td colspan="2"> <td colspan="2">
<input type="text" class="form-control" name="list-new-name" {{addmemberform.new_member}}
placeholder="{% trans "Name of user" %}">
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<textarea name="list-new-namelist" class="form-control" <textarea name="new_members" class="form-control"
placeholder="{% trans "Add multiple users at once (one identifier per line)." %}"></textarea> placeholder="{% trans "Add multiple users at once (one identifier per line)." %}"></textarea>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button> <button type="submit" class="btn btn-success">{% trans "Save" %}</button>
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
{{ form.req_traits|as_crispy_field }} {{ form.req_traits|as_crispy_field }}
{{ form.description|as_crispy_field }} {{ form.description|as_crispy_field }}
{{ form.system|as_crispy_field }} {{ form.system|as_crispy_field }}
{{ form.has_agent|as_crispy_field }}
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "External resources" %}</legend> <legend>{% trans "External resources" %}</legend>
......
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
{% endblocktrans %} {% endblocktrans %}
{% endif %} {% endif %}
{% if user == instance.owner or user.is_superuser %} {% if user == instance.owner or user.is_superuser %}
<span class="operation-wrapper">
<a href="{% url "dashboard.views.vm-transfer-ownership" instance.pk %}" <a href="{% url "dashboard.views.vm-transfer-ownership" instance.pk %}"
class="btn btn-link">{% trans "Transfer ownership..." %}</a> class="btn btn-link operation">{% trans "Transfer ownership..." %}</a>
</span>
{% endif %} {% endif %}
</p> </p>
<h3>{% trans "Permissions"|capfirst %}</h3> <h3>{% trans "Permissions"|capfirst %}</h3>
......
{% extends "dashboard/base.html" %}
{% load i18n %} {% load i18n %}
{% block content %} <div class="pull-right">
<div class="body-content"> <form action="{% url "dashboard.views.vm-transfer-ownership" pk=instance.pk %}" method="POST" style="max-width: 400px;">
<div class="panel panel-default"> {% csrf_token %}
<div class="panel-heading"> <label>
<h3 class="no-margin"> {{ form.name.label }}
{% trans "Transfer ownership" %} </label>
</h3> <div class="input-group">
</div> {{form.name}}
<div class="panel-body"> <div class="input-group-btn">
<div class="pull-right"> <input type="submit" value="{% trans "Save" %}" class="btn btn-primary">
<form action="" method="POST">
{% csrf_token %}
<label>
{% trans "E-mail address or identifier of user" %}:
<input name="name">
</label>
<input type="submit">
</form>
</div> </div>
</div> </div>
</div> </form>
{% endblock %} </div>
...@@ -1134,7 +1134,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1134,7 +1134,7 @@ class GroupDetailTest(LoginMixin, TestCase):
c = Client() c = Client()
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'}) str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group, self.assertEqual(user_in_group,
self.g1.user_set.count()) self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1144,7 +1144,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1144,7 +1144,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'user3') self.login(c, 'user3')
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'}) str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group, self.g1.user_set.count()) self.assertEqual(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
...@@ -1153,7 +1153,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1153,7 +1153,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'superuser') self.login(c, 'superuser')
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'}) str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group + 1, self.g1.user_set.count()) self.assertEqual(user_in_group + 1, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1162,7 +1162,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1162,7 +1162,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'user0') self.login(c, 'user0')
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'}) str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group + 1, self.g1.user_set.count()) self.assertEqual(user_in_group + 1, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1172,7 +1172,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1172,7 +1172,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nuser2'}) {'new_members': 'user1\r\nuser2'})
self.assertEqual(user_in_group + 2, self.g1.user_set.count()) self.assertEqual(user_in_group + 2, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1182,7 +1182,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1182,7 +1182,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nnoname\r\nuser2'}) {'new_members': 'user1\r\nnoname\r\nuser2'})
self.assertEqual(user_in_group + 2, self.g1.user_set.count()) self.assertEqual(user_in_group + 2, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1192,7 +1192,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1192,7 +1192,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nuser2'}) {'new_members': 'user1\r\nuser2'})
self.assertEqual(user_in_group, self.g1.user_set.count()) self.assertEqual(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
...@@ -1201,7 +1201,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1201,7 +1201,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nuser2'}) {'new_members': 'user1\r\nuser2'})
self.assertEqual(user_in_group, self.g1.user_set.count()) self.assertEqual(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1471,8 +1471,8 @@ class TransferOwnershipViewTest(LoginMixin, TestCase): ...@@ -1471,8 +1471,8 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
c2 = self.u2.notification_set.count() c2 = self.u2.notification_set.count()
c = Client() c = Client()
self.login(c, 'user2') self.login(c, 'user2')
response = c.post('/dashboard/vm/1/tx/') response = c.post('/dashboard/vm/1/tx/', {'name': 'userx'})
assert response.status_code == 400 assert response.status_code == 403
self.assertEqual(self.u2.notification_set.count(), c2) self.assertEqual(self.u2.notification_set.count(), c2)
def test_owned_offer(self): def test_owned_offer(self):
......
...@@ -70,9 +70,10 @@ from .forms import ( ...@@ -70,9 +70,10 @@ from .forms import (
UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm, UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm,
VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm, VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm,
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm, CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm, TraitsForm, RawDataForm, GroupPermissionForm, AclUserOrGroupAddForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm, VmResourcesForm, VmAddInterfaceForm, VmListSearchForm,
TemplateListSearchForm, ConnectCommandForm TemplateListSearchForm, ConnectCommandForm,
TransferOwnershipForm, AddGroupMemberForm
) )
from .tables import ( from .tables import (
...@@ -90,7 +91,7 @@ from vm.models import ( ...@@ -90,7 +91,7 @@ from vm.models import (
from storage.models import Disk from storage.models import Disk
from firewall.models import Vlan, Host, Rule from firewall.models import Vlan, Host, Rule
from .models import (Favourite, Profile, GroupProfile, FutureMember, from .models import (Favourite, Profile, GroupProfile, FutureMember,
ConnectCommand) ConnectCommand, create_profile)
from .store_api import Store, NoStoreException, NotOkException from .store_api import Store, NoStoreException, NotOkException
...@@ -393,7 +394,7 @@ class VmDetailView(CheckedDetailView): ...@@ -393,7 +394,7 @@ class VmDetailView(CheckedDetailView):
).all() ).all()
context['acl'] = AclUpdateView.get_acl_data( context['acl'] = AclUpdateView.get_acl_data(
instance, self.request.user, 'dashboard.views.vm-acl') instance, self.request.user, 'dashboard.views.vm-acl')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
context['os_type_icon'] = instance.os_type.replace("unknown", context['os_type_icon'] = instance.os_type.replace("unknown",
"question") "question")
# ipv6 infos # ipv6 infos
...@@ -1285,7 +1286,8 @@ class GroupDetailView(CheckedDetailView): ...@@ -1285,7 +1286,8 @@ class GroupDetailView(CheckedDetailView):
context['acl'] = AclUpdateView.get_acl_data( context['acl'] = AclUpdateView.get_acl_data(
self.object.profile, self.request.user, self.object.profile, self.request.user,
'dashboard.views.group-acl') 'dashboard.views.group-acl')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
context['addmemberform'] = AddGroupMemberForm()
context['group_profile_form'] = GroupProfileUpdate.get_form_object( context['group_profile_form'] = GroupProfileUpdate.get_form_object(
self.request, self.object.profile) self.request, self.object.profile)
...@@ -1302,17 +1304,15 @@ class GroupDetailView(CheckedDetailView): ...@@ -1302,17 +1304,15 @@ class GroupDetailView(CheckedDetailView):
if request.POST.get('new_name'): if request.POST.get('new_name'):
return self.__set_name(request) return self.__set_name(request)
if request.POST.get('list-new-name'): if request.POST.get('new_member'):
return self.__add_user(request) return self.__add_user(request)
if request.POST.get('list-new-namelist'): if request.POST.get('new_members'):
return self.__add_list(request) return self.__add_list(request)
if (request.POST.get('list-new-name') is not None) and \ return redirect(reverse_lazy("dashboard.views.group-detail",
(request.POST.get('list-new-namelist') is not None): kwargs={'pk': self.get_object().pk}))
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.get_object().pk}))
def __add_user(self, request): def __add_user(self, request):
name = request.POST['list-new-name'] name = request.POST['new_member']
self.__add_username(request, name) self.__add_username(request, name)
return redirect(reverse_lazy("dashboard.views.group-detail", return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
...@@ -1331,9 +1331,7 @@ class GroupDetailView(CheckedDetailView): ...@@ -1331,9 +1331,7 @@ class GroupDetailView(CheckedDetailView):
messages.warning(request, _('User "%s" not found.') % name) messages.warning(request, _('User "%s" not found.') % name)
def __add_list(self, request): def __add_list(self, request):
if not self.get_has_level()(request.user, 'operator'): userlist = request.POST.get('new_members').split('\r\n')
raise PermissionDenied()
userlist = request.POST.get('list-new-namelist').split('\r\n')
for line in userlist: for line in userlist:
self.__add_username(request, line) self.__add_username(request, line)
return redirect(reverse_lazy("dashboard.views.group-detail", return redirect(reverse_lazy("dashboard.views.group-detail",
...@@ -1720,7 +1718,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -1720,7 +1718,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
obj, self.request.user, 'dashboard.views.template-acl') obj, self.request.user, 'dashboard.views.template-acl')
context['disks'] = obj.disks.all() context['disks'] = obj.disks.all()
context['is_owner'] = obj.has_level(self.request.user, 'owner') context['is_owner'] = obj.has_level(self.request.user, 'owner')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
return context return context
def get_success_url(self): def get_success_url(self):
...@@ -2771,11 +2769,30 @@ class FavouriteView(TemplateView): ...@@ -2771,11 +2769,30 @@ class FavouriteView(TemplateView):
return HttpResponse("Added.") return HttpResponse("Added.")
class TransferOwnershipView(LoginRequiredMixin, DetailView): class TransferOwnershipView(CheckedDetailView, DetailView):
model = Instance model = Instance
template_name = 'dashboard/vm-detail/tx-owner.html'
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_modal.html']
else:
return ['dashboard/nojs-wrapper.html']
def get_context_data(self, *args, **kwargs):
context = super(TransferOwnershipView, self).get_context_data(
*args, **kwargs)
context['form'] = TransferOwnershipForm()
context.update({
'box_title': _("Transfer ownership"),
'ajax_title': True,
'template': "dashboard/vm-detail/tx-owner.html",
})
return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = TransferOwnershipForm(request.POST)
if not form.is_valid():
return self.get(request)
try: try:
new_owner = search_user(request.POST['name']) new_owner = search_user(request.POST['name'])
except User.DoesNotExist: except User.DoesNotExist:
...@@ -3274,6 +3291,7 @@ class UserCreationView(LoginRequiredMixin, PermissionRequiredMixin, ...@@ -3274,6 +3291,7 @@ class UserCreationView(LoginRequiredMixin, PermissionRequiredMixin,
self.get_group(group_pk) self.get_group(group_pk)
ret = super(UserCreationView, self).post(*args, **kwargs) ret = super(UserCreationView, self).post(*args, **kwargs)
if self.object: if self.object:
create_profile(self.object)
self.object.groups.add(self.group) self.object.groups.add(self.group)
return redirect( return redirect(
reverse('dashboard.views.group-detail', args=[group_pk])) reverse('dashboard.views.group-detail', args=[group_pk]))
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
from django.forms import ModelForm from django.forms import ModelForm
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
...@@ -56,8 +57,9 @@ class BlacklistItemForm(ModelForm): ...@@ -56,8 +57,9 @@ class BlacklistItemForm(ModelForm):
) )
), ),
FormActions( FormActions(
Submit('submit', 'Save changes'), Submit('submit', _('Save changes')),
LinkButton('back', 'Back', reverse_lazy('network.blacklist_list')) LinkButton('back', _("Back"),
reverse_lazy('network.blacklist_list'))
) )
) )
...@@ -77,8 +79,8 @@ class DomainForm(ModelForm): ...@@ -77,8 +79,8 @@ class DomainForm(ModelForm):
), ),
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _('Save')),
LinkButton('back', 'Back', reverse_lazy('network.domain_list')) LinkButton('back', _("Back"), reverse_lazy('network.domain_list'))
) )
) )
...@@ -91,15 +93,15 @@ class GroupForm(ModelForm): ...@@ -91,15 +93,15 @@ class GroupForm(ModelForm):
helper.layout = Layout( helper.layout = Layout(
Div( Div(
Fieldset( Fieldset(
'Identity', '',
'name', 'name',
'description', 'description',
'owner', 'owner',
), ),
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _('Save')),
LinkButton('back', 'Back', reverse_lazy('network.group_list')) LinkButton('back', _("Back"), reverse_lazy('network.group_list'))
) )
) )
...@@ -112,13 +114,13 @@ class HostForm(ModelForm): ...@@ -112,13 +114,13 @@ class HostForm(ModelForm):
helper.layout = Layout( helper.layout = Layout(
Div( Div(
Fieldset( Fieldset(
'Identity', '',
'hostname', 'hostname',
'reverse', 'reverse',
'mac', 'mac',
), ),
Fieldset( Fieldset(
'Network', _('Network'),
'vlan', 'vlan',
'ipv4', 'ipv4',
'ipv6', 'ipv6',
...@@ -126,7 +128,7 @@ class HostForm(ModelForm): ...@@ -126,7 +128,7 @@ class HostForm(ModelForm):
'external_ipv4', 'external_ipv4',
), ),
Fieldset( Fieldset(
'Information', _('Information'),
'description', 'description',
'location', 'location',
'comment', 'comment',
...@@ -134,8 +136,8 @@ class HostForm(ModelForm): ...@@ -134,8 +136,8 @@ class HostForm(ModelForm):
), ),
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _('Save')),
LinkButton('back', 'Back', reverse_lazy('network.host_list'))) LinkButton('back', _('Back'), reverse_lazy('network.host_list')))
) )
class Meta: class Meta:
...@@ -159,8 +161,8 @@ class RecordForm(ModelForm): ...@@ -159,8 +161,8 @@ class RecordForm(ModelForm):
) )
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _("Save")),
LinkButton('back', 'Back', reverse_lazy('network.record_list')) LinkButton('back', _("Back"), reverse_lazy('network.record_list'))
) )
) )
...@@ -173,7 +175,7 @@ class RuleForm(ModelForm): ...@@ -173,7 +175,7 @@ class RuleForm(ModelForm):
helper.layout = Layout( helper.layout = Layout(
Div( Div(
Fieldset( Fieldset(
'Identity', '',
'direction', 'direction',
'description', 'description',
'foreign_network', 'foreign_network',
...@@ -189,7 +191,7 @@ class RuleForm(ModelForm): ...@@ -189,7 +191,7 @@ class RuleForm(ModelForm):
'nat_external_ipv4', 'nat_external_ipv4',
), ),
Fieldset( Fieldset(
'External', _('External'),
'vlan', 'vlan',
'vlangroup', 'vlangroup',
'host', 'host',
...@@ -198,8 +200,8 @@ class RuleForm(ModelForm): ...@@ -198,8 +200,8 @@ class RuleForm(ModelForm):
) )
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _("Save")),
LinkButton('back', 'Back', reverse_lazy('network.rule_list')) LinkButton('back', _("Back"), reverse_lazy('network.rule_list'))
) )
) )
...@@ -219,8 +221,8 @@ class SwitchPortForm(ModelForm): ...@@ -219,8 +221,8 @@ class SwitchPortForm(ModelForm):
) )
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _("Save")),
LinkButton('back', 'Back', LinkButton('back', _("Back"),
reverse_lazy('network.switch_port_list')) reverse_lazy('network.switch_port_list'))
) )
) )
...@@ -234,41 +236,42 @@ class VlanForm(ModelForm): ...@@ -234,41 +236,42 @@ class VlanForm(ModelForm):
helper.layout = Layout( helper.layout = Layout(
Div( Div(
Fieldset( Fieldset(
'Identity', '',
'name', 'name',
'vid', 'vid',
'network_type', 'network_type',
'managed', 'managed',
), ),
Fieldset( Fieldset(
'IPv4', _('IPv4'),
'network4', 'network4',
'snat_to', 'snat_to',
'snat_ip', 'snat_ip',
'dhcp_pool', 'dhcp_pool',
), ),
Fieldset( Fieldset(
'IPv6', _('IPv6'),
'network6', 'network6',
'ipv6_template', 'ipv6_template',
'host_ipv6_prefixlen', 'host_ipv6_prefixlen',
), ),
Fieldset( Fieldset(
'Domain name service', _('Domain name service'),
'domain', 'domain',
'reverse_domain', 'reverse_domain',
), ),
Fieldset( Fieldset(
'Info', _('Info'),
'description', 'description',
'comment', 'comment',
'owner',
# 'created_at', # 'created_at',
# 'modified_at', # 'modified_at',
), ),
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _("Save")),
LinkButton('back', 'Back', reverse_lazy('network.vlan_list')) LinkButton('back', _("Back"), reverse_lazy('network.vlan_list'))
) )
) )
...@@ -289,8 +292,8 @@ class VlanGroupForm(ModelForm): ...@@ -289,8 +292,8 @@ class VlanGroupForm(ModelForm):
) )
), ),
FormActions( FormActions(
Submit('submit', 'Save'), Submit('submit', _("Save")),
LinkButton('back', 'Back', reverse_lazy( LinkButton('back', _("Back"), reverse_lazy(
'network.vlan_group_list')) 'network.vlan_group_list'))
) )
) )
......
...@@ -7,73 +7,77 @@ ...@@ -7,73 +7,77 @@
{% block content %} {% block content %}
<div class="page-header"> <div class="page-header">
<a href="{% url "network.host_delete" pk=host_pk%}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this host" %}</a> <a href="{% url "network.host_delete" pk=host_pk%}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this host" %}</a>
<h2>{{ form.hostname.value }}</h2> <h2>{{ form.hostname.value }}</h2>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
{% crispy form %} {% crispy form %}
</div>
<div class="col-md-6">
<div class="page-header">
<a href="{% url "network.rule_create" %}?host={{ host_pk }}" class="btn btn-success pull-right btn-xs"><i class="fa fa-plus-circle"></i> {% trans "Add new rule" %}</a>
<h3>{% trans "Rules" %}</h3>
</div> </div>
<div class="col-md-6"> {% if rule_list.data.data.count > 0 %}
<div class="page-header"> {% render_table rule_list %}
<a href="{% url "network.rule_create" %}?host={{ host_pk }}" class="btn btn-success pull-right btn-xs"><i class="fa fa-plus-circle"></i> {% trans "Add new rule" %}</a> {% else %}
<h3>{% trans "Rules" %}</h3> {% trans "No rules associated with this host." %}
</div> {% endif %}
{% if rule_list.data.data.count > 0 %}
{% render_table rule_list %}
{% else %}
{% trans "No rules associated with this host!" %}
{% endif %}
<div class="page-header"> <div class="page-header">
<h3>{% trans "Groups" %}</h3> <h3>{% trans "Groups" %}</h3>
</div>
{% if group_rule_list|length > 0 %}
{% for group in group_rule_list %}
<div>
<h4 id="{{ group.pk }}_group_pk">{{ group.name }}
<a href="{% url "network.remove_host_group" pk=host_pk group_pk=group.pk %}?from={{ request.path }}">
<i class="fa fa-times" style="vertical-align: middle;"></i></a>
<a href="{% url "network.group" group.pk %}">
<i class="fa fa-pencil" style="vertical-align: middle;"></i></a>
</h4>
</div> </div>
{% if group_rule_list|length > 0 %} {% endfor %}
{% for group in group_rule_list %} {% else %}
<div> {% trans "This host is not added to any host groups!" %}
<h4 id="{{ group.pk }}_group_pk">{{ group.name }} {% endif %}
<a href="{% url "network.remove_host_group" pk=host_pk group_pk=group.pk %}?from={{ request.path }}">
<i class="fa fa-times" style="vertical-align: middle;"></i></a>
<a href="{% url "network.group" group.pk %}">
<i class="fa fa-pencil" style="vertical-align: middle;"></i></a>
</h4>
</div>
{% endfor %}
{% else %}
{% trans "This host is not added to any host groups!" %}
{% endif %}
<div class="page-header"> <div class="page-header">
<h3>{% trans "Add host group" %}</h3> <h3>{% trans "Add host group" %}</h3>
</div> </div>
{% if not_used_groups|length == 0 %} {% if not_used_groups|length == 0 %}
No more groups to add! {% trans "No more groups to add" %}
{% else %} {% else %}
<form action="{% url "network.add_host_group" pk=host_pk %}" method="POST"> <form action="{% url "network.add_host_group" pk=host_pk %}" method="POST">
{% csrf_token %} {% csrf_token %}
<div class="input-group"> <div class="input-group">
<select name="group" id="add_group" class="form-control"> <select name="group" id="add_group" class="form-control">
{% for rest in not_used_groups %} {% for rest in not_used_groups %}
<option value="{{ rest.pk }}">{{ rest }}</option> <option value="{{ rest.pk }}">{{ rest }}</option>
{% endfor %} {% endfor %}
</select> </select>
<div class="input-group-btn"> <div class="input-group-btn">
<input type="submit" value="{% trans "Add group" %}" class="btn btn-default"></input> <input type="submit" value="{% trans "Add group" %}" class="btn btn-default"></input>
</div> </div>
</div><!-- input-group --> </div><!-- input-group -->
</form> </form>
{% endif %} {% endif %}
<div class="page-header"> <div class="page-header">
<a href="{% url "network.record_create" %}?host={{ host_pk }}"
class="btn btn-xs btn-success pull-right">
<i class="fa fa-plus-circle"></i>
{% trans "Add new CNAME record" %}
</a>
<h3>{% trans "Records" %}</h3> <h3>{% trans "Records" %}</h3>
</div> </div>
{% render_table records_table %} {% render_table records_table %}
</div><!-- col-sm-5 -->
</div><!-- col-sm-5 -->
</div><!-- row --> </div><!-- row -->
{% endblock %} {% endblock %}
{% block extra_etc %} {% block extra_etc %}
<script src="{% static "js/host.js" %}"></script> <script src="{% static "js/host.js" %}"></script>
{% endblock %} {% endblock %}
...@@ -34,6 +34,7 @@ from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm, ...@@ -34,6 +34,7 @@ from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm,
BlacklistItemForm, RuleForm, VlanGroupForm, SwitchPortForm) BlacklistItemForm, RuleForm, VlanGroupForm, SwitchPortForm)
from django.contrib import messages from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
...@@ -42,25 +43,14 @@ from operator import itemgetter ...@@ -42,25 +43,14 @@ from operator import itemgetter
from itertools import chain from itertools import chain
import json import json
from dashboard.views import AclUpdateView from dashboard.views import AclUpdateView
from dashboard.forms import AclUserAddForm from dashboard.forms import AclUserOrGroupAddForm
class SuccessMessageMixin(FormMixin): class InitialOwnerMixin(FormMixin):
""" def get_initial(self):
Adds a success message on successful form submission. initial = super(InitialOwnerMixin, self).get_initial()
From django/contrib/messages/views.py@9a85ad89 initial['owner'] = self.request.user
""" return initial
success_message = ''
def form_valid(self, form):
response = super(SuccessMessageMixin, self).form_valid(form)
success_message = self.get_success_message(form.cleaned_data)
if success_message:
messages.success(self.request, success_message)
return response
def get_success_message(self, cleaned_data):
return self.success_message % cleaned_data
class IndexView(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): class IndexView(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
...@@ -190,7 +180,7 @@ class DomainDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -190,7 +180,7 @@ class DomainDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class DomainCreate(LoginRequiredMixin, SuperuserRequiredMixin, class DomainCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Domain model = Domain
template_name = "network/domain-create.html" template_name = "network/domain-create.html"
form_class = DomainForm form_class = DomainForm
...@@ -274,7 +264,7 @@ class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): ...@@ -274,7 +264,7 @@ class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
class GroupCreate(LoginRequiredMixin, SuperuserRequiredMixin, class GroupCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Group model = Group
template_name = "network/group-create.html" template_name = "network/group-create.html"
form_class = GroupForm form_class = GroupForm
...@@ -413,7 +403,7 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -413,7 +403,7 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class HostCreate(LoginRequiredMixin, SuperuserRequiredMixin, class HostCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Host model = Host
template_name = "network/host-create.html" template_name = "network/host-create.html"
form_class = HostForm form_class = HostForm
...@@ -497,7 +487,7 @@ class RecordDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -497,7 +487,7 @@ class RecordDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin, class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Record model = Record
template_name = "network/record-create.html" template_name = "network/record-create.html"
form_class = RecordForm form_class = RecordForm
...@@ -505,10 +495,23 @@ class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -505,10 +495,23 @@ class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin,
success_message = _(u'Successfully created record!') success_message = _(u'Successfully created record!')
def get_initial(self): def get_initial(self):
return { initial = super(RecordCreate, self).get_initial()
# 'owner': 1, initial['domain'] = self.request.GET.get('domain')
'domain': self.request.GET.get('domain'),
} host_pk = self.request.GET.get("host")
try:
host = Host.objects.get(pk=host_pk)
except (Host.DoesNotExist, ValueError):
host = None
if host:
initial.update({
'type': "CNAME",
'host': host,
'address': host.get_fqdn(),
})
return initial
class RecordDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): class RecordDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
...@@ -556,18 +559,19 @@ class RuleDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -556,18 +559,19 @@ class RuleDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class RuleCreate(LoginRequiredMixin, SuperuserRequiredMixin, class RuleCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Rule model = Rule
template_name = "network/rule-create.html" template_name = "network/rule-create.html"
form_class = RuleForm form_class = RuleForm
success_message = _(u'Successfully created rule!') success_message = _(u'Successfully created rule!')
def get_initial(self): def get_initial(self):
return { initial = super(RuleCreate, self).get_initial()
# 'owner': 1, initial.update({
'host': self.request.GET.get('host'), 'host': self.request.GET.get('host'),
'hostgroup': self.request.GET.get('hostgroup') 'hostgroup': self.request.GET.get('hostgroup')
} })
return initial
class RuleDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): class RuleDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
...@@ -660,14 +664,14 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -660,14 +664,14 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
context['vlan_vid'] = self.kwargs.get('vid') context['vlan_vid'] = self.kwargs.get('vid')
context['acl'] = AclUpdateView.get_acl_data( context['acl'] = AclUpdateView.get_acl_data(
self.object, self.request.user, 'network.vlan-acl') self.object, self.request.user, 'network.vlan-acl')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
return context return context
success_url = reverse_lazy('network.vlan_list') success_url = reverse_lazy('network.vlan_list')
class VlanCreate(LoginRequiredMixin, SuperuserRequiredMixin, class VlanCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Vlan model = Vlan
template_name = "network/vlan-create.html" template_name = "network/vlan-create.html"
form_class = VlanForm form_class = VlanForm
...@@ -747,7 +751,7 @@ class VlanGroupDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -747,7 +751,7 @@ class VlanGroupDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class VlanGroupCreate(LoginRequiredMixin, SuperuserRequiredMixin, class VlanGroupCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = VlanGroup model = VlanGroup
template_name = "network/vlan-group-create.html" template_name = "network/vlan-group-create.html"
form_class = VlanGroupForm form_class = VlanGroupForm
......
...@@ -367,7 +367,8 @@ class Disk(TimeStampedModel): ...@@ -367,7 +367,8 @@ class Disk(TimeStampedModel):
disk = cls.__create(user, params) disk = cls.__create(user, params)
disk.clean() disk.clean()
disk.save() disk.save()
logger.debug("Disk created: %s", params) logger.debug(u"Disk created from: %s",
unicode(params.get("base", "nobase")))
return disk return disk
@classmethod @classmethod
......
...@@ -24,7 +24,7 @@ from celery.signals import worker_ready ...@@ -24,7 +24,7 @@ from celery.signals import worker_ready
from celery.contrib.abortable import AbortableAsyncResult from celery.contrib.abortable import AbortableAsyncResult
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import CharField, ForeignKey from django.db.models import CharField, ForeignKey, BooleanField
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
...@@ -70,6 +70,8 @@ class InstanceActivity(ActivityModel): ...@@ -70,6 +70,8 @@ class InstanceActivity(ActivityModel):
help_text=_('Instance this activity works on.'), help_text=_('Instance this activity works on.'),
verbose_name=_('instance')) verbose_name=_('instance'))
resultant_state = CharField(blank=True, max_length=20, null=True) resultant_state = CharField(blank=True, max_length=20, null=True)
interruptible = BooleanField(default=False, help_text=_(
'Other activities can interrupt this one.'))
class Meta: class Meta:
app_label = 'vm' app_label = 'vm'
...@@ -91,24 +93,30 @@ class InstanceActivity(ActivityModel): ...@@ -91,24 +93,30 @@ class InstanceActivity(ActivityModel):
@classmethod @classmethod
def create(cls, code_suffix, instance, task_uuid=None, user=None, def create(cls, code_suffix, instance, task_uuid=None, user=None,
concurrency_check=True, readable_name=None, concurrency_check=True, readable_name=None,
resultant_state=None): resultant_state=None, interruptible=False):
readable_name = _normalize_readable_name(readable_name, code_suffix) readable_name = _normalize_readable_name(readable_name, code_suffix)
# Check for concurrent activities # Check for concurrent activities
active_activities = instance.activity_log.filter(finished__isnull=True) active_activities = instance.activity_log.filter(finished__isnull=True)
if concurrency_check and active_activities.exists(): if concurrency_check and active_activities.exists():
raise ActivityInProgressError.create(active_activities[0]) for i in active_activities:
if i.interruptible:
activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix) i.finish(False, result=ugettext_noop(
"Interrupted by other activity."))
else:
raise ActivityInProgressError.create(i)
activity_code = cls.construct_activity_code(code_suffix)
act = cls(activity_code=activity_code, instance=instance, parent=None, act = cls(activity_code=activity_code, instance=instance, parent=None,
resultant_state=resultant_state, started=timezone.now(), resultant_state=resultant_state, started=timezone.now(),
readable_name_data=readable_name.to_dict(), readable_name_data=readable_name.to_dict(),
task_uuid=task_uuid, user=user) task_uuid=task_uuid, user=user, interruptible=interruptible)
act.save() act.save()
return act return act
def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True, def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True,
readable_name=None, resultant_state=None): readable_name=None, resultant_state=None,
interruptible=False):
readable_name = _normalize_readable_name(readable_name, code_suffix) readable_name = _normalize_readable_name(readable_name, code_suffix)
# Check for concurrent activities # Check for concurrent activities
...@@ -119,7 +127,7 @@ class InstanceActivity(ActivityModel): ...@@ -119,7 +127,7 @@ class InstanceActivity(ActivityModel):
act = InstanceActivity( act = InstanceActivity(
activity_code=join_activity_code(self.activity_code, code_suffix), activity_code=join_activity_code(self.activity_code, code_suffix),
instance=self.instance, parent=self, instance=self.instance, parent=self,
resultant_state=resultant_state, resultant_state=resultant_state, interruptible=interruptible,
readable_name_data=readable_name.to_dict(), started=timezone.now(), readable_name_data=readable_name.to_dict(), started=timezone.now(),
task_uuid=task_uuid, user=self.user) task_uuid=task_uuid, user=self.user)
act.save() act.save()
...@@ -183,13 +191,14 @@ class InstanceActivity(ActivityModel): ...@@ -183,13 +191,14 @@ class InstanceActivity(ActivityModel):
@contextmanager @contextmanager
def sub_activity(self, code_suffix, on_abort=None, on_commit=None, def sub_activity(self, code_suffix, on_abort=None, on_commit=None,
readable_name=None, task_uuid=None, readable_name=None, task_uuid=None,
concurrency_check=True): concurrency_check=True, interruptible=False):
"""Create a transactional context for a nested instance activity. """Create a transactional context for a nested instance activity.
""" """
if not readable_name: if not readable_name:
warn("Set readable_name", stacklevel=3) warn("Set readable_name", stacklevel=3)
act = self.create_sub(code_suffix, task_uuid, concurrency_check, act = self.create_sub(code_suffix, task_uuid, concurrency_check,
readable_name=readable_name) readable_name=readable_name,
interruptible=interruptible)
return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit) return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit)
def get_operation(self): def get_operation(self):
......
...@@ -123,6 +123,10 @@ class VirtualMachineDescModel(BaseResourceConfigModel): ...@@ -123,6 +123,10 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
'format like "%s".') % 'format like "%s".') %
'Ubuntu 12.04 LTS Desktop amd64')) 'Ubuntu 12.04 LTS Desktop amd64'))
tags = TaggableManager(blank=True, verbose_name=_("tags")) tags = TaggableManager(blank=True, verbose_name=_("tags"))
has_agent = BooleanField(verbose_name=_('has agent'), default=True,
help_text=_(
'If the machine has agent installed, and '
'the manager should wait for its start.'))
class Meta: class Meta:
abstract = True abstract = True
...@@ -424,7 +428,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -424,7 +428,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
# prepare parameters # prepare parameters
common_fields = ['name', 'description', 'num_cores', 'ram_size', common_fields = ['name', 'description', 'num_cores', 'ram_size',
'max_ram_size', 'arch', 'priority', 'boot_menu', 'max_ram_size', 'arch', 'priority', 'boot_menu',
'raw_data', 'lease', 'access_method', 'system'] 'raw_data', 'lease', 'access_method', 'system',
'has_agent']
params = dict(template=template, owner=owner, pw=pwgen()) params = dict(template=template, owner=owner, pw=pwgen())
params.update([(f, getattr(template, f)) for f in common_fields]) params.update([(f, getattr(template, f)) for f in common_fields])
params.update(kwargs) # override defaults w/ user supplied values params.update(kwargs) # override defaults w/ user supplied values
......
...@@ -299,16 +299,20 @@ class DeployOperation(InstanceOperation): ...@@ -299,16 +299,20 @@ class DeployOperation(InstanceOperation):
"deploy network")): "deploy network")):
self.instance.deploy_net() self.instance.deploy_net()
try:
self.instance.renew(parent_activity=activity)
except:
pass
# Resume vm # Resume vm
with activity.sub_activity( with activity.sub_activity(
'booting', readable_name=ugettext_noop( 'booting', readable_name=ugettext_noop(
"boot virtual machine")): "boot virtual machine")):
self.instance.resume_vm(timeout=timeout) self.instance.resume_vm(timeout=timeout)
try: if self.instance.has_agent:
self.instance.renew(parent_activity=activity) activity.sub_activity('os_boot', readable_name=ugettext_noop(
except: "wait operating system loading"), interruptible=True)
pass
register_operation(DeployOperation) register_operation(DeployOperation)
...@@ -425,8 +429,11 @@ class RebootOperation(InstanceOperation): ...@@ -425,8 +429,11 @@ class RebootOperation(InstanceOperation):
required_perms = () required_perms = ()
accept_states = ('RUNNING', ) accept_states = ('RUNNING', )
def _operation(self, timeout=5): def _operation(self, activity, timeout=5):
self.instance.reboot_vm(timeout=timeout) self.instance.reboot_vm(timeout=timeout)
if self.instance.has_agent:
activity.sub_activity('os_boot', readable_name=ugettext_noop(
"wait operating system loading"), interruptible=True)
register_operation(RebootOperation) register_operation(RebootOperation)
...@@ -499,8 +506,11 @@ class ResetOperation(InstanceOperation): ...@@ -499,8 +506,11 @@ class ResetOperation(InstanceOperation):
required_perms = () required_perms = ()
accept_states = ('RUNNING', ) accept_states = ('RUNNING', )
def _operation(self, timeout=5): def _operation(self, activity, timeout=5):
self.instance.reset_vm(timeout=timeout) self.instance.reset_vm(timeout=timeout)
if self.instance.has_agent:
activity.sub_activity('os_boot', readable_name=ugettext_noop(
"wait operating system loading"), interruptible=True)
register_operation(ResetOperation) register_operation(ResetOperation)
......
...@@ -85,16 +85,22 @@ def agent_started(vm, version=None): ...@@ -85,16 +85,22 @@ def agent_started(vm, version=None):
from vm.models import Instance, instance_activity, InstanceActivity from vm.models import Instance, instance_activity, InstanceActivity
instance = Instance.objects.get(id=int(vm.split('-')[-1])) instance = Instance.objects.get(id=int(vm.split('-')[-1]))
queue = instance.get_remote_queue_name("agent") queue = instance.get_remote_queue_name("agent")
initialized = InstanceActivity.objects.filter( initialized = instance.activity_log.filter(
instance=instance, activity_code='vm.Instance.agent.cleanup').exists() activity_code='vm.Instance.agent.cleanup').exists()
with instance_activity(code_suffix='agent', with instance_activity(code_suffix='agent',
readable_name=ugettext_noop('agent'), readable_name=ugettext_noop('agent'),
concurrency_check=False,
instance=instance) as act: instance=instance) as act:
with act.sub_activity('starting', with act.sub_activity('starting',
readable_name=ugettext_noop('starting')): readable_name=ugettext_noop('starting')):
pass pass
for i in InstanceActivity.objects.filter(
instance=instance, activity_code__endswith='.os_boot',
finished__isnull=True):
i.finish(True)
if version and version != settings.AGENT_VERSION: if version and version != settings.AGENT_VERSION:
try: try:
update_agent(instance, act) update_agent(instance, act)
...@@ -108,10 +114,9 @@ def agent_started(vm, version=None): ...@@ -108,10 +114,9 @@ def agent_started(vm, version=None):
send_init_commands(instance, act) send_init_commands(instance, act)
send_networking_commands(instance, act) send_networking_commands(instance, act)
with act.sub_activity( with act.sub_activity('start_access_server',
'start_access_server', readable_name=ugettext_noop(
readable_name=ugettext_noop('start access server') 'start access server')):
):
start_access_server.apply_async(queue=queue, args=(vm, )) start_access_server.apply_async(queue=queue, args=(vm, ))
......
...@@ -217,6 +217,8 @@ class InstanceActivityTestCase(TestCase): ...@@ -217,6 +217,8 @@ class InstanceActivityTestCase(TestCase):
def test_create_concurrency_check(self): def test_create_concurrency_check(self):
instance = MagicMock(spec=Instance) instance = MagicMock(spec=Instance)
instance.activity_log.filter.return_value.__iter__.return_value = iter(
[MagicMock(spec=InstanceActivity, interruptible=False)])
instance.activity_log.filter.return_value.exists.return_value = True instance.activity_log.filter.return_value.exists.return_value = True
with self.assertRaises(ActivityInProgressError): with self.assertRaises(ActivityInProgressError):
......
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