Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gelencsér Szabolcs
/
circlestack
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
a1d135bf
authored
Feb 22, 2018
by
Szabolcs Gelencser
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Deploy and Shutoff operations work
parent
7cb09284
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
79 additions
and
216 deletions
+79
-216
.idea/workspace.xml
+0
-0
circle/acl/models.py
+11
-97
circle/circle/db.sqlite3
+0
-0
circle/common/operations.py
+14
-75
circle/dashboard/urls.py
+3
-3
circle/dashboard/views/util.py
+1
-1
circle/dashboard/views/vm.py
+30
-39
circle/vm/migrations/0006_remove_instance_name.py
+19
-0
circle/vm/models/instance.py
+1
-1
circle/vm/operations.py
+0
-0
No files found.
.idea/workspace.xml
View file @
a1d135bf
This diff is collapsed.
Click to expand it.
circle/acl/models.py
View file @
a1d135bf
...
...
@@ -76,20 +76,10 @@ class AclBase(Model):
object_level_set
=
GenericRelation
(
ObjectLevel
)
def
clone_acl
(
self
,
other
):
"""Clone full ACL from other object."""
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
)
pass
@classmethod
def
get_level_object
(
cls
,
level
):
"""Get Level object for this model by codename."""
ct
=
ContentType
.
objects
.
get_for_model
(
cls
)
return
Level
.
objects
.
get
(
codename
=
level
,
content_type
=
ct
)
...
...
@@ -110,104 +100,28 @@ class AclBase(Model):
raise
AttributeError
(
'"whom" must be a User or Group object.'
)
def
set_user_level
(
self
,
user
,
level
):
"""Set level of object for a user.
: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
()
#TODO: delete
pass
def
set_group_level
(
self
,
group
,
level
):
"""Set level of object for a user.
: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
()
#TODO: delete
pass
def
has_level
(
self
,
user
,
level
,
group_also
=
True
):
return
True
#TODO: implement
logger
.
debug
(
'
%
s.has_level(
%
s,
%
s,
%
s) called'
,
*
[
unicode
(
p
)
for
p
in
[
self
,
user
,
level
,
group_also
]])
if
user
is
None
or
not
user
.
is_authenticated
():
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
return
True
def
get_users_with_level
(
self
,
**
kwargs
):
# TODO: implement
logger
.
debug
(
'
%
s.get_users_with_level() called'
,
unicode
(
self
))
object_levels
=
(
self
.
object_level_set
.
filter
(
**
kwargs
)
.
select_related
(
'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
return
[]
def
get_groups_with_level
(
self
):
# TODO: implement
logger
.
debug
(
'
%
s.get_groups_with_level() called'
,
unicode
(
self
))
object_levels
=
(
self
.
object_level_set
.
select_related
(
'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
return
[]
@classmethod
def
get_objects_with_level
(
cls
,
level
,
user
,
...
...
circle/circle/db.sqlite3
View file @
a1d135bf
No preview for this file type
circle/common/operations.py
View file @
a1d135bf
...
...
@@ -26,32 +26,15 @@ from .models import (activity_context, has_suffix, humanize_exception,
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
):
"""Base class for VM operations.
"""
async_queue
=
'localhost.man'
required_perms
=
None
superuser_required
=
False
do_not_call_in_templates
=
True
abortable
=
False
has_percentage
=
False
@classmethod
def
get_activity_code_suffix
(
cls
):
return
cls
.
id
def
__call__
(
self
,
**
kwargs
):
return
self
.
call
(
**
kwargs
)
...
...
@@ -63,10 +46,12 @@ class Operation(object):
def
__unicode__
(
self
):
return
self
.
name
def
__prelude
(
self
,
kwargs
):
def
__prelude
(
self
,
request
,
kwargs
):
"""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
auxargs
=
allargs
.
copy
()
# auxiliary (i.e. only for _operation) args
...
...
@@ -75,11 +60,8 @@ class Operation(object):
skip_auth_check
=
auxargs
.
pop
(
'system'
)
user
=
auxargs
.
pop
(
'user'
)
parent_activity
=
auxargs
.
pop
(
'parent_activity'
)
if
parent_activity
and
user
is
None
and
not
skip_auth_check
:
user
=
allargs
[
'user'
]
=
parent_activity
.
user
if
user
is
None
:
# parent was a system call
skip_auth_check
=
True
if
user
is
None
:
# parent was a system call
skip_auth_check
=
True
# check for unexpected keyword arguments
argspec
=
getargspec
(
self
.
_operation
)
...
...
@@ -93,12 +75,9 @@ class Operation(object):
self
.
check_auth
(
user
)
self
.
check_precond
()
activity
=
self
.
create_activity
(
parent
=
parent_activity
,
user
=
user
,
kwargs
=
kwargs
)
return
activity
,
allargs
,
auxargs
return
allargs
,
auxargs
def
_exec_op
(
self
,
allargs
,
auxargs
):
def
_exec_op
(
self
,
request
,
allargs
,
auxargs
):
"""Execute the operation inside the specified activity's context.
"""
# compile arguments for _operation
...
...
@@ -110,13 +89,7 @@ class Operation(object):
if
k
in
argspec
.
args
}
arguments
.
update
(
auxargs
)
with
activity_context
(
allargs
[
'activity'
],
on_abort
=
self
.
on_abort
,
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
return
self
.
_operation
(
request
,
**
arguments
)
def
_operation
(
self
,
**
kwargs
):
"""This method is the operation's particular implementation.
...
...
@@ -125,25 +98,7 @@ class Operation(object):
"""
raise
NotImplementedError
def
async
(
self
,
**
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
):
def
call
(
self
,
request
,
**
kwargs
):
"""Execute the operation (synchronously).
Anticipated keyword arguments:
...
...
@@ -159,9 +114,8 @@ class Operation(object):
logger
.
info
(
"
%
s called (synchronously) on
%
s with the following "
"parameters:
%
r"
,
self
.
__class__
.
__name__
,
self
.
subject
,
kwargs
)
activity
,
allargs
,
auxargs
=
self
.
__prelude
(
kwargs
)
allargs
[
'activity'
]
=
activity
return
self
.
_exec_op
(
allargs
,
auxargs
)
allargs
,
auxargs
=
self
.
__prelude
(
request
,
kwargs
)
return
self
.
_exec_op
(
request
,
allargs
,
auxargs
)
def
check_precond
(
self
):
pass
...
...
@@ -185,11 +139,8 @@ class Operation(object):
def
check_auth
(
self
,
user
):
"""Check if user is permitted to run this operation on this instance
"""
self
.
check_perms
(
user
)
def
create_activity
(
self
,
parent
,
user
,
kwargs
):
raise
NotImplementedError
#TODO: implement
pass
def
on_abort
(
self
,
activity
,
error
):
"""This method is called when the operation aborts (i.e. raises an
...
...
@@ -197,18 +148,6 @@ class Operation(object):
"""
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
):
"""This method is called when the operation executes successfully.
"""
...
...
circle/dashboard/urls.py
View file @
a1d135bf
...
...
@@ -18,7 +18,7 @@
from
__future__
import
absolute_import
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
.views
import
(
...
...
@@ -99,8 +99,8 @@ urlpatterns = [
# url(r'^node/activity/(?P<pk>\d+)/$', NodeActivityDetail.as_view(),
# name='dashboard.views.node-activity'),
#
#
url(r'^favourite/$', FavouriteView.as_view(),
#
name='dashboard.views.favourite'),
url
(
r'^favourite/$'
,
FavouriteView
.
as_view
(),
name
=
'dashboard.views.favourite'
),
# url(r'^group/delete/(?P<pk>\d+)/$', GroupDelete.as_view(),
# name="dashboard.views.delete-group"),
# url(r'^group/list/$', GroupList.as_view(),
...
...
circle/dashboard/views/util.py
View file @
a1d135bf
...
...
@@ -294,7 +294,7 @@ class OperationView(RedirectToLoginMixin, DetailView):
result
=
None
done
=
False
try
:
task
=
self
.
get_op
()
.
async
(
user
=
request
.
user
,
**
extra
)
task
=
self
.
get_op
()
.
call
(
request
,
user
=
request
.
user
,
**
extra
)
except
HumanReadableException
as
e
:
e
.
send_message
(
request
)
logger
.
exception
(
"Could not start operation"
)
...
...
circle/dashboard/views/vm.py
View file @
a1d135bf
...
...
@@ -21,6 +21,7 @@ import logging
from
collections
import
OrderedDict
import
openstack_api
from
dashboard.models
import
Favourite
from
django.conf
import
settings
from
django.contrib
import
messages
from
django.contrib.auth.mixins
import
LoginRequiredMixin
...
...
@@ -110,8 +111,6 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
context
=
super
(
VmDetailView
,
self
)
.
get_context_data
(
**
kwargs
)
instance
=
context
[
'instance'
]
user
=
self
.
request
.
user
is_operator
=
instance
.
has_level
(
user
,
"operator"
)
is_owner
=
instance
.
has_level
(
user
,
"owner"
)
ops
=
get_operations
(
instance
,
user
)
hide_tutorial
=
self
.
request
.
COOKIES
.
get
(
"hide_tutorial_for_
%
s"
%
instance
.
pk
)
==
"True"
...
...
@@ -155,9 +154,7 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
# context['ipv6_port'] = instance.get_connect_port(use_ipv6=True)
# resources forms
can_edit
=
(
instance
.
has_level
(
user
,
"owner"
)
and
self
.
request
.
user
.
has_perm
(
"vm.change_resources"
))
can_edit
=
True
context
[
'resources_form'
]
=
VmResourcesForm
(
can_edit
=
can_edit
,
instance
=
instance
)
...
...
@@ -173,11 +170,7 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
context
[
'client_download'
]
=
self
.
request
.
COOKIES
.
get
(
'downloaded_client'
)
# can link template
context
[
'can_link_template'
]
=
instance
.
template
and
is_operator
# is operator/owner
context
[
'is_operator'
]
=
is_operator
context
[
'is_owner'
]
=
is_owner
context
[
'can_link_template'
]
=
instance
.
template
# operation also allows RUNNING (if with_shutdown is present)
context
[
'save_resources_enabled'
]
=
instance
.
status
in
(
...
...
@@ -307,7 +300,7 @@ def get_operations(instance, user):
try
:
op
=
v
.
get_op_by_object
(
instance
)
# op.check_auth(user)
#
op.check_precond()
op
.
check_precond
()
except
PermissionDenied
as
e
:
logger
.
debug
(
'Not showing operation
%
s for
%
s:
%
s'
,
k
,
instance
,
unicode
(
e
))
...
...
@@ -743,26 +736,24 @@ vm_ops = OrderedDict([
(
'deploy'
,
VmDeployView
),
(
'wake_up'
,
VmOperationView
.
factory
(
op
=
'wake_up'
,
icon
=
'sun-o'
,
effect
=
'success'
)),
# ('sleep', VmOperationView.factory(
# extra_bases=[TokenOperationView],
# op='sleep', icon='moon-o', effect='info')),
(
'sleep'
,
VmOperationView
.
factory
(
op
=
'sleep'
,
icon
=
'moon-o'
,
effect
=
'info'
)),
# ('migrate', VmMigrateView),
# ('save_as_template', VmSaveView),
#
('reboot', VmOperationView.factory(
#
op='reboot', icon='refresh', effect='warning')),
(
'reboot'
,
VmOperationView
.
factory
(
op
=
'reboot'
,
icon
=
'refresh'
,
effect
=
'warning'
)),
# ('reset', VmOperationView.factory(
# op='reset', icon='bolt', effect='warning')),
# ('shutdown', VmOperationView.factory(
# op='shutdown', icon='power-off', effect='warning')),
#
('shut_off', VmOperationView.factory(
#
op='shut_off', icon='plug', effect='warning')),
(
'shut_off'
,
VmOperationView
.
factory
(
op
=
'shut_off'
,
icon
=
'plug'
,
effect
=
'warning'
)),
# ('recover', VmOperationView.factory(
# op='recover', icon='medkit', effect='warning')),
# ('nostate', VmStateChangeView),
# ('redeploy', RedeployView),
# ('destroy', VmOperationView.factory(
# extra_bases=[TokenOperationView],
# op='destroy', icon='times', effect='danger')),
(
'destroy'
,
VmOperationView
.
factory
(
op
=
'destroy'
,
icon
=
'times'
,
effect
=
'danger'
)),
# ('create_disk', VmCreateDiskView),
# ('download_disk', VmDownloadDiskView),
# ('resize_disk', VmDiskModifyView.factory(
...
...
@@ -790,8 +781,8 @@ vm_ops = OrderedDict([
# )),
# ('rename', VmRenameView),
])
#
#
def
_get_activity_icon
(
act
):
op
=
act
.
get_operation
()
if
op
and
op
.
id
in
vm_ops
:
...
...
@@ -1265,7 +1256,7 @@ def vm_activity(request, pk):
# activities = activities[:10]
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
[
'icon'
]
=
instance
.
get_status_icon
()
latest
=
instance
.
get_latest_activity_in_progress
()
...
...
@@ -1275,8 +1266,8 @@ def vm_activity(request, pk):
context
=
{
'instance'
:
instance
,
# 'activities': activities
,
# 'show_show_all': show_show_all
,
'activities'
:
()
,
'show_show_all'
:
False
,
'ops'
:
get_operations
(
instance
,
request
.
user
),
}
...
...
@@ -1299,19 +1290,19 @@ def vm_activity(request, pk):
)
#
#
#
class FavouriteView(TemplateView):
#
#
def post(self, *args, **kwargs):
#
user = self.request.user
#
vm = Instance.objects.get(pk=self.request.POST.get("vm"))
#
if not vm.has_level(user, 'user'):
#
raise PermissionDenied()
#
try:
#
Favourite.objects.get(instance=vm, user=user).delete()
#
return HttpResponse("Deleted.")
#
except Favourite.DoesNotExist:
#
Favourite(instance=vm, user=user).save()
#
return HttpResponse("Added.")
class
FavouriteView
(
TemplateView
):
def
post
(
self
,
*
args
,
**
kwargs
):
user
=
self
.
request
.
user
vm
=
Instance
.
objects
.
get
(
pk
=
self
.
request
.
POST
.
get
(
"vm"
))
if
not
vm
.
has_level
(
user
,
'user'
):
raise
PermissionDenied
()
try
:
Favourite
.
objects
.
get
(
instance
=
vm
,
user
=
user
)
.
delete
()
return
HttpResponse
(
"Deleted."
)
except
Favourite
.
DoesNotExist
:
Favourite
(
instance
=
vm
,
user
=
user
)
.
save
()
return
HttpResponse
(
"Added."
)
#
#
# class TransferInstanceOwnershipConfirmView(TransferOwnershipConfirmView):
...
...
circle/vm/migrations/0006_remove_instance_name.py
0 → 100644
View file @
a1d135bf
# -*- 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'
,
),
]
circle/vm/models/instance.py
View file @
a1d135bf
...
...
@@ -209,7 +209,7 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
return
'template.
%
d'
%
self
.
pk
class
Instance
(
AclBase
,
OperatedMixin
,
TimeStampedModel
):
class
Instance
(
OperatedMixin
,
TimeStampedModel
):
"""Virtual machine instance.
"""
...
...
circle/vm/operations.py
View file @
a1d135bf
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment