Commit a1d135bf by Szabolcs Gelencser

Deploy and Shutoff operations work

parent 7cb09284
...@@ -76,20 +76,10 @@ class AclBase(Model): ...@@ -76,20 +76,10 @@ class AclBase(Model):
object_level_set = GenericRelation(ObjectLevel) object_level_set = GenericRelation(ObjectLevel)
def clone_acl(self, other): def clone_acl(self, other):
"""Clone full ACL from other object.""" pass
assert self.id != other.id or type(self) != type(other)
self.object_level_set.clear()
for i in other.object_level_set.all():
ol = self.object_level_set.create(level=i.level)
for j in i.users.all():
ol.users.add(j)
for j in i.groups.all():
ol.groups.add(j)
@classmethod @classmethod
def get_level_object(cls, level): def get_level_object(cls, level):
"""Get Level object for this model by codename."""
ct = ContentType.objects.get_for_model(cls) ct = ContentType.objects.get_for_model(cls)
return Level.objects.get(codename=level, content_type=ct) return Level.objects.get(codename=level, content_type=ct)
...@@ -110,104 +100,28 @@ class AclBase(Model): ...@@ -110,104 +100,28 @@ class AclBase(Model):
raise AttributeError('"whom" must be a User or Group object.') raise AttributeError('"whom" must be a User or Group object.')
def set_user_level(self, user, level): def set_user_level(self, user, level):
#TODO: delete
"""Set level of object for a user. pass
:param whom: user the level is set for
:type whom: User
:param level: codename of level to set, or None
:type level: Level or str or unicode or NoneType
"""
logger.info('%s.set_user_level(%s, %s) called',
*[unicode(p) for p in [self, user, level]])
if level is None:
pk = None
else:
if isinstance(level, basestring):
level = self.get_level_object(level)
if not self.object_level_set.filter(level_id=level.pk).exists():
self.object_level_set.create(level=level)
pk = level.pk
for i in self.object_level_set.all():
if i.level_id != pk:
i.users.remove(user)
else:
i.users.add(user)
i.save()
def set_group_level(self, group, level): def set_group_level(self, group, level):
#TODO: delete
"""Set level of object for a user. pass
:param whom: user the level is set for
:type whom: User or unicode or str
:param level: codename of level to set
:type level: str or unicode
"""
logger.info('%s.set_group_level(%s, %s) called',
*[unicode(p) for p in [self, group, level]])
if level is None:
pk = None
else:
if isinstance(level, basestring):
level = self.get_level_object(level)
if not self.object_level_set.filter(level_id=level.pk).exists():
self.object_level_set.create(level=level)
pk = level.pk
for i in self.object_level_set.all():
if i.level_id != pk:
i.groups.remove(group)
else:
i.groups.add(group)
i.save()
def has_level(self, user, level, group_also=True): def has_level(self, user, level, group_also=True):
return True #TODO: implement
logger.debug('%s.has_level(%s, %s, %s) called', logger.debug('%s.has_level(%s, %s, %s) called',
*[unicode(p) for p in [self, user, level, group_also]]) *[unicode(p) for p in [self, user, level, group_also]])
if user is None or not user.is_authenticated(): return True
return False
if getattr(user, 'is_superuser', False):
logger.debug('- superuser granted')
return True
if isinstance(level, basestring):
level = self.get_level_object(level)
logger.debug("- level set by str: %s", unicode(level))
object_levels = self.object_level_set.filter(
level__weight__gte=level.weight).all()
groups = user.groups.values_list('id', flat=True) if group_also else []
for i in object_levels:
if i.users.filter(pk=user.pk).exists():
return True
if group_also and i.groups.filter(pk__in=groups).exists():
return True
return False
def get_users_with_level(self, **kwargs): def get_users_with_level(self, **kwargs):
# TODO: implement
logger.debug('%s.get_users_with_level() called', unicode(self)) logger.debug('%s.get_users_with_level() called', unicode(self))
object_levels = (self.object_level_set.filter(**kwargs).select_related( return []
'level').prefetch_related('users').all())
users = []
for object_level in object_levels:
name = object_level.level.codename
olusers = object_level.users.all()
users.extend([(u, name) for u in olusers])
logger.debug('- %s: %s' % (name, [u.username for u in olusers]))
return users
def get_groups_with_level(self): def get_groups_with_level(self):
# TODO: implement
logger.debug('%s.get_groups_with_level() called', unicode(self)) logger.debug('%s.get_groups_with_level() called', unicode(self))
object_levels = (self.object_level_set.select_related( return []
'level').prefetch_related('groups').all())
groups = []
for object_level in object_levels:
name = object_level.level.codename
olgroups = object_level.groups.all()
groups.extend([(g, name) for g in olgroups])
logger.debug('- %s: %s' % (name, [g.name for g in olgroups]))
return groups
@classmethod @classmethod
def get_objects_with_level(cls, level, user, def get_objects_with_level(cls, level, user,
......
No preview for this file type
...@@ -26,32 +26,15 @@ from .models import (activity_context, has_suffix, humanize_exception, ...@@ -26,32 +26,15 @@ from .models import (activity_context, has_suffix, humanize_exception,
logger = getLogger(__name__) logger = getLogger(__name__)
class SubOperationMixin(object):
required_perms = ()
def create_activity(self, parent, user, kwargs):
if not parent:
raise TypeError("SubOperation can only be called with "
"parent_activity specified.")
return super(SubOperationMixin, self).create_activity(
parent, user, kwargs)
class Operation(object): class Operation(object):
"""Base class for VM operations. """Base class for VM operations.
""" """
async_queue = 'localhost.man'
required_perms = None required_perms = None
superuser_required = False superuser_required = False
do_not_call_in_templates = True do_not_call_in_templates = True
abortable = False abortable = False
has_percentage = False has_percentage = False
@classmethod
def get_activity_code_suffix(cls):
return cls.id
def __call__(self, **kwargs): def __call__(self, **kwargs):
return self.call(**kwargs) return self.call(**kwargs)
...@@ -63,10 +46,12 @@ class Operation(object): ...@@ -63,10 +46,12 @@ class Operation(object):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def __prelude(self, kwargs): def __prelude(self, request, kwargs):
"""This method contains the shared prelude of call and async. """This method contains the shared prelude of call and async.
""" """
defaults = {'parent_activity': None, 'system': False, 'user': None} self._operation.im_self.get_from_os(request)
defaults = {'system': False, 'user': None}
allargs = dict(defaults, **kwargs) # all arguments allargs = dict(defaults, **kwargs) # all arguments
auxargs = allargs.copy() # auxiliary (i.e. only for _operation) args auxargs = allargs.copy() # auxiliary (i.e. only for _operation) args
...@@ -75,11 +60,8 @@ class Operation(object): ...@@ -75,11 +60,8 @@ class Operation(object):
skip_auth_check = auxargs.pop('system') skip_auth_check = auxargs.pop('system')
user = auxargs.pop('user') user = auxargs.pop('user')
parent_activity = auxargs.pop('parent_activity') if user is None: # parent was a system call
if parent_activity and user is None and not skip_auth_check: skip_auth_check = True
user = allargs['user'] = parent_activity.user
if user is None: # parent was a system call
skip_auth_check = True
# check for unexpected keyword arguments # check for unexpected keyword arguments
argspec = getargspec(self._operation) argspec = getargspec(self._operation)
...@@ -93,12 +75,9 @@ class Operation(object): ...@@ -93,12 +75,9 @@ class Operation(object):
self.check_auth(user) self.check_auth(user)
self.check_precond() self.check_precond()
activity = self.create_activity( return allargs, auxargs
parent=parent_activity, user=user, kwargs=kwargs)
return activity, allargs, auxargs
def _exec_op(self, allargs, auxargs): def _exec_op(self, request, allargs, auxargs):
"""Execute the operation inside the specified activity's context. """Execute the operation inside the specified activity's context.
""" """
# compile arguments for _operation # compile arguments for _operation
...@@ -110,13 +89,7 @@ class Operation(object): ...@@ -110,13 +89,7 @@ class Operation(object):
if k in argspec.args} if k in argspec.args}
arguments.update(auxargs) arguments.update(auxargs)
with activity_context(allargs['activity'], on_abort=self.on_abort, return self._operation(request, **arguments)
on_commit=self.on_commit) as act:
retval = self._operation(**arguments)
if (act.result is None and isinstance(
retval, (basestring, int, HumanReadableObject))):
act.result = retval
return retval
def _operation(self, **kwargs): def _operation(self, **kwargs):
"""This method is the operation's particular implementation. """This method is the operation's particular implementation.
...@@ -125,25 +98,7 @@ class Operation(object): ...@@ -125,25 +98,7 @@ class Operation(object):
""" """
raise NotImplementedError raise NotImplementedError
def async(self, **kwargs): def call(self, request, **kwargs):
"""Execute the operation asynchronously.
Only a quick, preliminary check is ran before creating the associated
activity and queuing the job.
The returned value is the handle for the asynchronous job.
For more information, check the synchronous call's documentation.
"""
logger.info("%s called asynchronously on %s with the following "
"parameters: %r", self.__class__.__name__, self.subject,
kwargs)
activity, allargs, auxargs = self.__prelude(kwargs)
return self.async_operation.apply_async(
args=(self.id, self.subject.pk, activity.pk, allargs, auxargs, ),
queue=self.async_queue)
def call(self, **kwargs):
"""Execute the operation (synchronously). """Execute the operation (synchronously).
Anticipated keyword arguments: Anticipated keyword arguments:
...@@ -159,9 +114,8 @@ class Operation(object): ...@@ -159,9 +114,8 @@ class Operation(object):
logger.info("%s called (synchronously) on %s with the following " logger.info("%s called (synchronously) on %s with the following "
"parameters: %r", self.__class__.__name__, self.subject, "parameters: %r", self.__class__.__name__, self.subject,
kwargs) kwargs)
activity, allargs, auxargs = self.__prelude(kwargs) allargs, auxargs = self.__prelude(request, kwargs)
allargs['activity'] = activity return self._exec_op(request, allargs, auxargs)
return self._exec_op(allargs, auxargs)
def check_precond(self): def check_precond(self):
pass pass
...@@ -185,11 +139,8 @@ class Operation(object): ...@@ -185,11 +139,8 @@ class Operation(object):
def check_auth(self, user): def check_auth(self, user):
"""Check if user is permitted to run this operation on this instance """Check if user is permitted to run this operation on this instance
""" """
#TODO: implement
self.check_perms(user) pass
def create_activity(self, parent, user, kwargs):
raise NotImplementedError
def on_abort(self, activity, error): def on_abort(self, activity, error):
"""This method is called when the operation aborts (i.e. raises an """This method is called when the operation aborts (i.e. raises an
...@@ -197,18 +148,6 @@ class Operation(object): ...@@ -197,18 +148,6 @@ class Operation(object):
""" """
pass pass
def get_activity_name(self, kwargs):
try:
return self.activity_name
except AttributeError:
try:
return self.name._proxy____args[0] # ewww!
except AttributeError:
raise ImproperlyConfigured(
"Set Operation.activity_name to an ugettext_nooped "
"string or a create_readable call, or override "
"get_activity_name to create a name dynamically")
def on_commit(self, activity): def on_commit(self, activity):
"""This method is called when the operation executes successfully. """This method is called when the operation executes successfully.
""" """
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete
from dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops from dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops, FavouriteView
from django.conf.urls import url from django.conf.urls import url
from .views import ( from .views import (
...@@ -99,8 +99,8 @@ urlpatterns = [ ...@@ -99,8 +99,8 @@ urlpatterns = [
# url(r'^node/activity/(?P<pk>\d+)/$', NodeActivityDetail.as_view(), # url(r'^node/activity/(?P<pk>\d+)/$', NodeActivityDetail.as_view(),
# name='dashboard.views.node-activity'), # name='dashboard.views.node-activity'),
# #
# url(r'^favourite/$', FavouriteView.as_view(), url(r'^favourite/$', FavouriteView.as_view(),
# name='dashboard.views.favourite'), name='dashboard.views.favourite'),
# url(r'^group/delete/(?P<pk>\d+)/$', GroupDelete.as_view(), # url(r'^group/delete/(?P<pk>\d+)/$', GroupDelete.as_view(),
# name="dashboard.views.delete-group"), # name="dashboard.views.delete-group"),
# url(r'^group/list/$', GroupList.as_view(), # url(r'^group/list/$', GroupList.as_view(),
......
...@@ -294,7 +294,7 @@ class OperationView(RedirectToLoginMixin, DetailView): ...@@ -294,7 +294,7 @@ class OperationView(RedirectToLoginMixin, DetailView):
result = None result = None
done = False done = False
try: try:
task = self.get_op().async(user=request.user, **extra) task = self.get_op().call(request, user=request.user, **extra)
except HumanReadableException as e: except HumanReadableException as e:
e.send_message(request) e.send_message(request)
logger.exception("Could not start operation") logger.exception("Could not start operation")
......
...@@ -21,6 +21,7 @@ import logging ...@@ -21,6 +21,7 @@ import logging
from collections import OrderedDict from collections import OrderedDict
import openstack_api import openstack_api
from dashboard.models import Favourite
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
...@@ -110,8 +111,6 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView): ...@@ -110,8 +111,6 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
context = super(VmDetailView, self).get_context_data(**kwargs) context = super(VmDetailView, self).get_context_data(**kwargs)
instance = context['instance'] instance = context['instance']
user = self.request.user user = self.request.user
is_operator = instance.has_level(user, "operator")
is_owner = instance.has_level(user, "owner")
ops = get_operations(instance, user) ops = get_operations(instance, user)
hide_tutorial = self.request.COOKIES.get( hide_tutorial = self.request.COOKIES.get(
"hide_tutorial_for_%s" % instance.pk) == "True" "hide_tutorial_for_%s" % instance.pk) == "True"
...@@ -155,9 +154,7 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView): ...@@ -155,9 +154,7 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
# context['ipv6_port'] = instance.get_connect_port(use_ipv6=True) # context['ipv6_port'] = instance.get_connect_port(use_ipv6=True)
# resources forms # resources forms
can_edit = ( can_edit = True
instance.has_level(user, "owner") and
self.request.user.has_perm("vm.change_resources"))
context['resources_form'] = VmResourcesForm( context['resources_form'] = VmResourcesForm(
can_edit=can_edit, instance=instance) can_edit=can_edit, instance=instance)
...@@ -173,11 +170,7 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView): ...@@ -173,11 +170,7 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
context['client_download'] = self.request.COOKIES.get( context['client_download'] = self.request.COOKIES.get(
'downloaded_client') 'downloaded_client')
# can link template # can link template
context['can_link_template'] = instance.template and is_operator context['can_link_template'] = instance.template
# is operator/owner
context['is_operator'] = is_operator
context['is_owner'] = is_owner
# operation also allows RUNNING (if with_shutdown is present) # operation also allows RUNNING (if with_shutdown is present)
context['save_resources_enabled'] = instance.status in ( context['save_resources_enabled'] = instance.status in (
...@@ -307,7 +300,7 @@ def get_operations(instance, user): ...@@ -307,7 +300,7 @@ def get_operations(instance, user):
try: try:
op = v.get_op_by_object(instance) op = v.get_op_by_object(instance)
# op.check_auth(user) # op.check_auth(user)
# op.check_precond() op.check_precond()
except PermissionDenied as e: except PermissionDenied as e:
logger.debug('Not showing operation %s for %s: %s', logger.debug('Not showing operation %s for %s: %s',
k, instance, unicode(e)) k, instance, unicode(e))
...@@ -743,26 +736,24 @@ vm_ops = OrderedDict([ ...@@ -743,26 +736,24 @@ vm_ops = OrderedDict([
('deploy', VmDeployView), ('deploy', VmDeployView),
('wake_up', VmOperationView.factory( ('wake_up', VmOperationView.factory(
op='wake_up', icon='sun-o', effect='success')), op='wake_up', icon='sun-o', effect='success')),
# ('sleep', VmOperationView.factory( ('sleep', VmOperationView.factory(
# extra_bases=[TokenOperationView], op='sleep', icon='moon-o', effect='info')),
# op='sleep', icon='moon-o', effect='info')),
# ('migrate', VmMigrateView), # ('migrate', VmMigrateView),
# ('save_as_template', VmSaveView), # ('save_as_template', VmSaveView),
# ('reboot', VmOperationView.factory( ('reboot', VmOperationView.factory(
# op='reboot', icon='refresh', effect='warning')), op='reboot', icon='refresh', effect='warning')),
# ('reset', VmOperationView.factory( # ('reset', VmOperationView.factory(
# op='reset', icon='bolt', effect='warning')), # op='reset', icon='bolt', effect='warning')),
# ('shutdown', VmOperationView.factory( # ('shutdown', VmOperationView.factory(
# op='shutdown', icon='power-off', effect='warning')), # op='shutdown', icon='power-off', effect='warning')),
# ('shut_off', VmOperationView.factory( ('shut_off', VmOperationView.factory(
# op='shut_off', icon='plug', effect='warning')), op='shut_off', icon='plug', effect='warning')),
# ('recover', VmOperationView.factory( # ('recover', VmOperationView.factory(
# op='recover', icon='medkit', effect='warning')), # op='recover', icon='medkit', effect='warning')),
# ('nostate', VmStateChangeView), # ('nostate', VmStateChangeView),
# ('redeploy', RedeployView), # ('redeploy', RedeployView),
# ('destroy', VmOperationView.factory( ('destroy', VmOperationView.factory(
# extra_bases=[TokenOperationView], op='destroy', icon='times', effect='danger')),
# op='destroy', icon='times', effect='danger')),
# ('create_disk', VmCreateDiskView), # ('create_disk', VmCreateDiskView),
# ('download_disk', VmDownloadDiskView), # ('download_disk', VmDownloadDiskView),
# ('resize_disk', VmDiskModifyView.factory( # ('resize_disk', VmDiskModifyView.factory(
...@@ -790,8 +781,8 @@ vm_ops = OrderedDict([ ...@@ -790,8 +781,8 @@ vm_ops = OrderedDict([
# )), # )),
# ('rename', VmRenameView), # ('rename', VmRenameView),
]) ])
#
#
def _get_activity_icon(act): def _get_activity_icon(act):
op = act.get_operation() op = act.get_operation()
if op and op.id in vm_ops: if op and op.id in vm_ops:
...@@ -1265,7 +1256,7 @@ def vm_activity(request, pk): ...@@ -1265,7 +1256,7 @@ def vm_activity(request, pk):
# activities = activities[:10] # activities = activities[:10]
response['connect_uri'] = instance.get_connect_uri() response['connect_uri'] = instance.get_connect_uri()
response['human_readable_status'] = instance.get_status_display() response['human_readable_status'] = '#TODO' #instance.get_status_display()
response['status'] = instance.status response['status'] = instance.status
response['icon'] = instance.get_status_icon() response['icon'] = instance.get_status_icon()
latest = instance.get_latest_activity_in_progress() latest = instance.get_latest_activity_in_progress()
...@@ -1275,8 +1266,8 @@ def vm_activity(request, pk): ...@@ -1275,8 +1266,8 @@ def vm_activity(request, pk):
context = { context = {
'instance': instance, 'instance': instance,
# 'activities': activities, 'activities': (),
# 'show_show_all': show_show_all, 'show_show_all': False,
'ops': get_operations(instance, request.user), 'ops': get_operations(instance, request.user),
} }
...@@ -1299,19 +1290,19 @@ def vm_activity(request, pk): ...@@ -1299,19 +1290,19 @@ def vm_activity(request, pk):
) )
# #
# #
# class FavouriteView(TemplateView): class FavouriteView(TemplateView):
#
# def post(self, *args, **kwargs): def post(self, *args, **kwargs):
# user = self.request.user user = self.request.user
# vm = Instance.objects.get(pk=self.request.POST.get("vm")) vm = Instance.objects.get(pk=self.request.POST.get("vm"))
# if not vm.has_level(user, 'user'): if not vm.has_level(user, 'user'):
# raise PermissionDenied() raise PermissionDenied()
# try: try:
# Favourite.objects.get(instance=vm, user=user).delete() Favourite.objects.get(instance=vm, user=user).delete()
# return HttpResponse("Deleted.") return HttpResponse("Deleted.")
# except Favourite.DoesNotExist: except Favourite.DoesNotExist:
# Favourite(instance=vm, user=user).save() Favourite(instance=vm, user=user).save()
# return HttpResponse("Added.") return HttpResponse("Added.")
# #
# #
# class TransferInstanceOwnershipConfirmView(TransferOwnershipConfirmView): # class TransferInstanceOwnershipConfirmView(TransferOwnershipConfirmView):
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-22 09:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('vm', '0005_remove_instance_owner'),
]
operations = [
migrations.RemoveField(
model_name='instance',
name='name',
),
]
...@@ -209,7 +209,7 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel): ...@@ -209,7 +209,7 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
return 'template.%d' % self.pk return 'template.%d' % self.pk
class Instance(AclBase, OperatedMixin, TimeStampedModel): class Instance(OperatedMixin, TimeStampedModel):
"""Virtual machine instance. """Virtual machine instance.
""" """
......
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