Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
a8fb5a86
authored
Jul 07, 2014
by
Bach Dániel
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature-fix-acls'
Conflicts: circle/vm/operations.py
parents
f7488817
f32bb524
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
283 additions
and
72 deletions
+283
-72
circle/circle/__init__.py
+20
-0
circle/common/operations.py
+5
-2
circle/dashboard/fixtures/test-vm-fixture.json
+18
-0
circle/dashboard/forms.py
+44
-13
circle/dashboard/static/dashboard/bootstrap-slider/bootstrap-slider.js
+3
-0
circle/dashboard/static/dashboard/vm-details.js
+12
-3
circle/dashboard/templates/dashboard/_template-choose.html
+2
-0
circle/dashboard/templates/dashboard/vm-detail/console.html
+6
-0
circle/dashboard/templates/dashboard/vm-detail/resources.html
+10
-1
circle/dashboard/templates/dashboard/vm-list/column-admin.html
+1
-1
circle/dashboard/tests/test_mockedviews.py
+6
-4
circle/dashboard/tests/test_views.py
+6
-0
circle/dashboard/urls.py
+1
-3
circle/dashboard/views.py
+45
-44
circle/storage/models.py
+3
-0
circle/vm/models/instance.py
+5
-0
circle/vm/operations.py
+91
-1
circle/vm/tests/test_models.py
+5
-0
No files found.
circle/circle/__init__.py
View file @
a8fb5a86
# register a signal do update permissions every migration.
# This is based on app django_extensions update_permissions command
from
south.signals
import
post_migrate
def
update_permissions_after_migration
(
app
,
**
kwargs
):
"""
Update app permission just after every migration.
This is based on app django_extensions update_permissions
management command.
"""
from
django.conf
import
settings
from
django.db.models
import
get_app
,
get_models
from
django.contrib.auth.management
import
create_permissions
create_permissions
(
get_app
(
app
),
get_models
(),
2
if
settings
.
DEBUG
else
0
)
post_migrate
.
connect
(
update_permissions_after_migration
)
circle/common/operations.py
View file @
a8fb5a86
...
@@ -20,7 +20,7 @@ from logging import getLogger
...
@@ -20,7 +20,7 @@ from logging import getLogger
from
.models
import
activity_context
,
has_suffix
from
.models
import
activity_context
,
has_suffix
from
django.core.exceptions
import
PermissionDenied
from
django.core.exceptions
import
PermissionDenied
,
ImproperlyConfigured
logger
=
getLogger
(
__name__
)
logger
=
getLogger
(
__name__
)
...
@@ -30,7 +30,7 @@ class Operation(object):
...
@@ -30,7 +30,7 @@ class Operation(object):
"""Base class for VM operations.
"""Base class for VM operations.
"""
"""
async_queue
=
'localhost.man'
async_queue
=
'localhost.man'
required_perms
=
()
required_perms
=
None
do_not_call_in_templates
=
True
do_not_call_in_templates
=
True
abortable
=
False
abortable
=
False
has_percentage
=
False
has_percentage
=
False
...
@@ -141,6 +141,9 @@ class Operation(object):
...
@@ -141,6 +141,9 @@ class Operation(object):
pass
pass
def
check_auth
(
self
,
user
):
def
check_auth
(
self
,
user
):
if
self
.
required_perms
is
None
:
raise
ImproperlyConfigured
(
"Set required_perms to () if none needed."
)
if
not
user
.
has_perms
(
self
.
required_perms
):
if
not
user
.
has_perms
(
self
.
required_perms
):
raise
PermissionDenied
(
"
%
s doesn't have the required permissions."
raise
PermissionDenied
(
"
%
s doesn't have the required permissions."
%
user
)
%
user
)
...
...
circle/dashboard/fixtures/test-vm-fixture.json
View file @
a8fb5a86
...
@@ -1240,6 +1240,24 @@
...
@@ -1240,6 +1240,24 @@
}
}
},
},
{
{
"pk"
:
1367
,
"model"
:
"auth.permission"
,
"fields"
:
{
"codename"
:
"create_vm"
,
"name"
:
"Can create a new VM."
,
"content_type"
:
28
}
},
{
"pk"
:
1368
,
"model"
:
"auth.permission"
,
"fields"
:
{
"codename"
:
"access_console"
,
"name"
:
"Can access the graphical console of a VM."
,
"content_type"
:
28
}
},
{
"pk"
:
1
,
"pk"
:
1
,
"model"
:
"auth.group"
,
"model"
:
"auth.group"
,
"fields"
:
{
"fields"
:
{
...
...
circle/dashboard/forms.py
View file @
a8fb5a86
...
@@ -25,6 +25,7 @@ from django.contrib.auth.forms import (
...
@@ -25,6 +25,7 @@ from django.contrib.auth.forms import (
)
)
from
django.contrib.auth.models
import
User
,
Group
from
django.contrib.auth.models
import
User
,
Group
from
django.core.validators
import
URLValidator
from
django.core.validators
import
URLValidator
from
django.core.exceptions
import
PermissionDenied
,
ValidationError
from
crispy_forms.helper
import
FormHelper
from
crispy_forms.helper
import
FormHelper
from
crispy_forms.layout
import
(
from
crispy_forms.layout
import
(
...
@@ -593,6 +594,17 @@ class TemplateForm(forms.ModelForm):
...
@@ -593,6 +594,17 @@ class TemplateForm(forms.ModelForm):
n
=
self
.
instance
.
interface_set
.
values_list
(
"vlan"
,
flat
=
True
)
n
=
self
.
instance
.
interface_set
.
values_list
(
"vlan"
,
flat
=
True
)
self
.
initial
[
'networks'
]
=
n
self
.
initial
[
'networks'
]
=
n
self
.
allowed_fields
=
(
'name'
,
'access_method'
,
'description'
,
'system'
,
'tags'
)
if
self
.
user
.
has_perm
(
'vm.change_template_resources'
):
self
.
allowed_fields
+=
tuple
(
set
(
self
.
fields
.
keys
())
-
set
([
'raw_data'
]))
if
self
.
user
.
is_superuser
:
self
.
allowed_fields
+=
(
'raw_data'
,
)
for
name
,
field
in
self
.
fields
.
items
():
if
name
not
in
self
.
allowed_fields
:
field
.
widget
.
attrs
[
'disabled'
]
=
'disabled'
if
not
self
.
instance
.
pk
and
len
(
self
.
errors
)
<
1
:
if
not
self
.
instance
.
pk
and
len
(
self
.
errors
)
<
1
:
self
.
instance
.
priority
=
20
self
.
instance
.
priority
=
20
self
.
instance
.
ram_size
=
512
self
.
instance
.
ram_size
=
512
...
@@ -603,14 +615,35 @@ class TemplateForm(forms.ModelForm):
...
@@ -603,14 +615,35 @@ class TemplateForm(forms.ModelForm):
return
User
.
objects
.
get
(
pk
=
self
.
instance
.
owner
.
pk
)
return
User
.
objects
.
get
(
pk
=
self
.
instance
.
owner
.
pk
)
return
self
.
user
return
self
.
user
def
clean_raw_data
(
self
):
def
_clean_fields
(
self
):
# if raw_data has changed and the user is not superuser
try
:
if
"raw_data"
in
self
.
changed_data
and
not
self
.
user
.
is_superuser
:
old
=
InstanceTemplate
.
objects
.
get
(
pk
=
self
.
instance
.
pk
)
old_raw_data
=
InstanceTemplate
.
objects
.
get
(
except
InstanceTemplate
.
DoesNotExist
:
pk
=
self
.
instance
.
pk
)
.
raw_data
old
=
None
return
old_raw_data
for
name
,
field
in
self
.
fields
.
items
():
else
:
if
name
in
self
.
allowed_fields
:
return
self
.
cleaned_data
[
'raw_data'
]
value
=
field
.
widget
.
value_from_datadict
(
self
.
data
,
self
.
files
,
self
.
add_prefix
(
name
))
try
:
if
isinstance
(
field
,
forms
.
FileField
):
initial
=
self
.
initial
.
get
(
name
,
field
.
initial
)
value
=
field
.
clean
(
value
,
initial
)
else
:
value
=
field
.
clean
(
value
)
self
.
cleaned_data
[
name
]
=
value
if
hasattr
(
self
,
'clean_
%
s'
%
name
):
value
=
getattr
(
self
,
'clean_
%
s'
%
name
)()
self
.
cleaned_data
[
name
]
=
value
except
ValidationError
as
e
:
self
.
_errors
[
name
]
=
self
.
error_class
(
e
.
messages
)
if
name
in
self
.
cleaned_data
:
del
self
.
cleaned_data
[
name
]
elif
old
:
if
name
==
'networks'
:
self
.
cleaned_data
[
name
]
=
[
i
.
vlan
for
i
in
self
.
instance
.
interface_set
.
all
()]
else
:
self
.
cleaned_data
[
name
]
=
getattr
(
old
,
name
)
def
save
(
self
,
commit
=
True
):
def
save
(
self
,
commit
=
True
):
data
=
self
.
cleaned_data
data
=
self
.
cleaned_data
...
@@ -624,6 +657,8 @@ class TemplateForm(forms.ModelForm):
...
@@ -624,6 +657,8 @@ class TemplateForm(forms.ModelForm):
networks
=
InterfaceTemplate
.
objects
.
filter
(
networks
=
InterfaceTemplate
.
objects
.
filter
(
template
=
self
.
instance
)
.
values_list
(
"vlan"
,
flat
=
True
)
template
=
self
.
instance
)
.
values_list
(
"vlan"
,
flat
=
True
)
for
m
in
data
[
'networks'
]:
for
m
in
data
[
'networks'
]:
if
not
m
.
has_level
(
self
.
user
,
"user"
):
raise
PermissionDenied
()
if
m
.
pk
not
in
networks
:
if
m
.
pk
not
in
networks
:
InterfaceTemplate
(
vlan
=
m
,
managed
=
m
.
managed
,
InterfaceTemplate
(
vlan
=
m
,
managed
=
m
.
managed
,
template
=
self
.
instance
)
.
save
()
template
=
self
.
instance
)
.
save
()
...
@@ -635,10 +670,6 @@ class TemplateForm(forms.ModelForm):
...
@@ -635,10 +670,6 @@ class TemplateForm(forms.ModelForm):
@property
@property
def
helper
(
self
):
def
helper
(
self
):
kwargs_raw_data
=
{}
if
not
self
.
user
.
is_superuser
:
kwargs_raw_data
[
'readonly'
]
=
None
helper
=
FormHelper
()
helper
=
FormHelper
()
helper
.
layout
=
Layout
(
helper
.
layout
=
Layout
(
Field
(
"name"
),
Field
(
"name"
),
...
@@ -690,7 +721,7 @@ class TemplateForm(forms.ModelForm):
...
@@ -690,7 +721,7 @@ class TemplateForm(forms.ModelForm):
_
(
"Virtual machine settings"
),
_
(
"Virtual machine settings"
),
Field
(
'access_method'
),
Field
(
'access_method'
),
Field
(
'boot_menu'
),
Field
(
'boot_menu'
),
Field
(
'raw_data'
,
**
kwargs_raw_data
),
Field
(
'raw_data'
),
Field
(
'req_traits'
),
Field
(
'req_traits'
),
Field
(
'description'
),
Field
(
'description'
),
Field
(
"parent"
,
type
=
"hidden"
),
Field
(
"parent"
,
type
=
"hidden"
),
...
...
circle/dashboard/static/dashboard/bootstrap-slider/bootstrap-slider.js
View file @
a8fb5a86
...
@@ -192,6 +192,9 @@
...
@@ -192,6 +192,9 @@
},
},
mousedown
:
function
(
ev
)
{
mousedown
:
function
(
ev
)
{
if
(
this
.
element
[
0
].
disabled
)
{
return
false
;
}
// Touch: Get the original event:
// Touch: Get the original event:
if
(
this
.
touchCapable
&&
ev
.
type
===
'touchstart'
)
{
if
(
this
.
touchCapable
&&
ev
.
type
===
'touchstart'
)
{
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
a8fb5a86
...
@@ -11,13 +11,14 @@ $(function() {
...
@@ -11,13 +11,14 @@ $(function() {
/* save resources */
/* save resources */
$
(
'#vm-details-resources-save'
).
click
(
function
()
{
$
(
'#vm-details-resources-save'
).
click
(
function
()
{
$
(
'i.icon-save'
,
this
).
removeClass
(
"icon-save"
).
addClass
(
"icon-refresh icon-spin"
);
$
(
'i.icon-save'
,
this
).
removeClass
(
"icon-save"
).
addClass
(
"icon-refresh icon-spin"
);
var
vm
=
$
(
this
).
data
(
"vm"
);
$
.
ajax
({
$
.
ajax
({
type
:
'POST'
,
type
:
'POST'
,
url
:
location
.
href
,
url
:
"/dashboard/vm/"
+
vm
+
"/op/resources_change/"
,
data
:
$
(
'#vm-details-resources-form'
).
serialize
(),
data
:
$
(
'#vm-details-resources-form'
).
serialize
(),
success
:
function
(
data
,
textStatus
,
xhr
)
{
success
:
function
(
data
,
textStatus
,
xhr
)
{
addMessage
(
data
[
'message'
],
'success'
);
$
(
"#vm-details-resources-save i"
).
removeClass
(
'icon-refresh icon-spin'
).
addClass
(
"icon-save"
);
$
(
"#vm-details-resources-save i"
).
removeClass
(
'icon-refresh icon-spin'
).
addClass
(
"icon-save"
);
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
},
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
error
:
function
(
xhr
,
textStatus
,
error
)
{
$
(
"#vm-details-resources-save i"
).
removeClass
(
'icon-refresh icon-spin'
).
addClass
(
"icon-save"
);
$
(
"#vm-details-resources-save i"
).
removeClass
(
'icon-refresh icon-spin'
).
addClass
(
"icon-save"
);
...
@@ -330,7 +331,7 @@ function decideActivityRefresh() {
...
@@ -330,7 +331,7 @@ function decideActivityRefresh() {
/* unescapes html got via the request, also removes whitespaces and replaces all ' with " */
/* unescapes html got via the request, also removes whitespaces and replaces all ' with " */
function
unescapeHTML
(
html
)
{
function
unescapeHTML
(
html
)
{
return
html
.
replace
(
/</g
,
'<'
).
replace
(
/>/g
,
'>'
).
replace
(
/&/g
,
'&'
).
replace
(
/–/g
,
"–"
).
replace
(
/
\/
/g
,
""
).
replace
(
/'/g
,
'"'
).
replace
(
/ /g
,
''
);
return
html
.
replace
(
/</g
,
'<'
).
replace
(
/>/g
,
'>'
).
replace
(
/&/g
,
'&'
).
replace
(
/–/g
,
"–"
).
replace
(
/
\/
/g
,
""
).
replace
(
/'/g
,
'"'
).
replace
(
/
'/g
,
"'"
).
replace
(
/
/g
,
''
);
}
}
/* the html page contains some tags that were modified via js (titles for example), we delete these
/* the html page contains some tags that were modified via js (titles for example), we delete these
...
@@ -367,6 +368,14 @@ function checkNewActivity(only_status, runs) {
...
@@ -367,6 +368,14 @@ function checkNewActivity(only_status, runs) {
$
(
"[data-target=#_console]"
).
attr
(
"data-toggle"
,
"_pill"
).
attr
(
"href"
,
"#"
).
parent
(
"li"
).
addClass
(
"disabled"
);
$
(
"[data-target=#_console]"
).
attr
(
"data-toggle"
,
"_pill"
).
attr
(
"href"
,
"#"
).
parent
(
"li"
).
addClass
(
"disabled"
);
}
}
if
(
data
[
'status'
]
==
"STOPPED"
)
{
$
(
".enabled-when-stopped"
).
prop
(
"disabled"
,
false
);
$
(
".hide-when-stopped"
).
hide
();
}
else
{
$
(
".enabled-when-stopped"
).
prop
(
"disabled"
,
true
);
$
(
".hide-when-stopped"
).
show
();
}
if
(
runs
>
0
&&
decideActivityRefresh
())
{
if
(
runs
>
0
&&
decideActivityRefresh
())
{
setTimeout
(
setTimeout
(
function
()
{
checkNewActivity
(
only_status
,
runs
+
1
)},
function
()
{
checkNewActivity
(
only_status
,
runs
+
1
)},
...
...
circle/dashboard/templates/dashboard/_template-choose.html
View file @
a8fb5a86
...
@@ -16,10 +16,12 @@
...
@@ -16,10 +16,12 @@
<div
class=
"clearfix"
></div>
<div
class=
"clearfix"
></div>
</div>
</div>
{% endfor %}
{% endfor %}
{% if perms.vm.create_base_template %}
<div
class=
"panel panel-default template-choose-list-element"
>
<div
class=
"panel panel-default template-choose-list-element"
>
<input
type=
"radio"
name=
"parent"
value=
"base_vm"
/>
<input
type=
"radio"
name=
"parent"
value=
"base_vm"
/>
{% trans "Create a new base VM without disk" %}
{% trans "Create a new base VM without disk" %}
</div>
</div>
{% endif %}
<button
type=
"submit"
id=
"template-choose-next-button"
class=
"btn btn-success pull-right"
>
{% trans "Next" %}
</button>
<button
type=
"submit"
id=
"template-choose-next-button"
class=
"btn btn-success pull-right"
>
{% trans "Next" %}
</button>
<div
class=
"clearfix"
></div>
<div
class=
"clearfix"
></div>
</div>
</div>
...
...
circle/dashboard/templates/dashboard/vm-detail/console.html
View file @
a8fb5a86
{% load i18n %}
{% load i18n %}
<div
class=
"btn-toolbar"
>
<div
class=
"btn-toolbar"
>
{% if perms.vm.access_console %}
<button
id=
"sendCtrlAltDelButton"
class=
"btn btn-danger btn-sm"
>
{% trans "Send Ctrl+Alt+Del" %}
</button>
<button
id=
"sendCtrlAltDelButton"
class=
"btn btn-danger btn-sm"
>
{% trans "Send Ctrl+Alt+Del" %}
</button>
<button
id=
"sendPasswordButton"
class=
"btn btn-default btn-sm"
>
{% trans "Type password" %}
</button>
<button
id=
"sendPasswordButton"
class=
"btn btn-default btn-sm"
>
{% trans "Type password" %}
</button>
{% endif %}
<button
id=
"getScreenshotButton"
class=
"btn btn-info btn-sm pull-right"
data-vm-pk=
"{{ instance.pk }}"
><i
class=
"icon-picture"
></i>
{% trans "Screenshot" %}
</button>
<button
id=
"getScreenshotButton"
class=
"btn btn-info btn-sm pull-right"
data-vm-pk=
"{{ instance.pk }}"
><i
class=
"icon-picture"
></i>
{% trans "Screenshot" %}
</button>
</div>
</div>
{% if perms.vm.access_console %}
<div
class=
"alert alert-info"
id=
"noVNC_status"
>
<div
class=
"alert alert-info"
id=
"noVNC_status"
>
</div>
</div>
{% endif %}
<div
id=
"vm-console-screenshot"
>
<div
id=
"vm-console-screenshot"
>
<button
class=
"btn btn-danger btn-sm pull-right"
>
{% trans "Close" %}
</button>
<button
class=
"btn btn-danger btn-sm pull-right"
>
{% trans "Close" %}
</button>
...
@@ -14,6 +18,7 @@
...
@@ -14,6 +18,7 @@
<hr
/>
<hr
/>
</div>
</div>
{% if perms.vm.access_console %}
<canvas
id=
"noVNC_canvas"
width=
"640px"
height=
"20px"
>
Canvas not supported.
<canvas
id=
"noVNC_canvas"
width=
"640px"
height=
"20px"
>
Canvas not supported.
</canvas>
</canvas>
...
@@ -22,3 +27,4 @@
...
@@ -22,3 +27,4 @@
var
INCLUDE_URI
=
'{{ STATIC_URL }}dashboard/novnc/'
;
var
INCLUDE_URI
=
'{{ STATIC_URL }}dashboard/novnc/'
;
var
VNC_URL
=
"{{ vnc_url }}"
;
var
VNC_URL
=
"{{ vnc_url }}"
;
</script>
</script>
{% endif %}
circle/dashboard/templates/dashboard/vm-detail/resources.html
View file @
a8fb5a86
...
@@ -33,11 +33,20 @@
...
@@ -33,11 +33,20 @@
</div>
</div>
</p>
</p>
{% if can_change_resources %}
<p
class=
"row"
>
<p
class=
"row"
>
<div
class=
"col-sm-12"
>
<div
class=
"col-sm-12"
>
<button
type=
"submit"
class=
"btn btn-success btn-sm"
id=
"vm-details-resources-save"
><i
class=
"icon-save"
></i>
{% trans "Save resources" %}
</button>
<button
type=
"submit"
class=
"btn btn-success btn-sm enabled-when-stopped"
id=
"vm-details-resources-save"
data-vm=
"{{ instance.pk }}"
{%
if
not
op
.
resources_change
%}
disabled
{%
endif
%}
>
<i
class=
"icon-save"
></i>
{% trans "Save resources" %}
</button>
<span
class=
"hide-when-stopped"
{%
if
op
.
resources_change
%}
style=
"display: none;"
{%
endif
%}
>
{% trans "Stop your VM to change resources." %}
</span>
</div>
</div>
</p>
</p>
{% endif %}
</form>
</form>
<hr
/>
<hr
/>
...
...
circle/dashboard/templates/dashboard/vm-list/column-admin.html
View file @
a8fb5a86
<a
href=
"{% url "
dashboard
.
v
iews
.
vm-
migrate
"
pk=
record.pk
%}"
class=
"btn btn-default btn-xs vm-migrate"
data-vm-pk=
"{{ record.pk }}"
title
data-original-title=
"Migrate"
>
<a
href=
"{% url "
dashboard
.
v
m
.
op
.
migrate
"
pk=
record.pk
%}"
class=
"btn btn-default btn-xs vm-migrate"
data-vm-pk=
"{{ record.pk }}"
title
data-original-title=
"Migrate"
>
<i
class=
"icon-truck"
></i>
<i
class=
"icon-truck"
></i>
</a>
</a>
<a
id=
"vm-list-rename-button"
class=
"btn btn-default btn-xs"
title
data-original-title=
"Rename"
>
<a
id=
"vm-list-rename-button"
class=
"btn btn-default btn-xs"
title
data-original-title=
"Rename"
>
...
...
circle/dashboard/tests/test_mockedviews.py
View file @
a8fb5a86
...
@@ -159,7 +159,7 @@ class VmOperationViewTestCase(unittest.TestCase):
...
@@ -159,7 +159,7 @@ class VmOperationViewTestCase(unittest.TestCase):
assert
not
msg
.
error
.
called
assert
not
msg
.
error
.
called
def
test_migrate_failed
(
self
):
def
test_migrate_failed
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
})
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
}
,
superuser
=
True
)
view
=
vm_ops
[
'migrate'
]
view
=
vm_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
...
@@ -177,7 +177,7 @@ class VmOperationViewTestCase(unittest.TestCase):
...
@@ -177,7 +177,7 @@ class VmOperationViewTestCase(unittest.TestCase):
assert
msg
.
error
.
called
assert
msg
.
error
.
called
def
test_migrate_template
(
self
):
def
test_migrate_template
(
self
):
request
=
FakeRequestFactory
()
request
=
FakeRequestFactory
(
superuser
=
True
)
view
=
vm_ops
[
'migrate'
]
view
=
vm_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
:
with
patch
.
object
(
view
,
'get_object'
)
as
go
:
...
@@ -190,7 +190,7 @@ class VmOperationViewTestCase(unittest.TestCase):
...
@@ -190,7 +190,7 @@ class VmOperationViewTestCase(unittest.TestCase):
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
.
status_code
,
200
)
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
.
status_code
,
200
)
def
test_save_as_wo_name
(
self
):
def
test_save_as_wo_name
(
self
):
request
=
FakeRequestFactory
(
POST
=
{})
request
=
FakeRequestFactory
(
POST
=
{}
,
has_perms_mock
=
True
)
view
=
vm_ops
[
'save_as_template'
]
view
=
vm_ops
[
'save_as_template'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
...
@@ -224,7 +224,7 @@ class VmOperationViewTestCase(unittest.TestCase):
...
@@ -224,7 +224,7 @@ class VmOperationViewTestCase(unittest.TestCase):
assert
not
msg
.
error
.
called
assert
not
msg
.
error
.
called
def
test_save_as_template
(
self
):
def
test_save_as_template
(
self
):
request
=
FakeRequestFactory
()
request
=
FakeRequestFactory
(
has_perms_mock
=
True
)
view
=
vm_ops
[
'save_as_template'
]
view
=
vm_ops
[
'save_as_template'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
:
with
patch
.
object
(
view
,
'get_object'
)
as
go
:
...
@@ -246,6 +246,8 @@ def FakeRequestFactory(*args, **kwargs):
...
@@ -246,6 +246,8 @@ def FakeRequestFactory(*args, **kwargs):
user
=
UserFactory
()
user
=
UserFactory
()
user
.
is_authenticated
=
lambda
:
kwargs
.
get
(
'authenticated'
,
True
)
user
.
is_authenticated
=
lambda
:
kwargs
.
get
(
'authenticated'
,
True
)
user
.
is_superuser
=
kwargs
.
get
(
'superuser'
,
False
)
user
.
is_superuser
=
kwargs
.
get
(
'superuser'
,
False
)
if
kwargs
.
get
(
'has_perms_mock'
,
False
):
user
.
has_perms
=
MagicMock
(
return_value
=
True
)
request
=
HttpRequest
()
request
=
HttpRequest
()
request
.
user
=
user
request
.
user
=
user
...
...
circle/dashboard/tests/test_views.py
View file @
a8fb5a86
...
@@ -63,6 +63,8 @@ class VmDetailTest(LoginMixin, TestCase):
...
@@ -63,6 +63,8 @@ class VmDetailTest(LoginMixin, TestCase):
self
.
g1
.
user_set
.
add
(
self
.
u1
)
self
.
g1
.
user_set
.
add
(
self
.
u1
)
self
.
g1
.
user_set
.
add
(
self
.
u2
)
self
.
g1
.
user_set
.
add
(
self
.
u2
)
self
.
g1
.
save
()
self
.
g1
.
save
()
self
.
u1
.
user_permissions
.
add
(
Permission
.
objects
.
get
(
codename
=
'create_vm'
))
settings
[
"default_vlangroup"
]
=
'public'
settings
[
"default_vlangroup"
]
=
'public'
VlanGroup
.
objects
.
create
(
name
=
'public'
)
VlanGroup
.
objects
.
create
(
name
=
'public'
)
...
@@ -1544,6 +1546,8 @@ class VmDetailVncTest(LoginMixin, TestCase):
...
@@ -1544,6 +1546,8 @@ class VmDetailVncTest(LoginMixin, TestCase):
inst
.
node
=
Node
.
objects
.
all
()[
0
]
inst
.
node
=
Node
.
objects
.
all
()[
0
]
inst
.
save
()
inst
.
save
()
inst
.
set_level
(
self
.
u1
,
'operator'
)
inst
.
set_level
(
self
.
u1
,
'operator'
)
self
.
u1
.
user_permissions
.
add
(
Permission
.
objects
.
get
(
codename
=
'access_console'
))
response
=
c
.
get
(
'/dashboard/vm/1/vnctoken/'
)
response
=
c
.
get
(
'/dashboard/vm/1/vnctoken/'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -1554,6 +1558,8 @@ class VmDetailVncTest(LoginMixin, TestCase):
...
@@ -1554,6 +1558,8 @@ class VmDetailVncTest(LoginMixin, TestCase):
inst
.
node
=
Node
.
objects
.
all
()[
0
]
inst
.
node
=
Node
.
objects
.
all
()[
0
]
inst
.
save
()
inst
.
save
()
inst
.
set_level
(
self
.
u1
,
'user'
)
inst
.
set_level
(
self
.
u1
,
'user'
)
self
.
u1
.
user_permissions
.
add
(
Permission
.
objects
.
get
(
codename
=
'access_console'
))
response
=
c
.
get
(
'/dashboard/vm/1/vnctoken/'
)
response
=
c
.
get
(
'/dashboard/vm/1/vnctoken/'
)
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
response
.
status_code
,
403
)
...
...
circle/dashboard/urls.py
View file @
a8fb5a86
...
@@ -28,7 +28,7 @@ from .views import (
...
@@ -28,7 +28,7 @@ from .views import (
NotificationView
,
PortDelete
,
TemplateAclUpdateView
,
TemplateCreate
,
NotificationView
,
PortDelete
,
TemplateAclUpdateView
,
TemplateCreate
,
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TransferOwnershipConfirmView
,
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TransferOwnershipConfirmView
,
TransferOwnershipView
,
vm_activity
,
VmCreate
,
VmDelete
,
VmDetailView
,
TransferOwnershipView
,
vm_activity
,
VmCreate
,
VmDelete
,
VmDetailView
,
VmDetailVncTokenView
,
VmGraphView
,
VmList
,
VmMassDelete
,
VmMigrateView
,
VmDetailVncTokenView
,
VmGraphView
,
VmList
,
VmMassDelete
,
VmRenewView
,
DiskRemoveView
,
get_disk_download_status
,
InterfaceDeleteView
,
VmRenewView
,
DiskRemoveView
,
get_disk_download_status
,
InterfaceDeleteView
,
GroupRemoveAclUserView
,
GroupRemoveAclGroupView
,
GroupRemoveUserView
,
GroupRemoveAclUserView
,
GroupRemoveAclGroupView
,
GroupRemoveUserView
,
GroupRemoveFutureUserView
,
GroupRemoveFutureUserView
,
...
@@ -83,8 +83,6 @@ urlpatterns = patterns(
...
@@ -83,8 +83,6 @@ urlpatterns = patterns(
url
(
r'^vm/mass-delete/'
,
VmMassDelete
.
as_view
(),
url
(
r'^vm/mass-delete/'
,
VmMassDelete
.
as_view
(),
name
=
'dashboard.view.mass-delete-vm'
),
name
=
'dashboard.view.mass-delete-vm'
),
url
(
r'^vm/(?P<pk>\d+)/activity/$'
,
vm_activity
),
url
(
r'^vm/(?P<pk>\d+)/activity/$'
,
vm_activity
),
url
(
r'^vm/(?P<pk>\d+)/migrate/$'
,
VmMigrateView
.
as_view
(),
name
=
'dashboard.views.vm-migrate'
),
url
(
r'^vm/(?P<pk>\d+)/renew/((?P<key>.*)/?)$'
,
VmRenewView
.
as_view
(),
url
(
r'^vm/(?P<pk>\d+)/renew/((?P<key>.*)/?)$'
,
VmRenewView
.
as_view
(),
name
=
'dashboard.views.vm-renew'
),
name
=
'dashboard.views.vm-renew'
),
url
(
r'^vm/activity/(?P<pk>\d+)/$'
,
InstanceActivityDetail
.
as_view
(),
url
(
r'^vm/activity/(?P<pk>\d+)/$'
,
InstanceActivityDetail
.
as_view
(),
...
...
circle/dashboard/views.py
View file @
a8fb5a86
...
@@ -244,6 +244,8 @@ class VmDetailVncTokenView(CheckedDetailView):
...
@@ -244,6 +244,8 @@ class VmDetailVncTokenView(CheckedDetailView):
self
.
object
=
self
.
get_object
()
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
has_level
(
request
.
user
,
'operator'
):
if
not
self
.
object
.
has_level
(
request
.
user
,
'operator'
):
raise
PermissionDenied
()
raise
PermissionDenied
()
if
not
request
.
user
.
has_perm
(
'vm.access_console'
):
raise
PermissionDenied
()
if
self
.
object
.
node
:
if
self
.
object
.
node
:
with
instance_activity
(
code_suffix
=
'console-accessed'
,
with
instance_activity
(
code_suffix
=
'console-accessed'
,
instance
=
self
.
object
,
user
=
request
.
user
,
instance
=
self
.
object
,
user
=
request
.
user
,
...
@@ -294,13 +296,14 @@ class VmDetailView(CheckedDetailView):
...
@@ -294,13 +296,14 @@ class VmDetailView(CheckedDetailView):
if
self
.
request
.
user
.
is_superuser
:
if
self
.
request
.
user
.
is_superuser
:
context
[
'traits_form'
]
=
TraitsForm
(
instance
=
instance
)
context
[
'traits_form'
]
=
TraitsForm
(
instance
=
instance
)
context
[
'raw_data_form'
]
=
RawDataForm
(
instance
=
instance
)
context
[
'raw_data_form'
]
=
RawDataForm
(
instance
=
instance
)
# resources change perm
context
[
'can_change_resources'
]
=
self
.
request
.
user
.
has_perm
(
"vm.change_resources"
)
return
context
return
context
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
if
(
request
.
POST
.
get
(
'ram-size'
)
and
request
.
POST
.
get
(
'cpu-count'
)
and
request
.
POST
.
get
(
'cpu-priority'
)):
return
self
.
__set_resources
(
request
)
options
=
{
options
=
{
'change_password'
:
self
.
__change_password
,
'change_password'
:
self
.
__change_password
,
'new_name'
:
self
.
__set_name
,
'new_name'
:
self
.
__set_name
,
...
@@ -328,33 +331,6 @@ class VmDetailView(CheckedDetailView):
...
@@ -328,33 +331,6 @@ class VmDetailView(CheckedDetailView):
return
redirect
(
reverse_lazy
(
"dashboard.views.detail"
,
return
redirect
(
reverse_lazy
(
"dashboard.views.detail"
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
}))
kwargs
=
{
'pk'
:
self
.
object
.
pk
}))
def
__set_resources
(
self
,
request
):
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
if
not
request
.
user
.
has_perm
(
'vm.change_resources'
):
raise
PermissionDenied
()
resources
=
{
'num_cores'
:
request
.
POST
.
get
(
'cpu-count'
),
'ram_size'
:
request
.
POST
.
get
(
'ram-size'
),
'max_ram_size'
:
request
.
POST
.
get
(
'ram-size'
),
# TODO: max_ram
'priority'
:
request
.
POST
.
get
(
'cpu-priority'
)
}
Instance
.
objects
.
filter
(
pk
=
self
.
object
.
pk
)
.
update
(
**
resources
)
success_message
=
_
(
"Resources successfully updated."
)
if
request
.
is_ajax
():
response
=
{
'message'
:
success_message
}
return
HttpResponse
(
json
.
dumps
(
response
),
content_type
=
"application/json"
)
else
:
messages
.
success
(
request
,
success_message
)
return
redirect
(
reverse_lazy
(
"dashboard.views.detail"
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
}))
def
__set_name
(
self
,
request
):
def
__set_name
(
self
,
request
):
self
.
object
=
self
.
get_object
()
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
...
@@ -606,8 +582,9 @@ class VmOperationView(OperationView):
...
@@ -606,8 +582,9 @@ class VmOperationView(OperationView):
model
=
Instance
model
=
Instance
context_object_name
=
'instance'
# much simpler to mock object
context_object_name
=
'instance'
# much simpler to mock object
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
,
extra
=
None
,
*
args
,
**
kwargs
):
resp
=
super
(
VmOperationView
,
self
)
.
post
(
request
,
*
args
,
**
kwargs
)
resp
=
super
(
VmOperationView
,
self
)
.
post
(
request
,
extra
,
*
args
,
**
kwargs
)
if
request
.
is_ajax
():
if
request
.
is_ajax
():
store
=
messages
.
get_messages
(
request
)
store
=
messages
.
get_messages
(
request
)
store
.
used
=
True
store
.
used
=
True
...
@@ -699,6 +676,29 @@ class VmSaveView(FormOperationMixin, VmOperationView):
...
@@ -699,6 +676,29 @@ class VmSaveView(FormOperationMixin, VmOperationView):
effect
=
'info'
effect
=
'info'
form_class
=
VmSaveForm
form_class
=
VmSaveForm
class
VmResourcesChangeView
(
VmOperationView
):
op
=
'resources_change'
icon
=
"save"
show_in_toolbar
=
False
def
post
(
self
,
request
,
extra
=
None
,
*
args
,
**
kwargs
):
if
extra
is
None
:
extra
=
{}
resources
=
{
'num_cores'
:
"cpu-count"
,
'priority'
:
"cpu-priority"
,
'ram_size'
:
"ram-size"
,
"max_ram_size"
:
"ram-size"
,
# TODO
}
for
k
,
v
in
resources
.
iteritems
():
extra
[
k
]
=
request
.
POST
.
get
(
v
)
return
super
(
VmResourcesChangeView
,
self
)
.
post
(
request
,
extra
,
*
args
,
**
kwargs
)
vm_ops
=
OrderedDict
([
vm_ops
=
OrderedDict
([
(
'deploy'
,
VmOperationView
.
factory
(
(
'deploy'
,
VmOperationView
.
factory
(
op
=
'deploy'
,
icon
=
'play'
,
effect
=
'success'
)),
op
=
'deploy'
,
icon
=
'play'
,
effect
=
'success'
)),
...
@@ -1012,7 +1012,7 @@ class GroupAclUpdateView(AclUpdateView):
...
@@ -1012,7 +1012,7 @@ class GroupAclUpdateView(AclUpdateView):
kwargs
=
self
.
kwargs
))
kwargs
=
self
.
kwargs
))
class
TemplateChoose
(
TemplateView
):
class
TemplateChoose
(
LoginRequiredMixin
,
TemplateView
):
def
get_template_names
(
self
):
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
if
self
.
request
.
is_ajax
():
...
@@ -1045,6 +1045,9 @@ class TemplateChoose(TemplateView):
...
@@ -1045,6 +1045,9 @@ class TemplateChoose(TemplateView):
else
:
else
:
template
=
get_object_or_404
(
InstanceTemplate
,
pk
=
template
)
template
=
get_object_or_404
(
InstanceTemplate
,
pk
=
template
)
if
not
template
.
has_level
(
request
.
user
,
"user"
):
raise
PermissionDenied
()
instance
=
Instance
.
create_from_template
(
instance
=
Instance
.
create_from_template
(
template
=
template
,
owner
=
request
.
user
,
is_base
=
True
)
template
=
template
,
owner
=
request
.
user
,
is_base
=
True
)
...
@@ -1072,7 +1075,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
...
@@ -1072,7 +1075,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return
context
return
context
def
get
(
self
,
*
args
,
**
kwargs
):
def
get
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
request
.
user
.
has_perm
(
'vm.create_template'
):
if
not
self
.
request
.
user
.
has_perm
(
'vm.create_
base_
template'
):
raise
PermissionDenied
()
raise
PermissionDenied
()
return
super
(
TemplateCreate
,
self
)
.
get
(
*
args
,
**
kwargs
)
return
super
(
TemplateCreate
,
self
)
.
get
(
*
args
,
**
kwargs
)
...
@@ -1083,7 +1086,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
...
@@ -1083,7 +1086,7 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return
kwargs
return
kwargs
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
if
not
self
.
request
.
user
.
has_perm
(
'vm.create_template'
):
if
not
self
.
request
.
user
.
has_perm
(
'vm.create_
base_
template'
):
raise
PermissionDenied
()
raise
PermissionDenied
()
form
=
self
.
form_class
(
request
.
POST
,
user
=
request
.
user
)
form
=
self
.
form_class
(
request
.
POST
,
user
=
request
.
user
)
...
@@ -1105,8 +1108,6 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
...
@@ -1105,8 +1108,6 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return
redirect
(
"
%
s#resources"
%
inst
.
get_absolute_url
())
return
redirect
(
"
%
s#resources"
%
inst
.
get_absolute_url
())
return
super
(
TemplateCreate
,
self
)
.
post
(
self
,
request
,
args
,
kwargs
)
def
__create_networks
(
self
,
vlans
,
user
):
def
__create_networks
(
self
,
vlans
,
user
):
networks
=
[]
networks
=
[]
for
v
in
vlans
:
for
v
in
vlans
:
...
@@ -1167,12 +1168,6 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
...
@@ -1167,12 +1168,6 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
template
=
self
.
get_object
()
template
=
self
.
get_object
()
if
not
template
.
has_level
(
request
.
user
,
'owner'
):
if
not
template
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
raise
PermissionDenied
()
for
disk
in
self
.
get_object
()
.
disks
.
all
():
if
not
disk
.
has_level
(
request
.
user
,
'user'
):
raise
PermissionDenied
()
for
network
in
self
.
get_object
()
.
interface_set
.
all
():
if
not
network
.
vlan
.
has_level
(
request
.
user
,
"user"
):
raise
PermissionDenied
()
return
super
(
TemplateDetail
,
self
)
.
post
(
self
,
request
,
args
,
kwargs
)
return
super
(
TemplateDetail
,
self
)
.
post
(
self
,
request
,
args
,
kwargs
)
def
get_form_kwargs
(
self
):
def
get_form_kwargs
(
self
):
...
@@ -1546,6 +1541,9 @@ class VmCreate(LoginRequiredMixin, TemplateView):
...
@@ -1546,6 +1541,9 @@ class VmCreate(LoginRequiredMixin, TemplateView):
return
[
'dashboard/nojs-wrapper.html'
]
return
[
'dashboard/nojs-wrapper.html'
]
def
get
(
self
,
request
,
form
=
None
,
*
args
,
**
kwargs
):
def
get
(
self
,
request
,
form
=
None
,
*
args
,
**
kwargs
):
if
not
request
.
user
.
has_perm
(
'vm.create_vm'
):
raise
PermissionDenied
()
form_error
=
form
is
not
None
form_error
=
form
is
not
None
template
=
(
form
.
template
.
pk
if
form_error
template
=
(
form
.
template
.
pk
if
form_error
else
request
.
GET
.
get
(
"template"
))
else
request
.
GET
.
get
(
"template"
))
...
@@ -1651,6 +1649,9 @@ class VmCreate(LoginRequiredMixin, TemplateView):
...
@@ -1651,6 +1649,9 @@ class VmCreate(LoginRequiredMixin, TemplateView):
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
user
=
request
.
user
user
=
request
.
user
if
not
request
.
user
.
has_perm
(
'vm.create_vm'
):
raise
PermissionDenied
()
# limit chekcs
# limit chekcs
try
:
try
:
limit
=
user
.
profile
.
instance_limit
limit
=
user
.
profile
.
instance_limit
...
...
circle/storage/models.py
View file @
a8fb5a86
...
@@ -106,6 +106,9 @@ class Disk(AclBase, TimeStampedModel):
...
@@ -106,6 +106,9 @@ class Disk(AclBase, TimeStampedModel):
ordering
=
[
'name'
]
ordering
=
[
'name'
]
verbose_name
=
_
(
'disk'
)
verbose_name
=
_
(
'disk'
)
verbose_name_plural
=
_
(
'disks'
)
verbose_name_plural
=
_
(
'disks'
)
permissions
=
(
(
'create_empty_disk'
,
_
(
'Can create an empty disk.'
)),
(
'download_disk'
,
_
(
'Can download a disk.'
)))
class
WrongDiskTypeError
(
Exception
):
class
WrongDiskTypeError
(
Exception
):
...
...
circle/vm/models/instance.py
View file @
a8fb5a86
...
@@ -151,6 +151,10 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
...
@@ -151,6 +151,10 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
ordering
=
(
'name'
,
)
ordering
=
(
'name'
,
)
permissions
=
(
permissions
=
(
(
'create_template'
,
_
(
'Can create an instance template.'
)),
(
'create_template'
,
_
(
'Can create an instance template.'
)),
(
'create_base_template'
,
_
(
'Can create an instance template (base).'
)),
(
'change_template_resources'
,
_
(
'Can change resources of a template.'
)),
)
)
verbose_name
=
_
(
'template'
)
verbose_name
=
_
(
'template'
)
verbose_name_plural
=
_
(
'templates'
)
verbose_name_plural
=
_
(
'templates'
)
...
@@ -263,6 +267,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -263,6 +267,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
(
'access_console'
,
_
(
'Can access the graphical console of a VM.'
)),
(
'access_console'
,
_
(
'Can access the graphical console of a VM.'
)),
(
'change_resources'
,
_
(
'Can change resources of a running VM.'
)),
(
'change_resources'
,
_
(
'Can change resources of a running VM.'
)),
(
'set_resources'
,
_
(
'Can change resources of a new VM.'
)),
(
'set_resources'
,
_
(
'Can change resources of a new VM.'
)),
(
'create_vm'
,
_
(
'Can create a new VM.'
)),
(
'config_ports'
,
_
(
'Can configure port forwards.'
)),
(
'config_ports'
,
_
(
'Can configure port forwards.'
)),
(
'recover'
,
_
(
'Can recover a destroyed VM.'
)),
(
'recover'
,
_
(
'Can recover a destroyed VM.'
)),
)
)
...
...
circle/vm/operations.py
View file @
a8fb5a86
...
@@ -42,6 +42,7 @@ class InstanceOperation(Operation):
...
@@ -42,6 +42,7 @@ class InstanceOperation(Operation):
acl_level
=
'owner'
acl_level
=
'owner'
async_operation
=
abortable_async_instance_operation
async_operation
=
abortable_async_instance_operation
host_cls
=
Instance
host_cls
=
Instance
concurrency_check
=
True
def
__init__
(
self
,
instance
):
def
__init__
(
self
,
instance
):
super
(
InstanceOperation
,
self
)
.
__init__
(
subject
=
instance
)
super
(
InstanceOperation
,
self
)
.
__init__
(
subject
=
instance
)
...
@@ -73,7 +74,7 @@ class InstanceOperation(Operation):
...
@@ -73,7 +74,7 @@ class InstanceOperation(Operation):
else
:
else
:
return
InstanceActivity
.
create
(
return
InstanceActivity
.
create
(
code_suffix
=
self
.
activity_code_suffix
,
instance
=
self
.
instance
,
code_suffix
=
self
.
activity_code_suffix
,
instance
=
self
.
instance
,
user
=
user
)
user
=
user
,
concurrency_check
=
self
.
concurrency_check
)
def
is_preferred
(
self
):
def
is_preferred
(
self
):
"""If this is the recommended op in the current state of the instance.
"""If this is the recommended op in the current state of the instance.
...
@@ -87,6 +88,7 @@ class AddInterfaceOperation(InstanceOperation):
...
@@ -87,6 +88,7 @@ class AddInterfaceOperation(InstanceOperation):
name
=
_
(
"add interface"
)
name
=
_
(
"add interface"
)
description
=
_
(
"Add a new network interface for the specified VLAN to "
description
=
_
(
"Add a new network interface for the specified VLAN to "
"the VM."
)
"the VM."
)
required_perms
=
()
def
_operation
(
self
,
activity
,
user
,
system
,
vlan
,
managed
=
None
):
def
_operation
(
self
,
activity
,
user
,
system
,
vlan
,
managed
=
None
):
if
managed
is
None
:
if
managed
is
None
:
...
@@ -109,6 +111,7 @@ class CreateDiskOperation(InstanceOperation):
...
@@ -109,6 +111,7 @@ class CreateDiskOperation(InstanceOperation):
id
=
'create_disk'
id
=
'create_disk'
name
=
_
(
"create disk"
)
name
=
_
(
"create disk"
)
description
=
_
(
"Create empty disk for the VM."
)
description
=
_
(
"Create empty disk for the VM."
)
required_perms
=
(
'storage.create_empty_disk'
,
)
def
check_precond
(
self
):
def
check_precond
(
self
):
super
(
CreateDiskOperation
,
self
)
.
check_precond
()
super
(
CreateDiskOperation
,
self
)
.
check_precond
()
...
@@ -123,6 +126,7 @@ class CreateDiskOperation(InstanceOperation):
...
@@ -123,6 +126,7 @@ class CreateDiskOperation(InstanceOperation):
if
not
name
:
if
not
name
:
name
=
"new disk"
name
=
"new disk"
disk
=
Disk
.
create
(
size
=
size
,
name
=
name
,
type
=
"qcow2-norm"
)
disk
=
Disk
.
create
(
size
=
size
,
name
=
name
,
type
=
"qcow2-norm"
)
disk
.
full_clean
()
self
.
instance
.
disks
.
add
(
disk
)
self
.
instance
.
disks
.
add
(
disk
)
register_operation
(
CreateDiskOperation
)
register_operation
(
CreateDiskOperation
)
...
@@ -135,6 +139,7 @@ class DownloadDiskOperation(InstanceOperation):
...
@@ -135,6 +139,7 @@ class DownloadDiskOperation(InstanceOperation):
description
=
_
(
"Download disk for the VM."
)
description
=
_
(
"Download disk for the VM."
)
abortable
=
True
abortable
=
True
has_percentage
=
True
has_percentage
=
True
required_perms
=
(
'storage.download_disk'
,
)
def
check_precond
(
self
):
def
check_precond
(
self
):
super
(
DownloadDiskOperation
,
self
)
.
check_precond
()
super
(
DownloadDiskOperation
,
self
)
.
check_precond
()
...
@@ -148,6 +153,7 @@ class DownloadDiskOperation(InstanceOperation):
...
@@ -148,6 +153,7 @@ class DownloadDiskOperation(InstanceOperation):
from
storage.models
import
Disk
from
storage.models
import
Disk
disk
=
Disk
.
download
(
url
=
url
,
name
=
name
,
task
=
task
)
disk
=
Disk
.
download
(
url
=
url
,
name
=
name
,
task
=
task
)
disk
.
full_clean
()
self
.
instance
.
disks
.
add
(
disk
)
self
.
instance
.
disks
.
add
(
disk
)
register_operation
(
DownloadDiskOperation
)
register_operation
(
DownloadDiskOperation
)
...
@@ -158,6 +164,12 @@ class DeployOperation(InstanceOperation):
...
@@ -158,6 +164,12 @@ class DeployOperation(InstanceOperation):
id
=
'deploy'
id
=
'deploy'
name
=
_
(
"deploy"
)
name
=
_
(
"deploy"
)
description
=
_
(
"Deploy new virtual machine with network."
)
description
=
_
(
"Deploy new virtual machine with network."
)
required_perms
=
()
def
check_precond
(
self
):
super
(
DeployOperation
,
self
)
.
check_precond
()
if
self
.
instance
.
status
in
[
'RUNNING'
,
'SUSPENDED'
]:
raise
self
.
instance
.
WrongStateError
(
self
.
instance
)
def
is_preferred
(
self
):
def
is_preferred
(
self
):
return
self
.
instance
.
status
in
(
self
.
instance
.
STATUS
.
STOPPED
,
return
self
.
instance
.
status
in
(
self
.
instance
.
STATUS
.
STOPPED
,
...
@@ -198,6 +210,7 @@ class DestroyOperation(InstanceOperation):
...
@@ -198,6 +210,7 @@ class DestroyOperation(InstanceOperation):
id
=
'destroy'
id
=
'destroy'
name
=
_
(
"destroy"
)
name
=
_
(
"destroy"
)
description
=
_
(
"Destroy virtual machine and its networks."
)
description
=
_
(
"Destroy virtual machine and its networks."
)
required_perms
=
()
def
on_commit
(
self
,
activity
):
def
on_commit
(
self
,
activity
):
activity
.
resultant_state
=
'DESTROYED'
activity
.
resultant_state
=
'DESTROYED'
...
@@ -239,11 +252,23 @@ class MigrateOperation(InstanceOperation):
...
@@ -239,11 +252,23 @@ class MigrateOperation(InstanceOperation):
id
=
'migrate'
id
=
'migrate'
name
=
_
(
"migrate"
)
name
=
_
(
"migrate"
)
description
=
_
(
"Live migrate running VM to another node."
)
description
=
_
(
"Live migrate running VM to another node."
)
required_perms
=
()
def
rollback
(
self
,
activity
):
def
rollback
(
self
,
activity
):
with
activity
.
sub_activity
(
'rollback_net'
):
with
activity
.
sub_activity
(
'rollback_net'
):
self
.
instance
.
deploy_net
()
self
.
instance
.
deploy_net
()
def
check_precond
(
self
):
super
(
MigrateOperation
,
self
)
.
check_precond
()
if
self
.
instance
.
status
not
in
[
'RUNNING'
]:
raise
self
.
instance
.
WrongStateError
(
self
.
instance
)
def
check_auth
(
self
,
user
):
if
not
user
.
is_superuser
:
raise
PermissionDenied
()
super
(
MigrateOperation
,
self
)
.
check_auth
(
user
=
user
)
def
_operation
(
self
,
activity
,
to_node
=
None
,
timeout
=
120
):
def
_operation
(
self
,
activity
,
to_node
=
None
,
timeout
=
120
):
if
not
to_node
:
if
not
to_node
:
with
activity
.
sub_activity
(
'scheduling'
)
as
sa
:
with
activity
.
sub_activity
(
'scheduling'
)
as
sa
:
...
@@ -278,6 +303,12 @@ class RebootOperation(InstanceOperation):
...
@@ -278,6 +303,12 @@ class RebootOperation(InstanceOperation):
id
=
'reboot'
id
=
'reboot'
name
=
_
(
"reboot"
)
name
=
_
(
"reboot"
)
description
=
_
(
"Reboot virtual machine with Ctrl+Alt+Del signal."
)
description
=
_
(
"Reboot virtual machine with Ctrl+Alt+Del signal."
)
required_perms
=
()
def
check_precond
(
self
):
super
(
RebootOperation
,
self
)
.
check_precond
()
if
self
.
instance
.
status
not
in
[
'RUNNING'
]:
raise
self
.
instance
.
WrongStateError
(
self
.
instance
)
def
_operation
(
self
,
timeout
=
5
):
def
_operation
(
self
,
timeout
=
5
):
self
.
instance
.
reboot_vm
(
timeout
=
timeout
)
self
.
instance
.
reboot_vm
(
timeout
=
timeout
)
...
@@ -291,6 +322,7 @@ class RemoveInterfaceOperation(InstanceOperation):
...
@@ -291,6 +322,7 @@ class RemoveInterfaceOperation(InstanceOperation):
id
=
'remove_interface'
id
=
'remove_interface'
name
=
_
(
"remove interface"
)
name
=
_
(
"remove interface"
)
description
=
_
(
"Remove the specified network interface from the VM."
)
description
=
_
(
"Remove the specified network interface from the VM."
)
required_perms
=
()
def
_operation
(
self
,
activity
,
user
,
system
,
interface
):
def
_operation
(
self
,
activity
,
user
,
system
,
interface
):
if
self
.
instance
.
is_running
:
if
self
.
instance
.
is_running
:
...
@@ -308,6 +340,7 @@ class RemoveDiskOperation(InstanceOperation):
...
@@ -308,6 +340,7 @@ class RemoveDiskOperation(InstanceOperation):
id
=
'remove_disk'
id
=
'remove_disk'
name
=
_
(
"remove disk"
)
name
=
_
(
"remove disk"
)
description
=
_
(
"Remove the specified disk from the VM."
)
description
=
_
(
"Remove the specified disk from the VM."
)
required_perms
=
()
def
check_precond
(
self
):
def
check_precond
(
self
):
super
(
RemoveDiskOperation
,
self
)
.
check_precond
()
super
(
RemoveDiskOperation
,
self
)
.
check_precond
()
...
@@ -328,6 +361,12 @@ class ResetOperation(InstanceOperation):
...
@@ -328,6 +361,12 @@ class ResetOperation(InstanceOperation):
id
=
'reset'
id
=
'reset'
name
=
_
(
"reset"
)
name
=
_
(
"reset"
)
description
=
_
(
"Reset virtual machine (reset button)."
)
description
=
_
(
"Reset virtual machine (reset button)."
)
required_perms
=
()
def
check_precond
(
self
):
super
(
ResetOperation
,
self
)
.
check_precond
()
if
self
.
instance
.
status
not
in
[
'RUNNING'
]:
raise
self
.
instance
.
WrongStateError
(
self
.
instance
)
def
_operation
(
self
,
timeout
=
5
):
def
_operation
(
self
,
timeout
=
5
):
self
.
instance
.
reset_vm
(
timeout
=
timeout
)
self
.
instance
.
reset_vm
(
timeout
=
timeout
)
...
@@ -345,6 +384,7 @@ class SaveAsTemplateOperation(InstanceOperation):
...
@@ -345,6 +384,7 @@ class SaveAsTemplateOperation(InstanceOperation):
Users can instantiate Virtual Machines from Templates.
Users can instantiate Virtual Machines from Templates.
"""
)
"""
)
abortable
=
True
abortable
=
True
required_perms
=
(
'vm.create_template'
,
)
def
is_preferred
(
self
):
def
is_preferred
(
self
):
return
(
self
.
instance
.
is_base
and
return
(
self
.
instance
.
is_base
and
...
@@ -365,6 +405,11 @@ class SaveAsTemplateOperation(InstanceOperation):
...
@@ -365,6 +405,11 @@ class SaveAsTemplateOperation(InstanceOperation):
for
disk
in
self
.
disks
:
for
disk
in
self
.
disks
:
disk
.
destroy
()
disk
.
destroy
()
def
check_precond
(
self
):
super
(
SaveAsTemplateOperation
,
self
)
.
check_precond
()
if
self
.
instance
.
status
not
in
[
'RUNNING'
,
'PENDING'
,
'STOPPED'
]:
raise
self
.
instance
.
WrongStateError
(
self
.
instance
)
def
_operation
(
self
,
activity
,
user
,
system
,
timeout
=
300
,
name
=
None
,
def
_operation
(
self
,
activity
,
user
,
system
,
timeout
=
300
,
name
=
None
,
with_shutdown
=
True
,
task
=
None
,
**
kwargs
):
with_shutdown
=
True
,
task
=
None
,
**
kwargs
):
if
with_shutdown
:
if
with_shutdown
:
...
@@ -435,6 +480,7 @@ class ShutdownOperation(InstanceOperation):
...
@@ -435,6 +480,7 @@ class ShutdownOperation(InstanceOperation):
name
=
_
(
"shutdown"
)
name
=
_
(
"shutdown"
)
description
=
_
(
"Shutdown virtual machine with ACPI signal."
)
description
=
_
(
"Shutdown virtual machine with ACPI signal."
)
abortable
=
True
abortable
=
True
required_perms
=
()
def
check_precond
(
self
):
def
check_precond
(
self
):
super
(
ShutdownOperation
,
self
)
.
check_precond
()
super
(
ShutdownOperation
,
self
)
.
check_precond
()
...
@@ -458,6 +504,12 @@ class ShutOffOperation(InstanceOperation):
...
@@ -458,6 +504,12 @@ class ShutOffOperation(InstanceOperation):
id
=
'shut_off'
id
=
'shut_off'
name
=
_
(
"shut off"
)
name
=
_
(
"shut off"
)
description
=
_
(
"Shut off VM (plug-out)."
)
description
=
_
(
"Shut off VM (plug-out)."
)
required_perms
=
()
def
check_precond
(
self
):
super
(
ShutOffOperation
,
self
)
.
check_precond
()
if
self
.
instance
.
status
not
in
[
'RUNNING'
]:
raise
self
.
instance
.
WrongStateError
(
self
.
instance
)
def
on_commit
(
self
,
activity
):
def
on_commit
(
self
,
activity
):
activity
.
resultant_state
=
'STOPPED'
activity
.
resultant_state
=
'STOPPED'
...
@@ -484,6 +536,7 @@ class SleepOperation(InstanceOperation):
...
@@ -484,6 +536,7 @@ class SleepOperation(InstanceOperation):
id
=
'sleep'
id
=
'sleep'
name
=
_
(
"sleep"
)
name
=
_
(
"sleep"
)
description
=
_
(
"Suspend virtual machine with memory dump."
)
description
=
_
(
"Suspend virtual machine with memory dump."
)
required_perms
=
()
def
is_preferred
(
self
):
def
is_preferred
(
self
):
return
(
not
self
.
instance
.
is_base
and
return
(
not
self
.
instance
.
is_base
and
...
@@ -527,6 +580,7 @@ class WakeUpOperation(InstanceOperation):
...
@@ -527,6 +580,7 @@ class WakeUpOperation(InstanceOperation):
Power on Virtual Machine and load its memory from dump.
Power on Virtual Machine and load its memory from dump.
"""
)
"""
)
required_perms
=
()
def
is_preferred
(
self
):
def
is_preferred
(
self
):
return
(
self
.
instance
.
is_base
and
return
(
self
.
instance
.
is_base
and
...
@@ -593,6 +647,7 @@ class FlushOperation(NodeOperation):
...
@@ -593,6 +647,7 @@ class FlushOperation(NodeOperation):
id
=
'flush'
id
=
'flush'
name
=
_
(
"flush"
)
name
=
_
(
"flush"
)
description
=
_
(
"Disable node and move all instances to other ones."
)
description
=
_
(
"Disable node and move all instances to other ones."
)
required_perms
=
()
def
on_abort
(
self
,
activity
,
error
):
def
on_abort
(
self
,
activity
,
error
):
from
manager.scheduler
import
TraitsUnsatisfiableException
from
manager.scheduler
import
TraitsUnsatisfiableException
...
@@ -600,6 +655,12 @@ class FlushOperation(NodeOperation):
...
@@ -600,6 +655,12 @@ class FlushOperation(NodeOperation):
if
self
.
node_enabled
:
if
self
.
node_enabled
:
self
.
node
.
enable
(
activity
.
user
,
activity
)
self
.
node
.
enable
(
activity
.
user
,
activity
)
def
check_auth
(
self
,
user
):
if
not
user
.
is_superuser
:
raise
PermissionDenied
()
super
(
FlushOperation
,
self
)
.
check_auth
(
user
=
user
)
def
_operation
(
self
,
activity
,
user
):
def
_operation
(
self
,
activity
,
user
):
self
.
node_enabled
=
self
.
node
.
enabled
self
.
node_enabled
=
self
.
node
.
enabled
self
.
node
.
disable
(
user
,
activity
)
self
.
node
.
disable
(
user
,
activity
)
...
@@ -617,6 +678,7 @@ class ScreenshotOperation(InstanceOperation):
...
@@ -617,6 +678,7 @@ class ScreenshotOperation(InstanceOperation):
name
=
_
(
"screenshot"
)
name
=
_
(
"screenshot"
)
description
=
_
(
"Get screenshot"
)
description
=
_
(
"Get screenshot"
)
acl_level
=
"owner"
acl_level
=
"owner"
required_perms
=
()
def
check_precond
(
self
):
def
check_precond
(
self
):
super
(
ScreenshotOperation
,
self
)
.
check_precond
()
super
(
ScreenshotOperation
,
self
)
.
check_precond
()
...
@@ -655,3 +717,31 @@ class RecoverOperation(InstanceOperation):
...
@@ -655,3 +717,31 @@ class RecoverOperation(InstanceOperation):
register_operation
(
RecoverOperation
)
register_operation
(
RecoverOperation
)
class
ResourcesOperation
(
InstanceOperation
):
activity_code_suffix
=
'Resources change'
id
=
'resources_change'
name
=
_
(
"resources change"
)
description
=
_
(
"Change resources"
)
acl_level
=
"owner"
concurrency_check
=
False
required_perms
=
(
'vm.change_resources'
,
)
def
check_precond
(
self
):
super
(
ResourcesOperation
,
self
)
.
check_precond
()
if
self
.
instance
.
status
not
in
[
"STOPPED"
,
"PENDING"
]:
raise
self
.
instance
.
WrongStateError
(
self
.
instance
)
def
_operation
(
self
,
user
,
num_cores
,
ram_size
,
max_ram_size
,
priority
):
self
.
instance
.
num_cores
=
num_cores
self
.
instance
.
ram_size
=
ram_size
self
.
instance
.
max_ram_size
=
max_ram_size
self
.
instance
.
priority
=
priority
self
.
instance
.
full_clean
()
self
.
instance
.
save
()
register_operation
(
ResourcesOperation
)
circle/vm/tests/test_models.py
View file @
a8fb5a86
...
@@ -103,6 +103,7 @@ class InstanceTestCase(TestCase):
...
@@ -103,6 +103,7 @@ class InstanceTestCase(TestCase):
inst
=
Mock
(
destroyed_at
=
None
,
spec
=
Instance
)
inst
=
Mock
(
destroyed_at
=
None
,
spec
=
Instance
)
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
node
=
MagicMock
(
spec
=
Node
)
inst
.
node
=
MagicMock
(
spec
=
Node
)
inst
.
status
=
'RUNNING'
migrate_op
=
MigrateOperation
(
inst
)
migrate_op
=
MigrateOperation
(
inst
)
with
patch
(
'vm.models.instance.vm_tasks.migrate'
)
as
migr
:
with
patch
(
'vm.models.instance.vm_tasks.migrate'
)
as
migr
:
act
=
MagicMock
()
act
=
MagicMock
()
...
@@ -118,6 +119,7 @@ class InstanceTestCase(TestCase):
...
@@ -118,6 +119,7 @@ class InstanceTestCase(TestCase):
inst
=
MagicMock
(
destroyed_at
=
None
,
spec
=
Instance
)
inst
=
MagicMock
(
destroyed_at
=
None
,
spec
=
Instance
)
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
node
=
MagicMock
(
spec
=
Node
)
inst
.
node
=
MagicMock
(
spec
=
Node
)
inst
.
status
=
'RUNNING'
migrate_op
=
MigrateOperation
(
inst
)
migrate_op
=
MigrateOperation
(
inst
)
with
patch
(
'vm.models.instance.vm_tasks.migrate'
)
as
migr
:
with
patch
(
'vm.models.instance.vm_tasks.migrate'
)
as
migr
:
inst
.
select_node
.
side_effect
=
AssertionError
inst
.
select_node
.
side_effect
=
AssertionError
...
@@ -133,6 +135,7 @@ class InstanceTestCase(TestCase):
...
@@ -133,6 +135,7 @@ class InstanceTestCase(TestCase):
inst
=
Mock
(
destroyed_at
=
None
,
spec
=
Instance
)
inst
=
Mock
(
destroyed_at
=
None
,
spec
=
Instance
)
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
node
=
MagicMock
(
spec
=
Node
)
inst
.
node
=
MagicMock
(
spec
=
Node
)
inst
.
status
=
'RUNNING'
e
=
Exception
(
'abc'
)
e
=
Exception
(
'abc'
)
setattr
(
e
,
'libvirtError'
,
''
)
setattr
(
e
,
'libvirtError'
,
''
)
inst
.
migrate_vm
.
side_effect
=
e
inst
.
migrate_vm
.
side_effect
=
e
...
@@ -372,6 +375,7 @@ class InstanceActivityTestCase(TestCase):
...
@@ -372,6 +375,7 @@ class InstanceActivityTestCase(TestCase):
node
=
MagicMock
(
spec
=
Node
,
enabled
=
True
)
node
=
MagicMock
(
spec
=
Node
,
enabled
=
True
)
node
.
instance_set
.
all
.
return_value
=
insts
node
.
instance_set
.
all
.
return_value
=
insts
user
=
MagicMock
(
spec
=
User
)
user
=
MagicMock
(
spec
=
User
)
user
.
is_superuser
=
MagicMock
(
return_value
=
True
)
flush_op
=
FlushOperation
(
node
)
flush_op
=
FlushOperation
(
node
)
with
patch
.
object
(
FlushOperation
,
'create_activity'
)
as
create_act
:
with
patch
.
object
(
FlushOperation
,
'create_activity'
)
as
create_act
:
...
@@ -383,6 +387,7 @@ class InstanceActivityTestCase(TestCase):
...
@@ -383,6 +387,7 @@ class InstanceActivityTestCase(TestCase):
node
.
disable
.
assert_called_with
(
user
,
act
)
node
.
disable
.
assert_called_with
(
user
,
act
)
for
i
in
insts
:
for
i
in
insts
:
i
.
migrate
.
assert_called
()
i
.
migrate
.
assert_called
()
user
.
is_superuser
.
assert_called
()
def
test_flush_disabled_wo_user
(
self
):
def
test_flush_disabled_wo_user
(
self
):
insts
=
[
MagicMock
(
spec
=
Instance
,
migrate
=
MagicMock
()),
insts
=
[
MagicMock
(
spec
=
Instance
,
migrate
=
MagicMock
()),
...
...
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