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
bf5b78a0
authored
Oct 14, 2014
by
Csók Tamás
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into issue-218
parents
41d90c7f
345a3659
Show whitespace changes
Inline
Side-by-side
Showing
57 changed files
with
1302 additions
and
628 deletions
+1302
-628
.gitignore
+1
-0
circle/acl/models.py
+1
-1
circle/circle/settings/base.py
+10
-1
circle/common/operations.py
+18
-1
circle/common/tests/test_operations.py
+14
-36
circle/dashboard/forms.py
+83
-2
circle/dashboard/models.py
+3
-0
circle/dashboard/static/dashboard/dashboard.css
+15
-2
circle/dashboard/static/dashboard/dashboard.js
+13
-2
circle/dashboard/static/dashboard/vm-common.js
+2
-10
circle/dashboard/static/dashboard/vm-details.js
+2
-0
circle/dashboard/static/local-logo.png
+0
-0
circle/dashboard/templates/branding.html
+2
-0
circle/dashboard/templates/dashboard/_disk-list-element.html
+20
-18
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
+5
-3
circle/dashboard/templates/dashboard/_vm-migrate.html
+13
-9
circle/dashboard/templates/dashboard/base.html
+1
-1
circle/dashboard/templates/dashboard/group-detail.html
+30
-0
circle/dashboard/templates/dashboard/index-nodes.html
+37
-31
circle/dashboard/templates/dashboard/instanceactivity_detail.html
+20
-0
circle/dashboard/templates/dashboard/node-list/column-vm.html
+1
-1
circle/dashboard/templates/dashboard/template-edit.html
+7
-1
circle/dashboard/templates/dashboard/vm-detail.html
+4
-1
circle/dashboard/templates/dashboard/vm-detail/_network-port-add.html
+2
-1
circle/dashboard/templates/dashboard/vm-detail/home.html
+38
-7
circle/dashboard/templates/dashboard/vm-detail/network.html
+2
-0
circle/dashboard/templates/dashboard/vm-list.html
+12
-1
circle/dashboard/tests/test_mockedviews.py
+32
-15
circle/dashboard/tests/test_views.py
+10
-10
circle/dashboard/views/group.py
+11
-4
circle/dashboard/views/node.py
+2
-0
circle/dashboard/views/template.py
+52
-0
circle/dashboard/views/util.py
+1
-1
circle/dashboard/views/vm.py
+80
-85
circle/fabfile.py
+4
-0
circle/firewall/migrations/0052_auto__chg_field_record_address.py
+202
-0
circle/firewall/models.py
+1
-1
circle/firewall/tasks/local_tasks.py
+20
-13
circle/manager/mancelery.py
+10
-0
circle/manager/moncelery.py
+10
-1
circle/manager/slowcelery.py
+10
-1
circle/network/views.py
+1
-1
circle/storage/models.py
+3
-0
circle/vm/models/__init__.py
+4
-6
circle/vm/models/activity.py
+3
-19
circle/vm/models/instance.py
+24
-174
circle/vm/models/network.py
+2
-3
circle/vm/models/node.py
+6
-5
circle/vm/operations.py
+322
-125
circle/vm/tasks/agent_tasks.py
+11
-1
circle/vm/tasks/local_agent_tasks.py
+73
-16
circle/vm/tests/test_models.py
+15
-12
circle/vm/tests/test_operations.py
+18
-1
miscellaneous/mancelery.conf
+6
-1
miscellaneous/moncelery.conf
+4
-1
miscellaneous/slowcelery.conf
+8
-2
requirements/base.txt
+1
-1
No files found.
.gitignore
View file @
bf5b78a0
...
@@ -23,6 +23,7 @@ celerybeat-schedule
...
@@ -23,6 +23,7 @@ celerybeat-schedule
.coverage
.coverage
*,cover
*,cover
coverage.xml
coverage.xml
.noseids
# Gettext object file:
# Gettext object file:
*.mo
*.mo
...
...
circle/acl/models.py
View file @
bf5b78a0
...
@@ -229,7 +229,7 @@ class AclBase(Model):
...
@@ -229,7 +229,7 @@ class AclBase(Model):
levelfilter
,
levelfilter
,
content_type
=
ct
,
level__weight__gte
=
level
.
weight
)
.
distinct
()
content_type
=
ct
,
level__weight__gte
=
level
.
weight
)
.
distinct
()
clsfilter
=
Q
(
object_level_set__in
=
ols
.
all
())
clsfilter
=
Q
(
object_level_set__in
=
ols
.
all
())
return
cls
.
objects
.
filter
(
clsfilter
)
return
cls
.
objects
.
filter
(
clsfilter
)
.
distinct
()
def
save
(
self
,
*
args
,
**
kwargs
):
def
save
(
self
,
*
args
,
**
kwargs
):
super
(
AclBase
,
self
)
.
save
(
*
args
,
**
kwargs
)
super
(
AclBase
,
self
)
.
save
(
*
args
,
**
kwargs
)
...
...
circle/circle/settings/base.py
View file @
bf5b78a0
...
@@ -431,9 +431,18 @@ LOGIN_REDIRECT_URL = "/"
...
@@ -431,9 +431,18 @@ LOGIN_REDIRECT_URL = "/"
AGENT_DIR
=
get_env_variable
(
AGENT_DIR
=
get_env_variable
(
'DJANGO_AGENT_DIR'
,
join
(
unicode
(
expanduser
(
"~"
)),
'agent'
))
'DJANGO_AGENT_DIR'
,
join
(
unicode
(
expanduser
(
"~"
)),
'agent'
))
# AGENT_DIR is the root directory for the agent.
# The directory structure SHOULD be:
# /home/username/agent
# |-- agent-linux
# | |-- .git
# | +-- ...
# |-- agent-win
# | +-- agent-win-%(version).exe
#
try
:
try
:
git_env
=
{
'GIT_DIR'
:
join
(
AGENT_DIR
,
'.git'
)}
git_env
=
{
'GIT_DIR'
:
join
(
join
(
AGENT_DIR
,
"agent-linux"
)
,
'.git'
)}
AGENT_VERSION
=
check_output
(
AGENT_VERSION
=
check_output
(
(
'git'
,
'log'
,
'-1'
,
r'--pretty=format:
%
h'
,
'HEAD'
),
env
=
git_env
)
(
'git'
,
'log'
,
'-1'
,
r'--pretty=format:
%
h'
,
'HEAD'
),
env
=
git_env
)
except
:
except
:
...
...
circle/common/operations.py
View file @
bf5b78a0
...
@@ -26,6 +26,17 @@ from .models import activity_context, has_suffix, humanize_exception
...
@@ -26,6 +26,17 @@ 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.
"""
"""
...
@@ -36,6 +47,10 @@ class Operation(object):
...
@@ -36,6 +47,10 @@ class Operation(object):
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
)
...
@@ -62,6 +77,8 @@ class Operation(object):
...
@@ -62,6 +77,8 @@ class Operation(object):
parent_activity
=
auxargs
.
pop
(
'parent_activity'
)
parent_activity
=
auxargs
.
pop
(
'parent_activity'
)
if
parent_activity
and
user
is
None
and
not
skip_auth_check
:
if
parent_activity
and
user
is
None
and
not
skip_auth_check
:
user
=
parent_activity
.
user
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
)
...
@@ -232,7 +249,7 @@ class OperatedMixin(object):
...
@@ -232,7 +249,7 @@ class OperatedMixin(object):
operation could be found.
operation could be found.
"""
"""
for
op
in
getattr
(
self
,
operation_registry_name
,
{})
.
itervalues
():
for
op
in
getattr
(
self
,
operation_registry_name
,
{})
.
itervalues
():
if
has_suffix
(
activity_code
,
op
.
activity_code_suffix
):
if
has_suffix
(
activity_code
,
op
.
get_activity_code_suffix
()
):
return
op
(
self
)
return
op
(
self
)
else
:
else
:
return
None
return
None
...
...
circle/common/tests/test_operations.py
View file @
bf5b78a0
...
@@ -27,9 +27,7 @@ class OperationTestCase(TestCase):
...
@@ -27,9 +27,7 @@ class OperationTestCase(TestCase):
class
AbortEx
(
Exception
):
class
AbortEx
(
Exception
):
pass
pass
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
op
.
async_operation
=
MagicMock
(
op
.
async_operation
=
MagicMock
(
apply_async
=
MagicMock
(
side_effect
=
AbortEx
))
apply_async
=
MagicMock
(
side_effect
=
AbortEx
))
...
@@ -44,9 +42,7 @@ class OperationTestCase(TestCase):
...
@@ -44,9 +42,7 @@ class OperationTestCase(TestCase):
class
AbortEx
(
Exception
):
class
AbortEx
(
Exception
):
pass
pass
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
with
patch
.
object
(
Operation
,
'create_activity'
,
side_effect
=
AbortEx
):
with
patch
.
object
(
Operation
,
'create_activity'
,
side_effect
=
AbortEx
):
with
patch
.
object
(
Operation
,
'check_precond'
)
as
chk_pre
:
with
patch
.
object
(
Operation
,
'check_precond'
)
as
chk_pre
:
try
:
try
:
...
@@ -55,9 +51,7 @@ class OperationTestCase(TestCase):
...
@@ -55,9 +51,7 @@ class OperationTestCase(TestCase):
self
.
assertTrue
(
chk_pre
.
called
)
self
.
assertTrue
(
chk_pre
.
called
)
def
test_auth_check_on_non_system_call
(
self
):
def
test_auth_check_on_non_system_call
(
self
):
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
user
=
MagicMock
()
user
=
MagicMock
()
with
patch
.
object
(
Operation
,
'check_auth'
)
as
check_auth
:
with
patch
.
object
(
Operation
,
'check_auth'
)
as
check_auth
:
with
patch
.
object
(
Operation
,
'check_precond'
),
\
with
patch
.
object
(
Operation
,
'check_precond'
),
\
...
@@ -67,9 +61,7 @@ class OperationTestCase(TestCase):
...
@@ -67,9 +61,7 @@ class OperationTestCase(TestCase):
check_auth
.
assert_called_with
(
user
)
check_auth
.
assert_called_with
(
user
)
def
test_no_auth_check_on_system_call
(
self
):
def
test_no_auth_check_on_system_call
(
self
):
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
with
patch
.
object
(
Operation
,
'check_auth'
,
side_effect
=
AssertionError
):
with
patch
.
object
(
Operation
,
'check_auth'
,
side_effect
=
AssertionError
):
with
patch
.
object
(
Operation
,
'check_precond'
),
\
with
patch
.
object
(
Operation
,
'check_precond'
),
\
patch
.
object
(
Operation
,
'create_activity'
),
\
patch
.
object
(
Operation
,
'create_activity'
),
\
...
@@ -77,39 +69,25 @@ class OperationTestCase(TestCase):
...
@@ -77,39 +69,25 @@ class OperationTestCase(TestCase):
op
.
call
(
system
=
True
)
op
.
call
(
system
=
True
)
def
test_no_exception_for_more_arguments_when_operation_takes_kwargs
(
self
):
def
test_no_exception_for_more_arguments_when_operation_takes_kwargs
(
self
):
class
KwargOp
(
Operation
):
op
=
TestOp
(
MagicMock
())
activity_code_suffix
=
'test'
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
id
=
'test'
patch
.
object
(
TestOp
,
'_exec_op'
):
def
_operation
(
self
,
**
kwargs
):
pass
op
=
KwargOp
(
MagicMock
())
with
patch
.
object
(
KwargOp
,
'create_activity'
),
\
patch
.
object
(
KwargOp
,
'_exec_op'
):
op
.
call
(
system
=
True
,
foo
=
42
)
op
.
call
(
system
=
True
,
foo
=
42
)
def
test_exception_for_unexpected_arguments
(
self
):
def
test_exception_for_unexpected_arguments
(
self
):
class
TestOp
(
Operation
):
activity_code_suffix
=
'test'
id
=
'test'
def
_operation
(
self
):
pass
op
=
TestOp
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
patch
.
object
(
TestOp
,
'_exec_op'
):
patch
.
object
(
TestOp
,
'_exec_op'
):
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
,
foo
=
42
)
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
,
bar
=
42
)
def
test_exception_for_missing_arguments
(
self
):
def
test_exception_for_missing_arguments
(
self
):
class
TestOp
(
Operation
):
op
=
TestOp
(
MagicMock
())
activity_code_suffix
=
'test'
with
patch
.
object
(
TestOp
,
'create_activity'
):
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
)
class
TestOp
(
Operation
):
id
=
'test'
id
=
'test'
def
_operation
(
self
,
foo
):
def
_operation
(
self
,
foo
):
pass
pass
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
):
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
)
circle/dashboard/forms.py
View file @
bf5b78a0
...
@@ -18,6 +18,7 @@
...
@@ -18,6 +18,7 @@
from
__future__
import
absolute_import
from
__future__
import
absolute_import
from
datetime
import
timedelta
from
datetime
import
timedelta
from
urlparse
import
urlparse
from
django.contrib.auth.forms
import
(
from
django.contrib.auth.forms
import
(
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
...
@@ -39,6 +40,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
...
@@ -39,6 +40,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
from
django.forms.widgets
import
TextInput
,
HiddenInput
from
django.forms.widgets
import
TextInput
,
HiddenInput
from
django.template
import
Context
from
django.template
import
Context
from
django.template.loader
import
render_to_string
from
django.template.loader
import
render_to_string
from
django.utils.html
import
escape
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
sizefield.widgets
import
FileSizeWidget
from
sizefield.widgets
import
FileSizeWidget
from
django.core.urlresolvers
import
reverse_lazy
from
django.core.urlresolvers
import
reverse_lazy
...
@@ -79,6 +81,12 @@ class VmSaveForm(forms.Form):
...
@@ -79,6 +81,12 @@ class VmSaveForm(forms.Form):
helper
.
form_tag
=
False
helper
.
form_tag
=
False
return
helper
return
helper
def
__init__
(
self
,
*
args
,
**
kwargs
):
default
=
kwargs
.
pop
(
'default'
,
None
)
super
(
VmSaveForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
default
:
self
.
fields
[
'name'
]
.
initial
=
default
class
VmCustomizeForm
(
forms
.
Form
):
class
VmCustomizeForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
widget
=
forms
.
TextInput
(
attrs
=
{
name
=
forms
.
CharField
(
widget
=
forms
.
TextInput
(
attrs
=
{
...
@@ -744,6 +752,20 @@ class VmRenewForm(forms.Form):
...
@@ -744,6 +752,20 @@ class VmRenewForm(forms.Form):
return
helper
return
helper
class
VmMigrateForm
(
forms
.
Form
):
live_migration
=
forms
.
BooleanField
(
required
=
False
,
initial
=
True
,
label
=
_
(
"live migration"
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
default
=
kwargs
.
pop
(
'default'
)
super
(
VmMigrateForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
.
insert
(
0
,
'to_node'
,
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
default
,
required
=
False
,
widget
=
forms
.
RadioSelect
(),
label
=
_
(
"Node"
)))
class
VmStateChangeForm
(
forms
.
Form
):
class
VmStateChangeForm
(
forms
.
Form
):
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
...
@@ -752,6 +774,7 @@ class VmStateChangeForm(forms.Form):
...
@@ -752,6 +774,7 @@ class VmStateChangeForm(forms.Form):
"but don't interrupt any tasks."
))
"but don't interrupt any tasks."
))
new_state
=
forms
.
ChoiceField
(
Instance
.
STATUS
,
label
=
_
(
new_state
=
forms
.
ChoiceField
(
Instance
.
STATUS
,
label
=
_
(
"New status"
))
"New status"
))
reset_node
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Reset node"
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
show_interrupt
=
kwargs
.
pop
(
'show_interrupt'
)
show_interrupt
=
kwargs
.
pop
(
'show_interrupt'
)
...
@@ -769,6 +792,17 @@ class VmStateChangeForm(forms.Form):
...
@@ -769,6 +792,17 @@ class VmStateChangeForm(forms.Form):
return
helper
return
helper
class
RedeployForm
(
forms
.
Form
):
with_emergency_change_state
=
forms
.
BooleanField
(
required
=
False
,
initial
=
True
,
label
=
_
(
"use emergency state change"
))
@property
def
helper
(
self
):
helper
=
FormHelper
(
self
)
helper
.
form_tag
=
False
return
helper
class
VmCreateDiskForm
(
forms
.
Form
):
class
VmCreateDiskForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
size
=
forms
.
CharField
(
size
=
forms
.
CharField
(
...
@@ -776,6 +810,12 @@ class VmCreateDiskForm(forms.Form):
...
@@ -776,6 +810,12 @@ class VmCreateDiskForm(forms.Form):
help_text
=
_
(
'Size of disk to create in bytes or with units '
help_text
=
_
(
'Size of disk to create in bytes or with units '
'like MB or GB.'
))
'like MB or GB.'
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
default
=
kwargs
.
pop
(
'default'
,
None
)
super
(
VmCreateDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
default
:
self
.
fields
[
'name'
]
.
initial
=
default
def
clean_size
(
self
):
def
clean_size
(
self
):
size_in_bytes
=
self
.
cleaned_data
.
get
(
"size"
)
size_in_bytes
=
self
.
cleaned_data
.
get
(
"size"
)
if
not
size_in_bytes
.
isdigit
()
and
len
(
size_in_bytes
)
>
0
:
if
not
size_in_bytes
.
isdigit
()
and
len
(
size_in_bytes
)
>
0
:
...
@@ -827,13 +867,42 @@ class VmDiskResizeForm(forms.Form):
...
@@ -827,13 +867,42 @@ class VmDiskResizeForm(forms.Form):
helper
.
form_tag
=
False
helper
.
form_tag
=
False
if
self
.
disk
:
if
self
.
disk
:
helper
.
layout
=
Layout
(
helper
.
layout
=
Layout
(
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
self
.
disk
),
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
escape
(
self
.
disk
)
),
Field
(
'disk'
),
Field
(
'size'
))
Field
(
'disk'
),
Field
(
'size'
))
return
helper
return
helper
class
VmDiskRemoveForm
(
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
self
.
disk
=
kwargs
.
pop
(
'default'
)
super
(
VmDiskRemoveForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
.
insert
(
0
,
'disk'
,
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
self
.
disk
,
required
=
True
,
empty_label
=
None
,
label
=
_
(
'Disk'
)))
if
self
.
disk
:
self
.
fields
[
'disk'
]
.
widget
=
HiddenInput
()
@property
def
helper
(
self
):
helper
=
FormHelper
(
self
)
helper
.
form_tag
=
False
if
self
.
disk
:
helper
.
layout
=
Layout
(
AnyTag
(
"div"
,
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
escape
(
self
.
disk
)),
css_class
=
"form-group"
,
),
Field
(
"disk"
),
)
return
helper
class
VmDownloadDiskForm
(
forms
.
Form
):
class
VmDownloadDiskForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
)
,
required
=
False
)
url
=
forms
.
CharField
(
label
=
_
(
'URL'
),
validators
=
[
URLValidator
(),
])
url
=
forms
.
CharField
(
label
=
_
(
'URL'
),
validators
=
[
URLValidator
(),
])
@property
@property
...
@@ -842,6 +911,18 @@ class VmDownloadDiskForm(forms.Form):
...
@@ -842,6 +911,18 @@ class VmDownloadDiskForm(forms.Form):
helper
.
form_tag
=
False
helper
.
form_tag
=
False
return
helper
return
helper
def
clean
(
self
):
cleaned_data
=
super
(
VmDownloadDiskForm
,
self
)
.
clean
()
if
not
cleaned_data
[
'name'
]:
if
cleaned_data
[
'url'
]:
cleaned_data
[
'name'
]
=
urlparse
(
cleaned_data
[
'url'
])
.
path
.
split
(
'/'
)[
-
1
]
if
not
cleaned_data
[
'name'
]:
raise
forms
.
ValidationError
(
_
(
"Could not find filename in URL, "
"please specify a name explicitly."
))
return
cleaned_data
class
VmAddInterfaceForm
(
forms
.
Form
):
class
VmAddInterfaceForm
(
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
...
...
circle/dashboard/models.py
View file @
bf5b78a0
...
@@ -236,6 +236,9 @@ class GroupProfile(AclBase):
...
@@ -236,6 +236,9 @@ class GroupProfile(AclBase):
help_text
=
_
(
'Unique identifier of the group at the organization.'
))
help_text
=
_
(
'Unique identifier of the group at the organization.'
))
description
=
TextField
(
blank
=
True
)
description
=
TextField
(
blank
=
True
)
def
__unicode__
(
self
):
return
self
.
group
.
name
def
save
(
self
,
*
args
,
**
kwargs
):
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
org_id
:
if
not
self
.
org_id
:
self
.
org_id
=
None
self
.
org_id
=
None
...
...
circle/dashboard/static/dashboard/dashboard.css
View file @
bf5b78a0
...
@@ -528,7 +528,7 @@ footer a, footer a:hover, footer a:visited {
...
@@ -528,7 +528,7 @@ footer a, footer a:hover, footer a:visited {
}
}
#dashboard-template-list
a
small
{
#dashboard-template-list
a
small
{
max-width
:
50
%
;
max-width
:
45
%
;
float
:
left
;
float
:
left
;
padding-top
:
2px
;
padding-top
:
2px
;
text-overflow
:
ellipsis
;
text-overflow
:
ellipsis
;
...
@@ -974,6 +974,10 @@ textarea[name="new_members"] {
...
@@ -974,6 +974,10 @@ textarea[name="new_members"] {
color
:
orange
;
color
:
orange
;
}
}
#vm-info-pane
{
margin-bottom
:
20px
;
}
.node-list-table
tbody
>
tr
>
td
,
.node-list-table
thead
>
tr
>
th
{
.node-list-table
tbody
>
tr
>
td
,
.node-list-table
thead
>
tr
>
th
{
vertical-align
:
middle
;
vertical-align
:
middle
;
}
}
...
@@ -996,10 +1000,19 @@ textarea[name="new_members"] {
...
@@ -996,10 +1000,19 @@ textarea[name="new_members"] {
max-width
:
100%
;
max-width
:
100%
;
}
}
#vm-list-table
tbody
td
:nth-child
(
3
)
{
#vm-list-table
td
.state
,
#vm-list-table
td
.memory
{
white-space
:
nowrap
;
white-space
:
nowrap
;
}
}
#vm-list-table
td
{
#vm-list-table
td
{
vertical-align
:
middle
;
vertical-align
:
middle
;
}
}
.disk-resize-btn
{
margin-right
:
5px
;
}
#vm-migrate-node-list
li
{
cursor
:
pointer
;
}
circle/dashboard/static/dashboard/dashboard.js
View file @
bf5b78a0
...
@@ -411,6 +411,17 @@ $(function () {
...
@@ -411,6 +411,17 @@ $(function () {
$
(
this
).
removeClass
(
"btn-default"
).
addClass
(
"btn-primary"
);
$
(
this
).
removeClass
(
"btn-default"
).
addClass
(
"btn-primary"
);
return
false
;
return
false
;
});
});
// vm migrate select for node
$
(
document
).
on
(
"click"
,
"#vm-migrate-node-list li"
,
function
(
e
)
{
var
li
=
$
(
this
).
closest
(
'li'
);
if
(
li
.
find
(
'input'
).
attr
(
'disabled'
))
return
true
;
$
(
'#vm-migrate-node-list li'
).
removeClass
(
'panel-primary'
);
li
.
addClass
(
'panel-primary'
).
find
(
'input'
).
prop
(
"checked"
,
true
);
return
true
;
});
});
});
function
generateVmHTML
(
pk
,
name
,
host
,
icon
,
_status
,
fav
,
is_last
)
{
function
generateVmHTML
(
pk
,
name
,
host
,
icon
,
_status
,
fav
,
is_last
)
{
...
@@ -445,7 +456,7 @@ function generateNodeHTML(name, icon, _status, url, is_last) {
...
@@ -445,7 +456,7 @@ function generateNodeHTML(name, icon, _status, url, is_last) {
function
generateNodeTagHTML
(
name
,
icon
,
_status
,
label
,
url
)
{
function
generateNodeTagHTML
(
name
,
icon
,
_status
,
label
,
url
)
{
return
'<a href="'
+
url
+
'" class="label '
+
label
+
'" >'
+
return
'<a href="'
+
url
+
'" class="label '
+
label
+
'" >'
+
'<i class="'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'<i class="
fa
'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'</a> '
;
'</a> '
;
}
}
...
@@ -618,7 +629,7 @@ function addModalConfirmation(func, data) {
...
@@ -618,7 +629,7 @@ function addModalConfirmation(func, data) {
}
}
function
clientInstalledAction
(
location
)
{
function
clientInstalledAction
(
location
)
{
setCookie
(
'downloaded_client'
,
true
,
365
*
24
*
60
*
60
,
"/"
);
setCookie
(
'downloaded_client'
,
true
,
365
*
24
*
60
*
60
*
1000
,
"/"
);
window
.
location
.
href
=
location
;
window
.
location
.
href
=
location
;
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
}
}
...
...
circle/dashboard/static/dashboard/vm-common.js
View file @
bf5b78a0
...
@@ -16,15 +16,6 @@ $(function() {
...
@@ -16,15 +16,6 @@ $(function() {
$
(
'#confirmation-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#confirmation-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#confirmation-modal'
).
remove
();
$
(
'#confirmation-modal'
).
remove
();
});
});
$
(
'#vm-migrate-node-list li'
).
click
(
function
(
e
)
{
var
li
=
$
(
this
).
closest
(
'li'
);
if
(
li
.
find
(
'input'
).
attr
(
'disabled'
))
return
true
;
$
(
'#vm-migrate-node-list li'
).
removeClass
(
'panel-primary'
);
li
.
addClass
(
'panel-primary'
).
find
(
'input'
).
attr
(
'checked'
,
true
);
return
false
;
});
$
(
'#vm-migrate-node-list li input:checked'
).
closest
(
'li'
).
addClass
(
'panel-primary'
);
$
(
'#vm-migrate-node-list li input:checked'
).
closest
(
'li'
).
addClass
(
'panel-primary'
);
}
}
});
});
...
@@ -51,7 +42,8 @@ $(function() {
...
@@ -51,7 +42,8 @@ $(function() {
if
(
data
.
success
)
{
if
(
data
.
success
)
{
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
if
(
data
.
with_reload
)
{
if
(
data
.
with_reload
)
{
location
.
reload
();
// when the activity check stops the page will reload
reload_vm_detail
=
true
;
}
}
/* if there are messages display them */
/* if there are messages display them */
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
bf5b78a0
var
show_all
=
false
;
var
show_all
=
false
;
var
in_progress
=
false
;
var
in_progress
=
false
;
var
activity_hash
=
5
;
var
activity_hash
=
5
;
var
reload_vm_detail
=
false
;
$
(
function
()
{
$
(
function
()
{
/* do we need to check for new activities */
/* do we need to check for new activities */
...
@@ -404,6 +405,7 @@ function checkNewActivity(runs) {
...
@@ -404,6 +405,7 @@ function checkNewActivity(runs) {
);
);
}
else
{
}
else
{
in_progress
=
false
;
in_progress
=
false
;
if
(
reload_vm_detail
)
location
.
reload
();
}
}
$
(
'a[href="#activity"] i'
).
removeClass
(
'fa-spin'
);
$
(
'a[href="#activity"] i'
).
removeClass
(
'fa-spin'
);
},
},
...
...
circle/dashboard/static/local-logo.png
0 → 100644
View file @
bf5b78a0
5.65 KB
circle/dashboard/templates/branding.html
0 → 100644
View file @
bf5b78a0
<img
src=
"{{ STATIC_URL}}dashboard/img/logo.png"
style=
"height: 25px;"
/>
<img
src=
"{{ STATIC_URL}}local-logo.png"
style=
"padding-left: 2px; height: 25px;"
/>
circle/dashboard/templates/dashboard/_disk-list-element.html
View file @
bf5b78a0
{% load i18n %}
{% load i18n %}
{% load sizefieldtags %}
{% load sizefieldtags %}
<i
class=
"fa {% if d.is_downloading %}fa-refresh fa-spin{% else %}fa-file{% if d.failed %}"
style=
"color: #d9534f;{% endif %}{% endif %}"
></i>
<i
class=
"fa fa-file"
></i>
{{ d.name }} (#{{ d.id }}) -
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if not d.is_downloading %}
{% if not d.failed %}
{% if op.remove_disk %}
{% if d.size %}{{ d.size|filesize }}{% endif %}
<span
class=
"operation-wrapper"
>
{% else %}
<a
href=
"{{ op.remove_disk.get_url }}?disk={{d.pk}}"
<div
class=
"label label-danger"
{%
if
user
.
is_superuser
%}
title=
"{{ d.get_latest_activity_result }}"
{%
endif
%}
>
{% trans "failed" %}
</div>
class=
"btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn
{% endif %}
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
{% else %}
<span
class=
"disk-list-disk-percentage"
data-disk-pk=
"{{ d.pk }}"
>
{{ d.get_download_percentage }}
</span>
%{% endif %}
<i
class=
"fa fa-{{ op.remove_disk.icon }}"
></i>
{% trans "Remove" %}
{% if is_owner != False %}
<a
href=
"{% url "
dashboard
.
views
.
disk-remove
"
pk=
d.pk
%}?
next=
{{
request
.
path
}}"
data-disk-pk=
"{{ d.pk }}"
class=
"btn btn-xs btn-danger pull-right disk-remove"
{%
if
not
long_remove
%}
title=
"{% trans "
Remove
"
%}"{%
endif
%}
>
<i
class=
"fa fa-times"
></i>
{% if long_remove %} {% trans "Remove" %}{% endif %}
</a>
</a>
{% if op.resize_disk %}
</span>
{% endif %}
{% if op.resize_disk %}
<span
class=
"operation-wrapper"
>
<span
class=
"operation-wrapper"
>
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-warning pull-right operation"
>
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
<i
class=
"fa fa-arrows-alt"
></i>
{% trans "Resize" %}
class=
"btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
<i
class=
"fa fa-{{ op.resize_disk.icon }}"
></i>
{% trans "Resize" %}
</a>
</a>
</span>
</span>
{% endif %}
{% endif %}
{% endif %}
<div
style=
"clear: both;"
></div>
<div
style=
"clear: both;"
></div>
{% if request.user.is_superuser %}
<small>
{% trans "File name" %}: {{ d.filename }}
</small>
{% endif %}
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
View file @
bf5b78a0
{% extends "dashboard/mass-operate.html" %}
{% extends "dashboard/mass-operate.html" %}
{% load i18n %}
{% load i18n %}
{% load sizefieldtags %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block formfields %}
{% block formfields %}
...
@@ -11,20 +12,20 @@
...
@@ -11,20 +12,20 @@
<label
for=
"migrate-to-none"
>
<label
for=
"migrate-to-none"
>
<strong>
{% trans "Reschedule" %}
</strong>
<strong>
{% trans "Reschedule" %}
</strong>
</label>
</label>
<input
id=
"migrate-to-none"
type=
"radio"
name=
"node"
value=
""
style=
"float: right;"
checked=
"checked"
>
<input
id=
"migrate-to-none"
type=
"radio"
name=
"
to_
node"
value=
""
style=
"float: right;"
checked=
"checked"
>
<span
class=
"vm-migrate-node-property"
>
<span
class=
"vm-migrate-node-property"
>
{% trans "This option will reschedule each virtual machine to the optimal node." %}
{% trans "This option will reschedule each virtual machine to the optimal node." %}
</span>
</span>
<div
style=
"clear: both;"
></div>
<div
style=
"clear: both;"
></div>
</div>
</div>
</li>
</li>
{% for n in
nodes
%}
{% for n in
form.fields.to_node.queryset.all
%}
<li
class=
"panel panel-default mass-migrate-node"
>
<li
class=
"panel panel-default mass-migrate-node"
>
<div
class=
"panel-body"
>
<div
class=
"panel-body"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<strong>
{{ n }}
</strong>
<strong>
{{ n }}
</strong>
</label>
</label>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"node"
value=
"{{ n.pk }}"
style=
"float: right;"
/>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"
to_
node"
value=
"{{ n.pk }}"
style=
"float: right;"
/>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<div
style=
"clear: both;"
></div>
<div
style=
"clear: both;"
></div>
...
@@ -32,5 +33,6 @@
...
@@ -32,5 +33,6 @@
</li>
</li>
{% endfor %}
{% endfor %}
</ul>
</ul>
{{ form.live_migration|as_crispy_field }}
<hr
/>
<hr
/>
{% endblock %}
{% endblock %}
circle/dashboard/templates/dashboard/_vm-migrate.html
View file @
bf5b78a0
{% extends "dashboard/operate.html" %}
{% extends "dashboard/operate.html" %}
{% load i18n %}
{% load i18n %}
{% load sizefieldtags %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block question %}
{% block question %}
<p>
<p>
...
@@ -13,24 +14,27 @@ Choose a compute node to migrate {{obj}} to.
...
@@ -13,24 +14,27 @@ Choose a compute node to migrate {{obj}} to.
{% block formfields %}
{% block formfields %}
<ul
id=
"vm-migrate-node-list"
class=
"list-unstyled"
>
<ul
id=
"vm-migrate-node-list"
class=
"list-unstyled"
>
{% with current=object.node.pk
selected=object.select_node
.pk %}
{% with current=object.node.pk
recommended=form.fields.to_node.initial
.pk %}
{% for n in
nodes
%}
{% for n in
form.fields.to_node.queryset.all
%}
<li
class=
"panel panel-default"
><div
class=
"panel-body"
>
<li
class=
"panel panel-default"
><div
class=
"panel-body"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<strong>
{{ n }}
</strong>
<strong>
{{ n }}
</strong>
<div
class=
"label label-primary"
>
<i
class=
"fa {{n.get_status_icon}}"
></i>
<div
class=
"label label-primary"
>
{{n.get_status_display}}
</div>
<i
class=
"fa {{n.get_status_icon}}"
></i>
{{n.get_status_display}}
</div>
{% if current == n.pk %}
<div
class=
"label label-info"
>
{% trans "current" %}
</div>
{% endif %}
{% if current == n.pk %}
<div
class=
"label label-info"
>
{% trans "current" %}
</div>
{% endif %}
{% if
select
ed == n.pk %}
<div
class=
"label label-success"
>
{% trans "recommended" %}
</div>
{% endif %}
{% if
recommend
ed == n.pk %}
<div
class=
"label label-success"
>
{% trans "recommended" %}
</div>
{% endif %}
</label>
</label>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"node"
value=
"{{ n.pk }}"
style=
"float: right;"
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"
to_
node"
value=
"{{ n.pk }}"
style=
"float: right;"
{%
if
current =
=
n
.
pk
%}
disabled=
"disabled"
{%
endif
%}
{%
if
current =
=
n
.
pk
%}
disabled=
"disabled"
{%
endif
%}
{%
if
selected =
=
n
.
pk
%}
checked=
"checked"
{%
endif
%}
/>
{%
if
recommended =
=
n
.
pk
%}
checked=
"checked"
{%
endif
%}
/>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<div
style=
"clear: both;"
></div>
<div
style=
"clear: both;"
></div>
</
div></
li>
</li>
{% endfor %}
{% endfor %}
{% endwith %}
{% endwith %}
</ul>
</ul>
{{ form.live_migration|as_crispy_field }}
{% endblock %}
{% endblock %}
circle/dashboard/templates/dashboard/base.html
View file @
bf5b78a0
...
@@ -13,7 +13,7 @@
...
@@ -13,7 +13,7 @@
{% block navbar-brand %}
{% block navbar-brand %}
<a
class=
"navbar-brand"
href=
"{% url "
dashboard
.
index
"
%}"
style=
"padding: 10px 15px;"
>
<a
class=
"navbar-brand"
href=
"{% url "
dashboard
.
index
"
%}"
style=
"padding: 10px 15px;"
>
<img
src=
"{{ STATIC_URL}}dashboard/img/logo.png"
style=
"height: 25px;"
/>
{% include "branding.html" %}
</a>
</a>
{% endblock %}
{% endblock %}
...
...
circle/dashboard/templates/dashboard/group-detail.html
View file @
bf5b78a0
...
@@ -52,6 +52,36 @@
...
@@ -52,6 +52,36 @@
<hr
/>
<hr
/>
<h3>
{% trans "Available objects for this group" %}
</h3>
<ul
class=
"dashboard-profile-vm-list fa-ul"
>
{% for i in vm_objects %}
<li>
<a
href=
"{{ i.get_absolute_url }}"
>
<i
class=
"fa fa-li {{ i.get_status_icon }}"
></i>
{{ i }}
</a>
</li>
{% endfor %}
{% for t in template_objects %}
<li>
<a
href=
"{{ t.get_absolute_url }}"
>
<i
class=
"fa fa-li fa-puzzle-piece"
></i>
{{ t }}
</a>
</li>
{% endfor %}
{% for g in group_objects %}
<li>
<a
href=
"{{ g.get_absolute_url }}"
>
<i
class=
"fa fa-li fa-users"
></i>
{{ g }}
</a>
</li>
{% endfor %}
</ul>
<hr
/>
<h3>
{% trans "User list"|capfirst %}
<h3>
{% trans "User list"|capfirst %}
{% if perms.auth.add_user %}
{% if perms.auth.add_user %}
<a
href=
"{% url "
dashboard
.
views
.
create-user
"
group
.
pk
%}"
class=
"btn btn-success pull-right"
>
{% trans "Create user" %}
</a>
<a
href=
"{% url "
dashboard
.
views
.
create-user
"
group
.
pk
%}"
class=
"btn btn-success pull-right"
>
{% trans "Create user" %}
</a>
...
...
circle/dashboard/templates/dashboard/index-nodes.html
View file @
bf5b78a0
{% load i18n %}
{% load i18n %}
<div
class=
"panel panel-default"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<div
class=
"panel-heading"
>
<div
class=
"pull-right toolbar"
>
<div
class=
"pull-right toolbar"
>
<div
class=
"btn-group"
>
<div
class=
"btn-group"
>
...
@@ -7,9 +7,10 @@
...
@@ -7,9 +7,10 @@
data-container=
"body"
><i
class=
"fa fa-dashboard"
></i></a>
data-container=
"body"
><i
class=
"fa fa-dashboard"
></i></a>
<a
href=
"#index-list-view"
data-index-box=
"node"
class=
"btn btn-default btn-xs disabled"
<a
href=
"#index-list-view"
data-index-box=
"node"
class=
"btn btn-default btn-xs disabled"
data-container=
"body"
><i
class=
"fa fa-list"
></i></a>
data-container=
"body"
><i
class=
"fa fa-list"
></i></a>
</div>
</div>
<span
class=
"btn btn-default btn-xs infobtn"
title=
"{% trans "
List
of
compute
nodes
,
also
called
worker
nodes
or
hypervisors
,
which
run
the
virtual
machines
."
%}"
><i
class=
"fa fa-info-circle"
></i></span>
<span
class=
"btn btn-default btn-xs infobtn"
title=
"{% trans "
List
of
compute
nodes
,
also
called
worker
nodes
or
hypervisors
,
which
run
the
virtual
machines
."
%}"
>
<i
class=
"fa fa-info-circle"
></i>
</span>
</div>
</div>
<h3
class=
"no-margin"
>
<h3
class=
"no-margin"
>
<i
class=
"fa fa-sitemap"
></i>
{% trans "Nodes" %}
<i
class=
"fa fa-sitemap"
></i>
{% trans "Nodes" %}
...
@@ -28,12 +29,40 @@
...
@@ -28,12 +29,40 @@
</a>
</a>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
<!-- #node-list-view -->
<div
class=
"panel-body"
id=
"node-graph-view"
style=
"display: none; min-height: 204px;"
>
<p
class=
"pull-right"
>
<input
class=
"knob"
data-fgColor=
"chartreuse"
data-thickness=
".4"
data-width=
"60"
data-height=
"60"
data-readOnly=
"true"
value=
"{% widthratio node_num.running sum_node_num 100 %}"
>
</p>
<p>
<span
class=
"big"
>
<big>
{{ node_num.running }}
</big>
running
</span>
+
<big>
{{ node_num.missing }}
</big>
missing +
<br><big>
{{ node_num.disabled }}
</big>
disabled +
<big>
{{ node_num.offline }}
</big>
offline
</p>
<ul
class=
"list-inline"
id=
"dashboard-node-taglist"
>
{% for i in nodes %}
<a
href=
"{{ i.get_absolute_url }}"
class=
"label {{i.get_status_label}}"
>
<i
class=
"fa {{ i.get_status_icon }}"
title=
"{{ i.get_status_display }}"
></i>
{{ i.name }}
</a>
{% endfor %}
</ul>
<div
class=
"clearfix"
></div>
</div>
<div
href=
"#"
class=
"list-group-item list-group-footer"
>
<div
href=
"#"
class=
"list-group-item list-group-footer"
>
<div
class=
"row"
>
<div
class=
"row"
>
<div
class=
"col-sm-6 col-xs-6 input-group input-group-sm"
>
<div
class=
"col-sm-6 col-xs-6 input-group input-group-sm"
>
<input
id=
"dashboard-node-search-input"
type=
"text"
class=
"form-control"
placeholder=
"{% trans "
Search
..."
%}"
/>
<input
id=
"dashboard-node-search-input"
type=
"text"
class=
"form-control"
placeholder=
"{% trans "
Search
..."
%}"
/>
<div
class=
"input-group-btn"
>
<div
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"form-control btn btn-primary"
title=
"search"
><i
class=
"fa fa-search"
></i></button>
<button
type=
"submit"
class=
"btn btn-primary"
title=
"{% trans "
Search
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-search"
></i>
</button>
</div>
</div>
</div>
</div>
<div
class=
"col-sm-6 text-right"
>
<div
class=
"col-sm-6 text-right"
>
...
@@ -45,33 +74,10 @@
...
@@ -45,33 +74,10 @@
{% trans "list" %}
{% trans "list" %}
{% endif %}
{% endif %}
</a>
</a>
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
><i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a
>
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
>
</div>
<i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</div
>
</a
>
</div>
</div>
</div>
</div>
<div
class=
"panel-body"
id=
"node-graph-view"
style=
"display: none"
>
<p
class=
"pull-right"
>
<input
class=
"knob"
data-fgColor=
"chartreuse"
data-thickness=
".4"
data-width=
"60"
data-height=
"60"
data-readOnly=
"true"
value=
"{% widthratio node_num.running sum_node_num 100 %}"
></p>
<p><span
class=
"big"
><big>
{{ node_num.running }}
</big>
running
</span>
+
<big>
{{ node_num.missing }}
</big>
missing +
<br><big>
{{ node_num.disabled }}
</big>
disabled +
<big>
{{ node_num.offline }}
</big>
offline
</p>
<ul
class=
"list-inline"
id=
"dashboard-node-taglist"
>
{% for i in nodes %}
<a
href=
"{{ i.get_absolute_url }}"
class=
"label {{i.get_status_label}}"
>
<i
class=
"fa {{ i.get_status_icon }}"
title=
"{{ i.get_status_display }}"
></i>
{{ i.name }}
</a>
{% endfor %}
</ul>
<div
class=
"clearfix"
></div>
<div
class=
"row"
>
<div
class=
"col-sm-6 text-right pull-right"
>
{% if more_nodes >= 0 %}
<a
class=
"btn btn-primary btn-xs"
href=
"{% url "
dashboard
.
views
.
node-list
"
%}"
>
<i
class=
"fa fa-chevron-circle-right"
></i>
{% blocktrans with count=more_nodes %}
<strong>
{{count}}
</strong>
more{% endblocktrans %}
</a>
{% endif %}
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
><i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
circle/dashboard/templates/dashboard/instanceactivity_detail.html
View file @
bf5b78a0
...
@@ -58,6 +58,26 @@
...
@@ -58,6 +58,26 @@
<dt>
{% trans "resultant state" %}
</dt>
<dt>
{% trans "resultant state" %}
</dt>
<dd>
{{object.resultant_state|default:'n/a'}}
</dd>
<dd>
{{object.resultant_state|default:'n/a'}}
</dd>
<dt>
{% trans "subactivities" %}
</dt>
{% for s in object.children.all %}
<dd>
<span
{%
if
s
.
result
%}
title=
"{{ s.result|get_text:user }}"
{%
endif
%}
>
<a
href=
"{{ s.get_absolute_url }}"
>
{{ s.readable_name|get_text:user|capfirst }}
</a></span>
–
{% if s.finished %}
{{ s.finished|time:"H:i:s" }}
{% else %}
<i
class=
"fa fa-refresh fa-spin"
class=
"sub-activity-loading-icon"
></i>
{% endif %}
{% if s.has_failed %}
<div
class=
"label label-danger"
>
{% trans "failed" %}
</div>
{% endif %}
</dd>
{% empty %}
<dd>
{% trans "none" %}
</dd>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
circle/dashboard/templates/dashboard/node-list/column-vm.html
View file @
bf5b78a0
{% load i18n %}
{% load i18n %}
<div
id=
"node-list-column-vm"
>
<div
id=
"node-list-column-vm"
>
<a
class=
"real-link"
href=
"{% url "
dashboard
.
views
.
vm-list
"
%}?
s=
node:{{
record
.
name
}}"
>
<a
class=
"real-link"
href=
"{% url "
dashboard
.
views
.
vm-list
"
%}?
s=
node
_exact
:{{
record
.
name
}}"
>
{{ value }}
{{ value }}
</a>
</a>
</div>
</div>
circle/dashboard/templates/dashboard/template-edit.html
View file @
bf5b78a0
...
@@ -86,7 +86,13 @@
...
@@ -86,7 +86,13 @@
{% endif %}
{% endif %}
{% for d in disks %}
{% for d in disks %}
<li>
<li>
{% include "dashboard/_disk-list-element.html" %}
<i
class=
"fa fa-file"
></i>
{{ d.name }} (#{{ d.id }}) -
<a
href=
"{% url "
dashboard
.
views
.
disk-remove
"
pk=
d.pk
%}?
next=
{{
request
.
path
}}"
data-disk-pk=
"{{ d.pk }}"
class=
"btn btn-xs btn-danger pull-right disk-remove"
{%
if
not
long_remove
%}
title=
"{% trans "
Remove
"
%}"{%
endif
%}
>
<i
class=
"fa fa-times"
></i>
{% if long_remove %} {% trans "Remove" %}{% endif %}
</a>
</li>
</li>
{% endfor %}
{% endfor %}
</ul>
</ul>
...
...
circle/dashboard/templates/dashboard/vm-detail.html
View file @
bf5b78a0
...
@@ -47,7 +47,10 @@
...
@@ -47,7 +47,10 @@
<div
class=
"input-group vm-details-home-name"
>
<div
class=
"input-group vm-details-home-name"
>
<input
id=
"vm-details-rename-name"
class=
"form-control input-sm"
name=
"new_name"
type=
"text"
value=
"{{ instance.name }}"
/>
<input
id=
"vm-details-rename-name"
class=
"form-control input-sm"
name=
"new_name"
type=
"text"
value=
"{{ instance.name }}"
/>
<span
class=
"input-group-btn"
>
<span
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-sm vm-details-rename-submit"
>
{% trans "Rename" %}
</button>
<button
type=
"submit"
class=
"btn btn-sm vm-details-rename-submit
{% if not is_operator %}disabled{% endif %}"
>
{% trans "Rename" %}
</button>
</span>
</span>
</div>
</div>
</form>
</form>
...
...
circle/dashboard/templates/dashboard/vm-detail/_network-port-add.html
View file @
bf5b78a0
...
@@ -11,7 +11,8 @@
...
@@ -11,7 +11,8 @@
<span
class=
"input-group-addon"
>
/
</span>
<span
class=
"input-group-addon"
>
/
</span>
<select
class=
"form-control"
name=
"proto"
style=
"width: 70px;"
><option>
tcp
</option><option>
udp
</option></select>
<select
class=
"form-control"
name=
"proto"
style=
"width: 70px;"
><option>
tcp
</option><option>
udp
</option></select>
<div
class=
"input-group-btn"
>
<div
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-success btn-sm"
>
{% trans "Add" %}
</button>
<button
type=
"submit"
class=
"btn btn-success btn-sm
{% if not is_operator %}disabled{% endif %}"
>
{% trans "Add" %}
</button>
</div>
</div>
</div>
</div>
</form>
</form>
...
...
circle/dashboard/templates/dashboard/vm-detail/home.html
View file @
bf5b78a0
...
@@ -6,7 +6,9 @@
...
@@ -6,7 +6,9 @@
<dd><i
class=
"fa fa-{{ os_type_icon }}"
></i>
{{ instance.system }}
</dd>
<dd><i
class=
"fa fa-{{ os_type_icon }}"
></i>
{{ instance.system }}
</dd>
<dt
style=
"margin-top: 5px;"
>
<dt
style=
"margin-top: 5px;"
>
{% trans "Name" %}:
{% trans "Name" %}:
{% if is_operator %}
<a
href=
"#"
class=
"vm-details-home-edit-name-click"
><i
class=
"fa fa-pencil"
></i></a>
<a
href=
"#"
class=
"vm-details-home-edit-name-click"
><i
class=
"fa fa-pencil"
></i></a>
{% endif %}
</dt>
</dt>
<dd>
<dd>
<div
class=
"vm-details-home-edit-name-click"
>
<div
class=
"vm-details-home-edit-name-click"
>
...
@@ -18,8 +20,9 @@
...
@@ -18,8 +20,9 @@
<div
class=
"input-group"
>
<div
class=
"input-group"
>
<input
type=
"text"
name=
"new_name"
value=
"{{ instance.name }}"
class=
"form-control input-sm"
/>
<input
type=
"text"
name=
"new_name"
value=
"{{ instance.name }}"
class=
"form-control input-sm"
/>
<span
class=
"input-group-btn"
>
<span
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-success btn-sm vm-details-rename-submit"
>
<button
type=
"submit"
class=
"btn btn-success btn-sm vm-details-rename-submit
<i
class=
"fa fa-pencil"
></i>
{% trans "Rename" %}
{% if not is_operator %}disabled{% endif %}"
title=
"{% trans "
Rename
"
%}"
>
<i
class=
"fa fa-pencil"
></i>
</button>
</button>
</span>
</span>
</div>
</div>
...
@@ -28,7 +31,9 @@
...
@@ -28,7 +31,9 @@
</dd>
</dd>
<dt
style=
"margin-top: 5px;"
>
<dt
style=
"margin-top: 5px;"
>
{% trans "Description" %}:
{% trans "Description" %}:
{% if is_operator %}
<a
href=
"#"
class=
"vm-details-home-edit-description-click"
><i
class=
"fa fa-pencil"
></i></a>
<a
href=
"#"
class=
"vm-details-home-edit-description-click"
><i
class=
"fa fa-pencil"
></i></a>
{% endif %}
</dt>
</dt>
<dd>
<dd>
{% csrf_token %}
{% csrf_token %}
...
@@ -38,7 +43,8 @@
...
@@ -38,7 +43,8 @@
<div
id=
"vm-details-home-description"
class=
"js-hidden"
>
<div
id=
"vm-details-home-description"
class=
"js-hidden"
>
<form
method=
"POST"
>
<form
method=
"POST"
>
<textarea
name=
"new_description"
class=
"form-control"
>
{{ instance.description }}
</textarea>
<textarea
name=
"new_description"
class=
"form-control"
>
{{ instance.description }}
</textarea>
<button
type=
"submit"
class=
"btn btn-xs btn-success vm-details-description-submit"
>
<button
type=
"submit"
class=
"btn btn-xs btn-success vm-details-description-submit
{% if not is_operator %}disabled{% endif %}"
>
<i
class=
"fa fa-pencil"
></i>
{% trans "Update" %}
<i
class=
"fa fa-pencil"
></i>
{% trans "Update" %}
</button>
</button>
</form>
</form>
...
@@ -58,9 +64,17 @@
...
@@ -58,9 +64,17 @@
</h4>
</h4>
<dl>
<dl>
<dt>
{% trans "Suspended at:" %}
</dt>
<dt>
{% trans "Suspended at:" %}
</dt>
<dd><i
class=
"fa fa-moon-o"
></i>
{{ instance.time_of_suspend|timeuntil }}
</dd>
<dd>
<span
title=
"{{ instance.time_of_suspend }}"
>
<i
class=
"fa fa-moon-o"
></i>
{{ instance.time_of_suspend|timeuntil }}
</span>
</dd>
<dt>
{% trans "Destroyed at:" %}
</dt>
<dt>
{% trans "Destroyed at:" %}
</dt>
<dd><i
class=
"fa fa-times"
></i>
{{ instance.time_of_delete|timeuntil }}
</dd>
<dd>
<span
title=
"{{ instance.time_of_delete }}"
>
<i
class=
"fa fa-times"
></i>
{{ instance.time_of_delete|timeuntil }}
</span>
</dd>
</dl>
</dl>
<div
style=
"font-weight: bold;"
>
{% trans "Tags" %}
</div>
<div
style=
"font-weight: bold;"
>
{% trans "Tags" %}
</div>
...
@@ -70,11 +84,13 @@
...
@@ -70,11 +84,13 @@
{% for t in instance.tags.all %}
{% for t in instance.tags.all %}
<div
class=
"label label-primary label-tag"
style=
"display: inline-block"
>
<div
class=
"label label-primary label-tag"
style=
"display: inline-block"
>
{{ t }}
{{ t }}
{% if is_operator %}
<a
href=
"#"
class=
"vm-details-remove-tag"
><i
class=
"fa fa-times"
></i></a>
<a
href=
"#"
class=
"vm-details-remove-tag"
><i
class=
"fa fa-times"
></i></a>
{% endif %}
</div>
</div>
{% endfor %}
{% endfor %}
{% else %}
{% else %}
<small>
{% trans "No tag added
!
" %}
</small>
<small>
{% trans "No tag added
.
" %}
</small>
{% endif %}
{% endif %}
</div>
</div>
<form
action=
""
method=
"POST"
>
<form
action=
""
method=
"POST"
>
...
@@ -85,11 +101,26 @@
...
@@ -85,11 +101,26 @@
<i class="fa fa-question"></i>
<i class="fa fa-question"></i>
</div>-->
</div>-->
<div
class=
"input-group-btn"
>
<div
class=
"input-group-btn"
>
<input
type=
"submit"
class=
"btn btn-default btn-sm input-tags"
value=
"{% trans "
Add
tag
"
%}"
/>
<input
type=
"submit"
class=
"btn btn-default btn-sm input-tags
{% if not is_operator %}disabled{% endif %}"
value=
"{% trans "
Add
tag
"
%}"
/>
</div>
</div>
</div>
</div>
</form>
</form>
</div>
<!-- id:vm-details-tags -->
</div>
<!-- id:vm-details-tags -->
{% if request.user.is_superuser %}
<dl>
<dt>
{% trans "Node" %}:
</dt>
<dd>
{% if instance.node %}
<a
href=
"{{ instance.node.get_absolute_url }}"
>
{{ instance.node.name }}
</a>
{% else %}
-
{% endif %}
</dd>
{% endif %}
</dl>
<dl>
<dl>
<dt>
{% trans "Template" %}:
</dt>
<dt>
{% trans "Template" %}:
</dt>
<dd>
<dd>
...
...
circle/dashboard/templates/dashboard/vm-detail/network.html
View file @
bf5b78a0
...
@@ -21,11 +21,13 @@
...
@@ -21,11 +21,13 @@
<a
href=
"{{ i.host.get_absolute_url }}"
<a
href=
"{{ i.host.get_absolute_url }}"
class=
"btn btn-default btn-xs"
>
{% trans "edit" %}
</a>
class=
"btn btn-default btn-xs"
>
{% trans "edit" %}
</a>
{% endif %}
{% endif %}
{% if is_owner %}
<a
href=
"{% url "
dashboard
.
views
.
interface-delete
"
pk=
i.pk
%}?
next=
{{
request
.
path
}}"
<a
href=
"{% url "
dashboard
.
views
.
interface-delete
"
pk=
i.pk
%}?
next=
{{
request
.
path
}}"
class=
"btn btn-danger btn-xs interface-remove"
class=
"btn btn-danger btn-xs interface-remove"
data-interface-pk=
"{{ i.pk }}"
>
data-interface-pk=
"{{ i.pk }}"
>
{% trans "remove" %}
{% trans "remove" %}
</a>
</a>
{% endif %}
</h3>
</h3>
{% if i.host %}
{% if i.host %}
<div
class=
"row"
>
<div
class=
"row"
>
...
...
circle/dashboard/templates/dashboard/vm-list.html
View file @
bf5b78a0
...
@@ -72,6 +72,10 @@
...
@@ -72,6 +72,10 @@
{% trans "Lease" as t %}
{% trans "Lease" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="lease" %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="lease" %}
</th>
</th>
<th
data-sort=
"string"
class=
"orderable sortable"
>
{% trans "Memory" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="ram_size" %}
</th>
{% if user.is_superuser %}
{% if user.is_superuser %}
<th
data-sort=
"string"
class=
"orderable sortable"
>
<th
data-sort=
"string"
class=
"orderable sortable"
>
{% trans "IP address" as t %}
{% trans "IP address" as t %}
...
@@ -86,7 +90,9 @@
...
@@ -86,7 +90,9 @@
{% for i in object_list %}
{% for i in object_list %}
<tr
class=
"{% cycle 'odd' 'even' %}"
data-vm-pk=
"{{ i.pk }}"
>
<tr
class=
"{% cycle 'odd' 'even' %}"
data-vm-pk=
"{{ i.pk }}"
>
<td
class=
"pk"
><div
id=
"vm-{{i.pk}}"
>
{{i.pk}}
</div>
</td>
<td
class=
"pk"
><div
id=
"vm-{{i.pk}}"
>
{{i.pk}}
</div>
</td>
<td
class=
"name"
><a
class=
"real-link"
href=
"{% url "
dashboard
.
views
.
detail
"
i
.
pk
%}"
>
{{ i.name }}
</a>
</td>
<td
class=
"name"
><a
class=
"real-link"
href=
"{% url "
dashboard
.
views
.
detail
"
i
.
pk
%}"
>
{{ i.name }}
</a>
</td>
<td
class=
"state"
>
<td
class=
"state"
>
<i
class=
"fa fa-fw
<i
class=
"fa fa-fw
{% if show_acts_in_progress and i.is_in_status_change %}
{% if show_acts_in_progress and i.is_in_status_change %}
...
@@ -104,7 +110,12 @@
...
@@ -104,7 +110,12 @@
{# include "dashboard/_display-name.html" with user=i.owner show_org=True #}
{# include "dashboard/_display-name.html" with user=i.owner show_org=True #}
</td>
</td>
<td
class=
"lease "
data-sort-value=
"{{ i.lease.name }}"
>
<td
class=
"lease "
data-sort-value=
"{{ i.lease.name }}"
>
<span
title=
"{{ i.time_of_suspend|timeuntil }} | {{ i.time_of_delete|timeuntil }}"
>
{{ i.lease.name }}
{{ i.lease.name }}
</span>
</td>
<td
class=
"memory "
data-sort-value=
"{{ i.ram_size }}"
>
{{ i.ram_size }} MiB
</td>
</td>
{% if user.is_superuser %}
{% if user.is_superuser %}
<td
class=
"ip_addr "
data-sort-value=
"{{ i.ipv4 }}"
>
<td
class=
"ip_addr "
data-sort-value=
"{{ i.ipv4 }}"
>
...
...
circle/dashboard/tests/test_mockedviews.py
View file @
bf5b78a0
...
@@ -34,6 +34,13 @@ from ..views import AclUpdateView
...
@@ -34,6 +34,13 @@ from ..views import AclUpdateView
from
..
import
views
from
..
import
views
class
QuerySet
(
list
):
model
=
MagicMock
()
def
get
(
self
,
*
args
,
**
kwargs
):
return
self
.
pop
()
class
ViewUserTestCase
(
unittest
.
TestCase
):
class
ViewUserTestCase
(
unittest
.
TestCase
):
def
test_404
(
self
):
def
test_404
(
self
):
...
@@ -145,58 +152,66 @@ class VmOperationViewTestCase(unittest.TestCase):
...
@@ -145,58 +152,66 @@ class VmOperationViewTestCase(unittest.TestCase):
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
def
test_migrate
(
self
):
def
test_migrate
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'to_node'
:
1
,
'live_migration'
:
True
},
superuser
=
True
)
view
=
vm_ops
[
'migrate'
]
view
=
vm_ops
[
'migrate'
]
node
=
MagicMock
(
pk
=
1
,
name
=
'node1'
)
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
,
\
patch
(
'dashboard.views.vm.get_object_or_404'
)
as
go4
:
patch
.
object
(
view
,
'get_form_kwargs'
)
as
form_kwargs
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
_meta
.
object_name
=
"Instance"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
.
async
=
MagicMock
()
inst
.
migrate
.
async
=
MagicMock
()
inst
.
has_level
.
return_value
=
True
inst
.
has_level
.
return_value
=
True
form_kwargs
.
return_value
=
{
'default'
:
100
,
'choices'
:
QuerySet
([
node
])}
go
.
return_value
=
inst
go
.
return_value
=
inst
go4
.
return_value
=
MagicMock
()
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
not
msg
.
error
.
called
assert
not
msg
.
error
.
called
assert
go4
.
called
inst
.
migrate
.
async
.
assert_called_once_with
(
to_node
=
node
,
live_migration
=
True
,
user
=
request
.
user
)
def
test_migrate_failed
(
self
):
def
test_migrate_failed
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
True
)
view
=
vm_ops
[
'migrate'
]
view
=
vm_ops
[
'migrate'
]
node
=
MagicMock
(
pk
=
1
,
name
=
'node1'
)
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
,
\
patch
(
'dashboard.views.vm.get_object_or_404'
)
as
go4
:
patch
.
object
(
view
,
'get_form_kwargs'
)
as
form_kwargs
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
_meta
.
object_name
=
"Instance"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
.
async
=
MagicMock
()
inst
.
migrate
.
async
=
MagicMock
()
inst
.
migrate
.
async
.
side_effect
=
Exception
inst
.
migrate
.
async
.
side_effect
=
Exception
inst
.
has_level
.
return_value
=
True
inst
.
has_level
.
return_value
=
True
form_kwargs
.
return_value
=
{
'default'
:
100
,
'choices'
:
QuerySet
([
node
])}
go
.
return_value
=
inst
go
.
return_value
=
inst
go4
.
return_value
=
MagicMock
()
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
inst
.
migrate
.
async
.
called
assert
msg
.
error
.
called
assert
msg
.
error
.
called
assert
go4
.
called
def
test_migrate_wo_permission
(
self
):
def
test_migrate_wo_permission
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
False
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
False
)
view
=
vm_ops
[
'migrate'
]
view
=
vm_ops
[
'migrate'
]
node
=
MagicMock
(
pk
=
1
,
name
=
'node1'
)
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.vm.get_object_or_404'
)
as
go4
:
patch
.
object
(
view
,
'get_form_kwargs'
)
as
form_kwargs
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
_meta
.
object_name
=
"Instance"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
.
async
=
MagicMock
()
inst
.
migrate
.
async
=
MagicMock
()
inst
.
has_level
.
return_value
=
True
inst
.
has_level
.
return_value
=
True
form_kwargs
.
return_value
=
{
'default'
:
100
,
'choices'
:
QuerySet
([
node
])}
go
.
return_value
=
inst
go
.
return_value
=
inst
go4
.
return_value
=
MagicMock
()
with
self
.
assertRaises
(
PermissionDenied
):
with
self
.
assertRaises
(
PermissionDenied
):
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
go4
.
called
assert
not
inst
.
migrate
.
async
.
called
def
test_migrate_template
(
self
):
def
test_migrate_template
(
self
):
"""check if GET dialog's template can be rendered"""
"""check if GET dialog's template can be rendered"""
...
@@ -219,6 +234,7 @@ class VmOperationViewTestCase(unittest.TestCase):
...
@@ -219,6 +234,7 @@ class VmOperationViewTestCase(unittest.TestCase):
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
:
patch
(
'dashboard.views.util.messages'
)
as
msg
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
name
=
"asd"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
save_as_template
=
Instance
.
_ops
[
'save_as_template'
](
inst
)
inst
.
save_as_template
=
Instance
.
_ops
[
'save_as_template'
](
inst
)
inst
.
save_as_template
.
async
=
MagicMock
()
inst
.
save_as_template
.
async
=
MagicMock
()
...
@@ -235,6 +251,7 @@ class VmOperationViewTestCase(unittest.TestCase):
...
@@ -235,6 +251,7 @@ class VmOperationViewTestCase(unittest.TestCase):
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
:
patch
(
'dashboard.views.util.messages'
)
as
msg
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
name
=
"asd"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
save_as_template
=
Instance
.
_ops
[
'save_as_template'
](
inst
)
inst
.
save_as_template
=
Instance
.
_ops
[
'save_as_template'
](
inst
)
inst
.
save_as_template
.
async
=
MagicMock
()
inst
.
save_as_template
.
async
=
MagicMock
()
...
@@ -301,7 +318,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
...
@@ -301,7 +318,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
def
test_migrate
(
self
):
def
test_migrate
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
True
)
view
=
vm_mass_ops
[
'migrate'
]
view
=
vm_mass_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
...
@@ -318,7 +335,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
...
@@ -318,7 +335,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
assert
not
msg2
.
error
.
called
assert
not
msg2
.
error
.
called
def
test_migrate_failed
(
self
):
def
test_migrate_failed
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
True
)
view
=
vm_mass_ops
[
'migrate'
]
view
=
vm_mass_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
...
@@ -334,7 +351,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
...
@@ -334,7 +351,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
assert
msg
.
error
.
called
assert
msg
.
error
.
called
def
test_migrate_wo_permission
(
self
):
def
test_migrate_wo_permission
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
False
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
False
)
view
=
vm_mass_ops
[
'migrate'
]
view
=
vm_mass_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
:
with
patch
.
object
(
view
,
'get_object'
)
as
go
:
...
...
circle/dashboard/tests/test_views.py
View file @
bf5b78a0
...
@@ -512,20 +512,20 @@ class VmDetailTest(LoginMixin, TestCase):
...
@@ -512,20 +512,20 @@ class VmDetailTest(LoginMixin, TestCase):
self
.
login
(
c
,
"user2"
)
self
.
login
(
c
,
"user2"
)
with
patch
.
object
(
Instance
,
'select_node'
,
return_value
=
None
),
\
with
patch
.
object
(
Instance
,
'select_node'
,
return_value
=
None
),
\
patch
.
object
(
WakeUpOperation
,
'async'
)
as
new_wake_up
,
\
patch
.
object
(
WakeUpOperation
,
'async'
)
as
new_wake_up
,
\
patch
(
'vm.tasks.vm_tasks.wake_up.apply_async'
)
as
wuaa
,
\
patch
.
object
(
Instance
.
WrongStateError
,
'send_message'
)
as
wro
:
patch
.
object
(
Instance
.
WrongStateError
,
'send_message'
)
as
wro
:
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
new_wake_up
.
side_effect
=
inst
.
wake_up
new_wake_up
.
side_effect
=
inst
.
wake_up
inst
.
_wake_up_vm
=
Mock
()
inst
.
get_remote_queue_name
=
Mock
(
return_value
=
'test'
)
inst
.
get_remote_queue_name
=
Mock
(
return_value
=
'test'
)
inst
.
status
=
'SUSPENDED'
inst
.
status
=
'SUSPENDED'
inst
.
set_level
(
self
.
u2
,
'owner'
)
inst
.
set_level
(
self
.
u2
,
'owner'
)
with
patch
(
'dashboard.views.messages'
)
as
msg
:
with
patch
(
'dashboard.views.messages'
)
as
msg
:
response
=
c
.
post
(
"/dashboard/vm/1/op/wake_up/"
)
response
=
c
.
post
(
"/dashboard/vm/1/op/wake_up/"
)
assert
not
msg
.
error
.
called
assert
not
msg
.
error
.
called
assert
inst
.
_wake_up_vm
.
called
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
inst
.
status
,
'RUNNING'
)
self
.
assertEqual
(
inst
.
status
,
'RUNNING'
)
assert
new_wake_up
.
called
assert
new_wake_up
.
called
assert
wuaa
.
called
assert
not
wro
.
called
assert
not
wro
.
called
def
test_unpermitted_wake_up
(
self
):
def
test_unpermitted_wake_up
(
self
):
...
@@ -1210,7 +1210,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1210,7 +1210,7 @@ class GroupDetailTest(LoginMixin, TestCase):
gp
=
self
.
g1
.
profile
gp
=
self
.
g1
.
profile
acl_users
=
len
(
gp
.
get_users_with_level
())
acl_users
=
len
(
gp
.
get_users_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'user3'
,
'level'
:
'owner'
})
{
'name'
:
'user3'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_users
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
acl_users
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
@@ -1221,7 +1221,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1221,7 +1221,7 @@ class GroupDetailTest(LoginMixin, TestCase):
gp
=
self
.
g1
.
profile
gp
=
self
.
g1
.
profile
acl_users
=
len
(
gp
.
get_users_with_level
())
acl_users
=
len
(
gp
.
get_users_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'user3'
,
'level'
:
'owner'
})
{
'name'
:
'user3'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_users
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
acl_users
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
@@ -1232,7 +1232,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1232,7 +1232,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self
.
login
(
c
,
'superuser'
)
self
.
login
(
c
,
'superuser'
)
acl_users
=
len
(
gp
.
get_users_with_level
())
acl_users
=
len
(
gp
.
get_users_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'user3'
,
'level'
:
'owner'
})
{
'name'
:
'user3'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_users
+
1
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
acl_users
+
1
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
@@ -1243,7 +1243,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1243,7 +1243,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self
.
login
(
c
,
'user0'
)
self
.
login
(
c
,
'user0'
)
acl_users
=
len
(
gp
.
get_users_with_level
())
acl_users
=
len
(
gp
.
get_users_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'user3'
,
'level'
:
'owner'
})
{
'name'
:
'user3'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_users
+
1
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
acl_users
+
1
,
len
(
gp
.
get_users_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
@@ -1253,7 +1253,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1253,7 +1253,7 @@ class GroupDetailTest(LoginMixin, TestCase):
gp
=
self
.
g1
.
profile
gp
=
self
.
g1
.
profile
acl_groups
=
len
(
gp
.
get_groups_with_level
())
acl_groups
=
len
(
gp
.
get_groups_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'group2'
,
'level'
:
'owner'
})
{
'name'
:
'group2'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_groups
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
acl_groups
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
@@ -1264,7 +1264,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1264,7 +1264,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self
.
login
(
c
,
'user3'
)
self
.
login
(
c
,
'user3'
)
acl_groups
=
len
(
gp
.
get_groups_with_level
())
acl_groups
=
len
(
gp
.
get_groups_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'group2'
,
'level'
:
'owner'
})
{
'name'
:
'group2'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_groups
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
acl_groups
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
@@ -1275,7 +1275,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1275,7 +1275,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self
.
login
(
c
,
'superuser'
)
self
.
login
(
c
,
'superuser'
)
acl_groups
=
len
(
gp
.
get_groups_with_level
())
acl_groups
=
len
(
gp
.
get_groups_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'group2'
,
'level'
:
'owner'
})
{
'name'
:
'group2'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_groups
+
1
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
acl_groups
+
1
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
@@ -1286,7 +1286,7 @@ class GroupDetailTest(LoginMixin, TestCase):
...
@@ -1286,7 +1286,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self
.
login
(
c
,
'user0'
)
self
.
login
(
c
,
'user0'
)
acl_groups
=
len
(
gp
.
get_groups_with_level
())
acl_groups
=
len
(
gp
.
get_groups_with_level
())
response
=
c
.
post
(
'/dashboard/group/'
+
response
=
c
.
post
(
'/dashboard/group/'
+
str
(
self
.
g1
.
pk
)
+
'/acl/'
,
str
(
gp
.
pk
)
+
'/acl/'
,
{
'name'
:
'group2'
,
'level'
:
'owner'
})
{
'name'
:
'group2'
,
'level'
:
'owner'
})
self
.
assertEqual
(
acl_groups
+
1
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
acl_groups
+
1
,
len
(
gp
.
get_groups_with_level
()))
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
response
.
status_code
,
302
)
...
...
circle/dashboard/views/group.py
View file @
bf5b78a0
...
@@ -39,6 +39,7 @@ from ..forms import (
...
@@ -39,6 +39,7 @@ from ..forms import (
GroupCreateForm
,
GroupProfileUpdateForm
,
GroupCreateForm
,
GroupProfileUpdateForm
,
)
)
from
..models
import
FutureMember
,
GroupProfile
from
..models
import
FutureMember
,
GroupProfile
from
vm.models
import
Instance
,
InstanceTemplate
from
..tables
import
GroupListTable
from
..tables
import
GroupListTable
from
.util
import
CheckedDetailView
,
AclUpdateView
,
search_user
,
saml_available
from
.util
import
CheckedDetailView
,
AclUpdateView
,
search_user
,
saml_available
...
@@ -100,6 +101,15 @@ class GroupDetailView(CheckedDetailView):
...
@@ -100,6 +101,15 @@ class GroupDetailView(CheckedDetailView):
context
[
'group_profile_form'
]
=
GroupProfileUpdate
.
get_form_object
(
context
[
'group_profile_form'
]
=
GroupProfileUpdate
.
get_form_object
(
self
.
request
,
self
.
object
.
profile
)
self
.
request
,
self
.
object
.
profile
)
context
.
update
({
'group_objects'
:
GroupProfile
.
get_objects_with_group_level
(
"operator"
,
self
.
get_object
()),
'vm_objects'
:
Instance
.
get_objects_with_group_level
(
"user"
,
self
.
get_object
()),
'template_objects'
:
InstanceTemplate
.
get_objects_with_group_level
(
"user"
,
self
.
get_object
()),
})
if
self
.
request
.
user
.
is_superuser
:
if
self
.
request
.
user
.
is_superuser
:
context
[
'group_perm_form'
]
=
GroupPermissionForm
(
context
[
'group_perm_form'
]
=
GroupPermissionForm
(
instance
=
self
.
object
)
instance
=
self
.
object
)
...
@@ -180,10 +190,7 @@ class GroupPermissionsView(SuperuserRequiredMixin, UpdateView):
...
@@ -180,10 +190,7 @@ class GroupPermissionsView(SuperuserRequiredMixin, UpdateView):
class
GroupAclUpdateView
(
AclUpdateView
):
class
GroupAclUpdateView
(
AclUpdateView
):
model
=
Group
model
=
GroupProfile
def
get_object
(
self
):
return
super
(
GroupAclUpdateView
,
self
)
.
get_object
()
.
profile
class
GroupList
(
LoginRequiredMixin
,
SingleTableView
):
class
GroupList
(
LoginRequiredMixin
,
SingleTableView
):
...
...
circle/dashboard/views/node.py
View file @
bf5b78a0
...
@@ -68,6 +68,8 @@ node_ops = OrderedDict([
...
@@ -68,6 +68,8 @@ node_ops = OrderedDict([
op
=
'passivate'
,
icon
=
'play-circle-o'
,
effect
=
'info'
)),
op
=
'passivate'
,
icon
=
'play-circle-o'
,
effect
=
'info'
)),
(
'disable'
,
NodeOperationView
.
factory
(
(
'disable'
,
NodeOperationView
.
factory
(
op
=
'disable'
,
icon
=
'times-circle-o'
,
effect
=
'danger'
)),
op
=
'disable'
,
icon
=
'times-circle-o'
,
effect
=
'danger'
)),
(
'reset'
,
NodeOperationView
.
factory
(
op
=
'reset'
,
icon
=
'stethoscope'
,
effect
=
'danger'
)),
(
'flush'
,
NodeOperationView
.
factory
(
(
'flush'
,
NodeOperationView
.
factory
(
op
=
'flush'
,
icon
=
'paint-brush'
,
effect
=
'danger'
)),
op
=
'flush'
,
icon
=
'paint-brush'
,
effect
=
'danger'
)),
])
])
...
...
circle/dashboard/views/template.py
View file @
bf5b78a0
...
@@ -37,6 +37,7 @@ from braces.views import (
...
@@ -37,6 +37,7 @@ from braces.views import (
from
django_tables2
import
SingleTableView
from
django_tables2
import
SingleTableView
from
vm.models
import
InstanceTemplate
,
InterfaceTemplate
,
Instance
,
Lease
from
vm.models
import
InstanceTemplate
,
InterfaceTemplate
,
Instance
,
Lease
from
storage.models
import
Disk
from
..forms
import
(
from
..forms
import
(
TemplateForm
,
TemplateListSearchForm
,
AclUserOrGroupAddForm
,
LeaseForm
,
TemplateForm
,
TemplateListSearchForm
,
AclUserOrGroupAddForm
,
LeaseForm
,
...
@@ -319,6 +320,57 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
...
@@ -319,6 +320,57 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return
kwargs
return
kwargs
class
DiskRemoveView
(
DeleteView
):
model
=
Disk
def
get_queryset
(
self
):
qs
=
super
(
DiskRemoveView
,
self
)
.
get_queryset
()
return
qs
.
exclude
(
template_set
=
None
)
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
DiskRemoveView
,
self
)
.
get_context_data
(
**
kwargs
)
disk
=
self
.
get_object
()
template
=
disk
.
template_set
.
get
()
if
not
template
.
has_level
(
self
.
request
.
user
,
'owner'
):
raise
PermissionDenied
()
context
[
'title'
]
=
_
(
"Disk remove confirmation"
)
context
[
'text'
]
=
_
(
"Are you sure you want to remove "
"<strong>
%(disk)
s</strong> from "
"<strong>
%(app)
s</strong>?"
%
{
'disk'
:
disk
,
'app'
:
template
}
)
return
context
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
disk
=
self
.
get_object
()
template
=
disk
.
template_set
.
get
()
if
not
template
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
template
.
remove_disk
(
disk
=
disk
,
user
=
request
.
user
)
disk
.
destroy
()
next_url
=
request
.
POST
.
get
(
"next"
)
success_url
=
next_url
if
next_url
else
template
.
get_absolute_url
()
success_message
=
_
(
"Disk successfully removed."
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'message'
:
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
"
%
s#resources"
%
success_url
)
class
LeaseCreate
(
LoginRequiredMixin
,
PermissionRequiredMixin
,
class
LeaseCreate
(
LoginRequiredMixin
,
PermissionRequiredMixin
,
SuccessMessageMixin
,
CreateView
):
SuccessMessageMixin
,
CreateView
):
model
=
Lease
model
=
Lease
...
...
circle/dashboard/views/util.py
View file @
bf5b78a0
...
@@ -184,7 +184,7 @@ class OperationView(RedirectToLoginMixin, DetailView):
...
@@ -184,7 +184,7 @@ class OperationView(RedirectToLoginMixin, DetailView):
@classmethod
@classmethod
def
get_urlname
(
cls
):
def
get_urlname
(
cls
):
return
'dashboard.
vm.op.
%
s'
%
cls
.
op
return
'dashboard.
%
s.op.
%
s'
%
(
cls
.
model
.
_meta
.
model_name
,
cls
.
op
)
@classmethod
@classmethod
def
get_instance_url
(
cls
,
pk
,
key
=
None
,
*
args
,
**
kwargs
):
def
get_instance_url
(
cls
,
pk
,
key
=
None
,
*
args
,
**
kwargs
):
...
...
circle/dashboard/views/vm.py
View file @
bf5b78a0
...
@@ -45,9 +45,10 @@ from common.models import (
...
@@ -45,9 +45,10 @@ from common.models import (
create_readable
,
HumanReadableException
,
fetch_human_exception
,
create_readable
,
HumanReadableException
,
fetch_human_exception
,
)
)
from
firewall.models
import
Vlan
,
Host
,
Rule
from
firewall.models
import
Vlan
,
Host
,
Rule
from
manager.scheduler
import
SchedulerError
from
storage.models
import
Disk
from
storage.models
import
Disk
from
vm.models
import
(
from
vm.models
import
(
Instance
,
instance_activity
,
InstanceActivity
,
Node
,
Lease
,
Instance
,
InstanceActivity
,
Node
,
Lease
,
InstanceTemplate
,
InterfaceTemplate
,
Interface
,
InstanceTemplate
,
InterfaceTemplate
,
Interface
,
)
)
from
.util
import
(
from
.util
import
(
...
@@ -58,7 +59,8 @@ from ..forms import (
...
@@ -58,7 +59,8 @@ from ..forms import (
AclUserOrGroupAddForm
,
VmResourcesForm
,
TraitsForm
,
RawDataForm
,
AclUserOrGroupAddForm
,
VmResourcesForm
,
TraitsForm
,
RawDataForm
,
VmAddInterfaceForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
VmSaveForm
,
VmAddInterfaceForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
VmSaveForm
,
VmRenewForm
,
VmStateChangeForm
,
VmListSearchForm
,
VmCustomizeForm
,
VmRenewForm
,
VmStateChangeForm
,
VmListSearchForm
,
VmCustomizeForm
,
TransferOwnershipForm
,
VmDiskResizeForm
,
TransferOwnershipForm
,
VmDiskResizeForm
,
RedeployForm
,
VmDiskRemoveForm
,
VmMigrateForm
,
)
)
from
..models
import
Favourite
,
Profile
from
..models
import
Favourite
,
Profile
...
@@ -76,10 +78,10 @@ class VmDetailVncTokenView(CheckedDetailView):
...
@@ -76,10 +78,10 @@ class VmDetailVncTokenView(CheckedDetailView):
if
not
request
.
user
.
has_perm
(
'vm.access_console'
):
if
not
request
.
user
.
has_perm
(
'vm.access_console'
):
raise
PermissionDenied
()
raise
PermissionDenied
()
if
self
.
object
.
node
:
if
self
.
object
.
node
:
with
instance_
activity
(
with
self
.
object
.
activity
(
code_suffix
=
'console-accessed'
,
instance
=
self
.
object
,
code_suffix
=
'console-accessed'
,
user
=
request
.
user
,
user
=
request
.
user
,
readable_name
=
ugettext_noop
(
readable_name
=
ugettext_noop
(
"console access"
),
"console access"
),
concurrency_check
=
False
):
concurrency_check
=
False
):
port
=
self
.
object
.
vnc_port
port
=
self
.
object
.
vnc_port
host
=
str
(
self
.
object
.
node
.
host
.
ipv4
)
host
=
str
(
self
.
object
.
node
.
host
.
ipv4
)
value
=
signing
.
dumps
({
'host'
:
host
,
'port'
:
port
},
value
=
signing
.
dumps
({
'host'
:
host
,
'port'
:
port
},
...
@@ -97,6 +99,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -97,6 +99,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
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
)
context
.
update
({
context
.
update
({
'graphite_enabled'
:
settings
.
GRAPHITE_URL
is
not
None
,
'graphite_enabled'
:
settings
.
GRAPHITE_URL
is
not
None
,
...
@@ -152,9 +156,11 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -152,9 +156,11 @@ class VmDetailView(GraphMixin, CheckedDetailView):
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'
]
=
(
context
[
'can_link_template'
]
=
instance
.
template
and
is_operator
instance
.
template
and
instance
.
template
.
has_level
(
user
,
"operator"
)
)
# is operator/owner
context
[
'is_operator'
]
=
is_operator
context
[
'is_owner'
]
=
is_owner
return
context
return
context
...
@@ -174,7 +180,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -174,7 +180,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
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
,
"operator"
):
raise
PermissionDenied
()
raise
PermissionDenied
()
new_name
=
request
.
POST
.
get
(
"new_name"
)
new_name
=
request
.
POST
.
get
(
"new_name"
)
Instance
.
objects
.
filter
(
pk
=
self
.
object
.
pk
)
.
update
(
Instance
.
objects
.
filter
(
pk
=
self
.
object
.
pk
)
.
update
(
...
@@ -197,7 +203,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -197,7 +203,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
def
__set_description
(
self
,
request
):
def
__set_description
(
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
,
"operator"
):
raise
PermissionDenied
()
raise
PermissionDenied
()
new_description
=
request
.
POST
.
get
(
"new_description"
)
new_description
=
request
.
POST
.
get
(
"new_description"
)
...
@@ -221,7 +227,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -221,7 +227,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
def
__add_tag
(
self
,
request
):
def
__add_tag
(
self
,
request
):
new_tag
=
request
.
POST
.
get
(
'new_tag'
)
new_tag
=
request
.
POST
.
get
(
'new_tag'
)
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
,
"operator"
):
raise
PermissionDenied
()
raise
PermissionDenied
()
if
len
(
new_tag
)
<
1
:
if
len
(
new_tag
)
<
1
:
...
@@ -243,7 +249,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -243,7 +249,7 @@ class VmDetailView(GraphMixin, CheckedDetailView):
try
:
try
:
to_remove
=
request
.
POST
.
get
(
'to_remove'
)
to_remove
=
request
.
POST
.
get
(
'to_remove'
)
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
,
"operator"
):
raise
PermissionDenied
()
raise
PermissionDenied
()
self
.
object
.
tags
.
remove
(
to_remove
)
self
.
object
.
tags
.
remove
(
to_remove
)
...
@@ -262,8 +268,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -262,8 +268,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
def
__add_port
(
self
,
request
):
def
__add_port
(
self
,
request
):
object
=
self
.
get_object
()
object
=
self
.
get_object
()
if
(
not
object
.
has_level
(
request
.
user
,
'owner'
)
or
if
not
(
object
.
has_level
(
request
.
user
,
"operator"
)
and
not
request
.
user
.
has_perm
(
'vm.config_ports'
)):
request
.
user
.
has_perm
(
'vm.config_ports'
)):
raise
PermissionDenied
()
raise
PermissionDenied
()
port
=
request
.
POST
.
get
(
"port"
)
port
=
request
.
POST
.
get
(
"port"
)
...
@@ -365,11 +371,9 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
...
@@ -365,11 +371,9 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
return
val
return
val
class
VmDiskResizeView
(
FormOperationMixin
,
VmOperationView
):
class
VmDiskModifyView
(
FormOperationMixin
,
VmOperationView
):
op
=
'resize_disk'
form_class
=
VmDiskResizeForm
show_in_toolbar
=
False
show_in_toolbar
=
False
with_reload
=
True
icon
=
'arrows-alt'
icon
=
'arrows-alt'
effect
=
"success"
effect
=
"success"
...
@@ -384,7 +388,7 @@ class VmDiskResizeView(FormOperationMixin, VmOperationView):
...
@@ -384,7 +388,7 @@ class VmDiskResizeView(FormOperationMixin, VmOperationView):
else
:
else
:
default
=
None
default
=
None
val
=
super
(
VmDisk
Resize
View
,
self
)
.
get_form_kwargs
()
val
=
super
(
VmDisk
Modify
View
,
self
)
.
get_form_kwargs
()
val
.
update
({
'choices'
:
choices
,
'default'
:
default
})
val
.
update
({
'choices'
:
choices
,
'default'
:
default
})
return
val
return
val
...
@@ -397,6 +401,14 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView):
...
@@ -397,6 +401,14 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView):
icon
=
'hdd-o'
icon
=
'hdd-o'
effect
=
"success"
effect
=
"success"
is_disk_operation
=
True
is_disk_operation
=
True
with_reload
=
True
def
get_form_kwargs
(
self
):
op
=
self
.
get_op
()
val
=
super
(
VmCreateDiskView
,
self
)
.
get_form_kwargs
()
num
=
op
.
instance
.
disks
.
count
()
+
1
val
[
'default'
]
=
"
%
s
%
d"
%
(
op
.
instance
.
name
,
num
)
return
val
class
VmDownloadDiskView
(
FormOperationMixin
,
VmOperationView
):
class
VmDownloadDiskView
(
FormOperationMixin
,
VmOperationView
):
...
@@ -407,29 +419,31 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView):
...
@@ -407,29 +419,31 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView):
icon
=
'download'
icon
=
'download'
effect
=
"success"
effect
=
"success"
is_disk_operation
=
True
is_disk_operation
=
True
with_reload
=
True
class
VmMigrateView
(
VmOperationView
):
class
VmMigrateView
(
FormOperationMixin
,
VmOperationView
):
op
=
'migrate'
op
=
'migrate'
icon
=
'truck'
icon
=
'truck'
effect
=
'info'
effect
=
'info'
template_name
=
'dashboard/_vm-migrate.html'
template_name
=
'dashboard/_vm-migrate.html'
form_class
=
VmMigrateForm
def
get_context_data
(
self
,
**
kwargs
):
def
get_form_kwargs
(
self
):
ctx
=
super
(
VmMigrateView
,
self
)
.
get_context_data
(
**
kwargs
)
online
=
(
n
.
pk
for
n
in
Node
.
objects
.
filter
(
enabled
=
True
)
if
n
.
online
)
ctx
[
'nodes'
]
=
[
n
for
n
in
Node
.
objects
.
filter
(
enabled
=
True
)
choices
=
Node
.
objects
.
filter
(
pk__in
=
online
)
if
n
.
online
]
default
=
None
return
ctx
inst
=
self
.
get_object
()
try
:
if
isinstance
(
inst
,
Instance
):
default
=
inst
.
select_node
()
except
SchedulerError
:
logger
.
exception
(
"scheduler error:"
)
def
post
(
self
,
request
,
extra
=
None
,
*
args
,
**
kwargs
):
val
=
super
(
VmMigrateView
,
self
)
.
get_form_kwargs
()
if
extra
is
None
:
val
.
update
({
'choices'
:
choices
,
'default'
:
default
})
extra
=
{}
return
val
node
=
self
.
request
.
POST
.
get
(
"node"
)
if
node
:
node
=
get_object_or_404
(
Node
,
pk
=
node
)
extra
[
"to_node"
]
=
node
return
super
(
VmMigrateView
,
self
)
.
post
(
request
,
extra
,
*
args
,
**
kwargs
)
class
VmSaveView
(
FormOperationMixin
,
VmOperationView
):
class
VmSaveView
(
FormOperationMixin
,
VmOperationView
):
...
@@ -439,6 +453,12 @@ class VmSaveView(FormOperationMixin, VmOperationView):
...
@@ -439,6 +453,12 @@ class VmSaveView(FormOperationMixin, VmOperationView):
effect
=
'info'
effect
=
'info'
form_class
=
VmSaveForm
form_class
=
VmSaveForm
def
get_form_kwargs
(
self
):
op
=
self
.
get_op
()
val
=
super
(
VmSaveView
,
self
)
.
get_form_kwargs
()
val
[
'default'
]
=
op
.
_rename
(
op
.
instance
.
name
)
return
val
class
VmResourcesChangeView
(
VmOperationView
):
class
VmResourcesChangeView
(
VmOperationView
):
op
=
'resources_change'
op
=
'resources_change'
...
@@ -599,6 +619,15 @@ class VmStateChangeView(FormOperationMixin, VmOperationView):
...
@@ -599,6 +619,15 @@ class VmStateChangeView(FormOperationMixin, VmOperationView):
return
val
return
val
class
RedeployView
(
FormOperationMixin
,
VmOperationView
):
op
=
'redeploy'
icon
=
'stethoscope'
effect
=
'danger'
show_in_toolbar
=
True
form_class
=
RedeployForm
wait_for_result
=
0.5
vm_ops
=
OrderedDict
([
vm_ops
=
OrderedDict
([
(
'deploy'
,
VmOperationView
.
factory
(
(
'deploy'
,
VmOperationView
.
factory
(
op
=
'deploy'
,
icon
=
'play'
,
effect
=
'success'
)),
op
=
'deploy'
,
icon
=
'play'
,
effect
=
'success'
)),
...
@@ -620,12 +649,18 @@ vm_ops = OrderedDict([
...
@@ -620,12 +649,18 @@ vm_ops = OrderedDict([
(
'recover'
,
VmOperationView
.
factory
(
(
'recover'
,
VmOperationView
.
factory
(
op
=
'recover'
,
icon
=
'medkit'
,
effect
=
'warning'
)),
op
=
'recover'
,
icon
=
'medkit'
,
effect
=
'warning'
)),
(
'nostate'
,
VmStateChangeView
),
(
'nostate'
,
VmStateChangeView
),
(
'redeploy'
,
RedeployView
),
(
'destroy'
,
VmOperationView
.
factory
(
(
'destroy'
,
VmOperationView
.
factory
(
extra_bases
=
[
TokenOperationView
],
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'
,
VmDiskResizeView
),
(
'resize_disk'
,
VmDiskModifyView
.
factory
(
op
=
'resize_disk'
,
form_class
=
VmDiskResizeForm
,
icon
=
'arrows-alt'
,
effect
=
"warning"
)),
(
'remove_disk'
,
VmDiskModifyView
.
factory
(
op
=
'remove_disk'
,
form_class
=
VmDiskRemoveForm
,
icon
=
'times'
,
effect
=
"danger"
)),
(
'add_interface'
,
VmAddInterfaceView
),
(
'add_interface'
,
VmAddInterfaceView
),
(
'renew'
,
VmRenewView
),
(
'renew'
,
VmRenewView
),
(
'resources_change'
,
VmResourcesChangeView
),
(
'resources_change'
,
VmResourcesChangeView
),
...
@@ -727,6 +762,12 @@ class MassOperationView(OperationView):
...
@@ -727,6 +762,12 @@ class MassOperationView(OperationView):
self
.
check_auth
()
self
.
check_auth
()
if
extra
is
None
:
if
extra
is
None
:
extra
=
{}
extra
=
{}
if
hasattr
(
self
,
'form_class'
):
form
=
self
.
form_class
(
self
.
request
.
POST
,
**
self
.
get_form_kwargs
())
if
form
.
is_valid
():
extra
.
update
(
form
.
cleaned_data
)
self
.
_call_operations
(
extra
)
self
.
_call_operations
(
extra
)
if
request
.
is_ajax
():
if
request
.
is_ajax
():
store
=
messages
.
get_messages
(
request
)
store
=
messages
.
get_messages
(
request
)
...
@@ -765,6 +806,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
...
@@ -765,6 +806,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
allowed_filters
=
{
allowed_filters
=
{
'name'
:
"name__icontains"
,
'name'
:
"name__icontains"
,
'node'
:
"node__name__icontains"
,
'node'
:
"node__name__icontains"
,
'node_exact'
:
"node__name"
,
'status'
:
"status__iexact"
,
'status'
:
"status__iexact"
,
'tags[]'
:
"tags__name__in"
,
'tags[]'
:
"tags__name__in"
,
'tags'
:
"tags__name__in"
,
# for search string
'tags'
:
"tags__name__in"
,
# for search string
...
@@ -850,8 +892,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
...
@@ -850,8 +892,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
in
[
i
.
name
for
i
in
Instance
.
_meta
.
fields
]
+
[
"pk"
]):
in
[
i
.
name
for
i
in
Instance
.
_meta
.
fields
]
+
[
"pk"
]):
queryset
=
queryset
.
order_by
(
sort
)
queryset
=
queryset
.
order_by
(
sort
)
return
queryset
.
filter
(
return
queryset
.
filter
(
**
self
.
get_queryset_filters
())
.
prefetch_related
(
**
self
.
get_queryset_filters
())
.
prefetch_related
(
"owner"
,
"node"
,
"owner__profile"
,
"interface_set"
,
"lease"
,
"owner"
,
"node"
,
"owner__profile"
,
"interface_set"
,
"lease"
,
"interface_set__host"
)
.
distinct
()
"interface_set__host"
)
.
distinct
()
...
@@ -1089,51 +1130,6 @@ class InstanceActivityDetail(CheckedDetailView):
...
@@ -1089,51 +1130,6 @@ class InstanceActivityDetail(CheckedDetailView):
return
ctx
return
ctx
class
DiskRemoveView
(
DeleteView
):
model
=
Disk
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
DiskRemoveView
,
self
)
.
get_context_data
(
**
kwargs
)
disk
=
self
.
get_object
()
app
=
disk
.
get_appliance
()
context
[
'title'
]
=
_
(
"Disk remove confirmation"
)
context
[
'text'
]
=
_
(
"Are you sure you want to remove "
"<strong>
%(disk)
s</strong> from "
"<strong>
%(app)
s</strong>?"
%
{
'disk'
:
disk
,
'app'
:
app
}
)
return
context
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
disk
=
self
.
get_object
()
app
=
disk
.
get_appliance
()
if
not
app
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
app
.
remove_disk
(
disk
=
disk
,
user
=
request
.
user
)
disk
.
destroy
()
next_url
=
request
.
POST
.
get
(
"next"
)
success_url
=
next_url
if
next_url
else
app
.
get_absolute_url
()
success_message
=
_
(
"Disk successfully removed."
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'message'
:
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
"
%
s#resources"
%
success_url
)
@require_GET
@require_GET
def
get_disk_download_status
(
request
,
pk
):
def
get_disk_download_status
(
request
,
pk
):
disk
=
Disk
.
objects
.
get
(
pk
=
pk
)
disk
=
Disk
.
objects
.
get
(
pk
=
pk
)
...
@@ -1381,9 +1377,8 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View):
...
@@ -1381,9 +1377,8 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View):
instance
,
owner
=
self
.
get_instance
(
key
,
request
.
user
)
instance
,
owner
=
self
.
get_instance
(
key
,
request
.
user
)
old
=
instance
.
owner
old
=
instance
.
owner
with
instance_activity
(
code_suffix
=
'ownership-transferred'
,
with
instance
.
activity
(
code_suffix
=
'ownership-transferred'
,
concurrency_check
=
False
,
concurrency_check
=
False
,
user
=
request
.
user
):
instance
=
instance
,
user
=
request
.
user
):
instance
.
owner
=
request
.
user
instance
.
owner
=
request
.
user
instance
.
clean
()
instance
.
clean
()
instance
.
save
()
instance
.
save
()
...
...
circle/fabfile.py
View file @
bf5b78a0
...
@@ -84,6 +84,10 @@ def make_messages():
...
@@ -84,6 +84,10 @@ def make_messages():
def
test
(
test
=
""
):
def
test
(
test
=
""
):
"Run portal tests"
"Run portal tests"
with
_workon
(
"circle"
),
cd
(
"~/circle/circle"
):
with
_workon
(
"circle"
),
cd
(
"~/circle/circle"
):
if
test
==
"f"
:
test
=
"--failed"
else
:
test
+=
" --with-id"
run
(
"./manage.py test --settings=circle.settings.test
%
s"
%
test
)
run
(
"./manage.py test --settings=circle.settings.test
%
s"
%
test
)
...
...
circle/firewall/migrations/0052_auto__chg_field_record_address.py
0 → 100644
View file @
bf5b78a0
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Changing field 'Record.address'
db
.
alter_column
(
u'firewall_record'
,
'address'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
400
))
def
backwards
(
self
,
orm
):
# Changing field 'Record.address'
db
.
alter_column
(
u'firewall_record'
,
'address'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
200
))
models
=
{
u'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
u'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"(u'content_type__app_label', u'content_type__model', u'codename')"
,
'unique_together'
:
"((u'content_type', u'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['contenttypes.ContentType']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
u'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Permission']"
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
u'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
u'firewall.blacklistitem'
:
{
'Meta'
:
{
'object_name'
:
'BlacklistItem'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Host']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv4'
:
(
'django.db.models.fields.GenericIPAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'39'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'reason'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'snort_message'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'tempban'"
,
'max_length'
:
'10'
})
},
u'firewall.domain'
:
{
'Meta'
:
{
'object_name'
:
'Domain'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'ttl'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'600'
})
},
u'firewall.ethernetdevice'
:
{
'Meta'
:
{
'object_name'
:
'EthernetDevice'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'switch_port'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'ethernet_devices'"
,
'to'
:
u"orm['firewall.SwitchPort']"
})
},
u'firewall.firewall'
:
{
'Meta'
:
{
'object_name'
:
'Firewall'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
})
},
u'firewall.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'firewall.host'
:
{
'Meta'
:
{
'ordering'
:
"('normalized_hostname', 'vlan')"
,
'unique_together'
:
"(('hostname', 'vlan'),)"
,
'object_name'
:
'Host'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'external_ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Group']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'hostname'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'ipv6'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'location'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'mac'
:
(
'firewall.fields.MACAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'17'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'normalized_hostname'
:
(
'common.models.HumanSortField'
,
[],
{
'default'
:
"''"
,
'maximum_number_length'
:
'4'
,
'max_length'
:
'80'
,
'monitor'
:
"'hostname'"
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'reverse'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'shared_ip'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Vlan']"
})
},
u'firewall.record'
:
{
'Meta'
:
{
'ordering'
:
"('domain', 'name')"
,
'object_name'
:
'Record'
},
'address'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'400'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'domain'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Domain']"
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Host']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'ttl'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'600'
}),
'type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'6'
})
},
u'firewall.rule'
:
{
'Meta'
:
{
'ordering'
:
"('direction', 'proto', 'sport', 'dport', 'nat_external_port', 'host')"
,
'object_name'
:
'Rule'
},
'action'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'drop'"
,
'max_length'
:
'10'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'direction'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'3'
}),
'dport'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'extra'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'firewall'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Firewall']"
}),
'foreign_network'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'ForeignRules'"
,
'to'
:
u"orm['firewall.VlanGroup']"
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Host']"
}),
'hostgroup'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'nat'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'nat_external_ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'nat_external_port'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'proto'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'sport'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Vlan']"
}),
'vlangroup'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.VlanGroup']"
}),
'weight'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'30000'
})
},
u'firewall.switchport'
:
{
'Meta'
:
{
'object_name'
:
'SwitchPort'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'tagged_vlans'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'tagged_ports'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.VlanGroup']"
}),
'untagged_vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'untagged_ports'"
,
'to'
:
u"orm['firewall.Vlan']"
})
},
u'firewall.vlan'
:
{
'Meta'
:
{
'object_name'
:
'Vlan'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'dhcp_pool'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'domain'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Domain']"
}),
'host_ipv6_prefixlen'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'112'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv6_template'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'2001:738:2001:4031:
%(b)
d:
%(c)
d:
%(d)
d:0'"
}),
'managed'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'network4'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
}),
'network6'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'network_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'portforward'"
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'reverse_domain'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'
%(d)
d.
%(c)
d.
%(b)
d.
%(a)
d.in-addr.arpa'"
}),
'snat_ip'
:
(
'django.db.models.fields.GenericIPAddressField'
,
[],
{
'max_length'
:
'39'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'snat_to'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Vlan']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vid'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'unique'
:
'True'
})
},
u'firewall.vlangroup'
:
{
'Meta'
:
{
'object_name'
:
'VlanGroup'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vlans'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Vlan']"
,
'null'
:
'True'
,
'blank'
:
'True'
})
}
}
complete_apps
=
[
'firewall'
]
\ No newline at end of file
circle/firewall/models.py
View file @
bf5b78a0
...
@@ -874,7 +874,7 @@ class Record(models.Model):
...
@@ -874,7 +874,7 @@ class Record(models.Model):
verbose_name
=
_
(
'host'
))
verbose_name
=
_
(
'host'
))
type
=
models
.
CharField
(
max_length
=
6
,
choices
=
CHOICES_type
,
type
=
models
.
CharField
(
max_length
=
6
,
choices
=
CHOICES_type
,
verbose_name
=
_
(
'type'
))
verbose_name
=
_
(
'type'
))
address
=
models
.
CharField
(
max_length
=
2
00
,
address
=
models
.
CharField
(
max_length
=
4
00
,
verbose_name
=
_
(
'address'
))
verbose_name
=
_
(
'address'
))
ttl
=
models
.
IntegerField
(
default
=
600
,
verbose_name
=
_
(
'ttl'
))
ttl
=
models
.
IntegerField
(
default
=
600
,
verbose_name
=
_
(
'ttl'
))
owner
=
models
.
ForeignKey
(
User
,
verbose_name
=
_
(
'owner'
))
owner
=
models
.
ForeignKey
(
User
,
verbose_name
=
_
(
'owner'
))
...
...
circle/firewall/tasks/local_tasks.py
View file @
bf5b78a0
...
@@ -29,26 +29,24 @@ settings = django.conf.settings.FIREWALL_SETTINGS
...
@@ -29,26 +29,24 @@ settings = django.conf.settings.FIREWALL_SETTINGS
logger
=
getLogger
(
__name__
)
logger
=
getLogger
(
__name__
)
def
_apply_once
(
name
,
queues
,
task
,
data
):
def
_apply_once
(
name
,
tasks
,
queues
,
task
,
data
):
"""Reload given networking component if needed.
"""Reload given networking component if needed.
"""
"""
lockname
=
"
%
s_lock"
%
name
if
name
not
in
tasks
:
if
not
cache
.
get
(
lockname
):
return
return
cache
.
delete
(
lockname
)
data
=
data
()
data
=
data
()
for
queue
in
queues
:
for
queue
in
queues
:
try
:
try
:
task
.
apply_async
(
args
=
data
,
queue
=
queue
,
expires
=
60
)
.
get
(
timeout
=
5
)
task
.
apply_async
(
args
=
data
,
queue
=
queue
,
expires
=
60
)
.
get
(
timeout
=
2
)
logger
.
info
(
"
%
s configuration is reloaded. (queue:
%
s)"
,
logger
.
info
(
"
%
s configuration is reloaded. (queue:
%
s)"
,
name
,
queue
)
name
,
queue
)
except
TimeoutError
as
e
:
except
TimeoutError
as
e
:
logger
.
critical
(
'
%
s (queue:
%
s
)'
,
e
,
queu
e
)
logger
.
critical
(
'
%
s (queue:
%
s
, task:
%
s)'
,
e
,
queue
,
nam
e
)
except
:
except
:
logger
.
critical
(
'Unhandled exception: queue:
%
s data:
%
s'
,
logger
.
critical
(
'Unhandled exception: queue:
%
s data:
%
s
task:
%
s
'
,
queue
,
data
,
exc_info
=
True
)
queue
,
data
,
name
,
exc_info
=
True
)
def
get_firewall_queues
():
def
get_firewall_queues
():
...
@@ -68,19 +66,28 @@ def reloadtask_worker():
...
@@ -68,19 +66,28 @@ def reloadtask_worker():
from
remote_tasks
import
(
reload_dns
,
reload_dhcp
,
reload_firewall
,
from
remote_tasks
import
(
reload_dns
,
reload_dhcp
,
reload_firewall
,
reload_firewall_vlan
,
reload_blacklist
)
reload_firewall_vlan
,
reload_blacklist
)
tasks
=
[]
for
i
in
(
'dns'
,
'dhcp'
,
'firewall'
,
'firewall_vlan'
,
'blacklist'
):
lockname
=
"
%
s_lock"
%
i
if
cache
.
get
(
lockname
):
tasks
.
append
(
i
)
cache
.
delete
(
lockname
)
logger
.
info
(
"reloadtask_worker: Reload
%
s"
,
", "
.
join
(
tasks
))
firewall_queues
=
get_firewall_queues
()
firewall_queues
=
get_firewall_queues
()
dns_queues
=
[(
"
%
s.dns"
%
i
)
for
i
in
dns_queues
=
[(
"
%
s.dns"
%
i
)
for
i
in
settings
.
get
(
'dns_queues'
,
[
gethostname
()])]
settings
.
get
(
'dns_queues'
,
[
gethostname
()])]
_apply_once
(
'dns'
,
dns_queues
,
reload_dns
,
_apply_once
(
'dns'
,
tasks
,
dns_queues
,
reload_dns
,
lambda
:
(
dns
(),
))
lambda
:
(
dns
(),
))
_apply_once
(
'dhcp'
,
firewall_queues
,
reload_dhcp
,
_apply_once
(
'dhcp'
,
tasks
,
firewall_queues
,
reload_dhcp
,
lambda
:
(
dhcp
(),
))
lambda
:
(
dhcp
(),
))
_apply_once
(
'firewall'
,
firewall_queues
,
reload_firewall
,
_apply_once
(
'firewall'
,
tasks
,
firewall_queues
,
reload_firewall
,
lambda
:
(
BuildFirewall
()
.
build_ipt
()))
lambda
:
(
BuildFirewall
()
.
build_ipt
()))
_apply_once
(
'firewall_vlan'
,
firewall_queues
,
reload_firewall_vlan
,
_apply_once
(
'firewall_vlan'
,
tasks
,
firewall_queues
,
reload_firewall_vlan
,
lambda
:
(
vlan
(),
))
lambda
:
(
vlan
(),
))
_apply_once
(
'blacklist'
,
firewall_queues
,
reload_blacklist
,
_apply_once
(
'blacklist'
,
tasks
,
firewall_queues
,
reload_blacklist
,
lambda
:
(
list
(
ipset
()),
))
lambda
:
(
list
(
ipset
()),
))
...
...
circle/manager/mancelery.py
View file @
bf5b78a0
...
@@ -16,12 +16,15 @@
...
@@ -16,12 +16,15 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
celery
import
Celery
from
celery
import
Celery
from
celery.signals
import
worker_ready
from
datetime
import
timedelta
from
datetime
import
timedelta
from
kombu
import
Queue
,
Exchange
from
kombu
import
Queue
,
Exchange
from
os
import
getenv
from
os
import
getenv
HOSTNAME
=
"localhost"
HOSTNAME
=
"localhost"
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
QUEUE_NAME
=
HOSTNAME
+
'.man'
celery
=
Celery
(
'manager'
,
celery
=
Celery
(
'manager'
,
broker
=
getenv
(
"AMQP_URI"
),
broker
=
getenv
(
"AMQP_URI"
),
...
@@ -57,3 +60,10 @@ celery.conf.update(
...
@@ -57,3 +60,10 @@ celery.conf.update(
}
}
)
)
@worker_ready.connect
()
def
cleanup_tasks
(
conf
=
None
,
**
kwargs
):
'''Discard all task and clean up activity.'''
from
vm.models.activity
import
cleanup
cleanup
(
queue_name
=
QUEUE_NAME
)
circle/manager/moncelery.py
View file @
bf5b78a0
...
@@ -16,12 +16,14 @@
...
@@ -16,12 +16,14 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
celery
import
Celery
from
celery
import
Celery
from
celery.signals
import
worker_ready
from
datetime
import
timedelta
from
datetime
import
timedelta
from
kombu
import
Queue
,
Exchange
from
kombu
import
Queue
,
Exchange
from
os
import
getenv
from
os
import
getenv
HOSTNAME
=
"localhost"
HOSTNAME
=
"localhost"
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
QUEUE_NAME
=
HOSTNAME
+
'.monitor'
celery
=
Celery
(
'monitor'
,
celery
=
Celery
(
'monitor'
,
broker
=
getenv
(
"AMQP_URI"
),
broker
=
getenv
(
"AMQP_URI"
),
...
@@ -34,7 +36,7 @@ celery.conf.update(
...
@@ -34,7 +36,7 @@ celery.conf.update(
CELERY_CACHE_BACKEND
=
CACHE_URI
,
CELERY_CACHE_BACKEND
=
CACHE_URI
,
CELERY_TASK_RESULT_EXPIRES
=
300
,
CELERY_TASK_RESULT_EXPIRES
=
300
,
CELERY_QUEUES
=
(
CELERY_QUEUES
=
(
Queue
(
HOSTNAME
+
'.monitor'
,
Exchange
(
'monitor'
,
type
=
'direct'
),
Queue
(
QUEUE_NAME
,
Exchange
(
'monitor'
,
type
=
'direct'
),
routing_key
=
"monitor"
),
routing_key
=
"monitor"
),
),
),
CELERYBEAT_SCHEDULE
=
{
CELERYBEAT_SCHEDULE
=
{
...
@@ -70,3 +72,10 @@ celery.conf.update(
...
@@ -70,3 +72,10 @@ celery.conf.update(
}
}
)
)
@worker_ready.connect
()
def
cleanup_tasks
(
conf
=
None
,
**
kwargs
):
'''Discard all task and clean up activity.'''
from
vm.models.activity
import
cleanup
cleanup
(
queue_name
=
QUEUE_NAME
)
circle/manager/slowcelery.py
View file @
bf5b78a0
...
@@ -16,12 +16,14 @@
...
@@ -16,12 +16,14 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
celery
import
Celery
from
celery
import
Celery
from
celery.signals
import
worker_ready
from
datetime
import
timedelta
from
datetime
import
timedelta
from
kombu
import
Queue
,
Exchange
from
kombu
import
Queue
,
Exchange
from
os
import
getenv
from
os
import
getenv
HOSTNAME
=
"localhost"
HOSTNAME
=
"localhost"
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
QUEUE_NAME
=
HOSTNAME
+
'.man.slow'
celery
=
Celery
(
'manager.slow'
,
celery
=
Celery
(
'manager.slow'
,
broker
=
getenv
(
"AMQP_URI"
),
broker
=
getenv
(
"AMQP_URI"
),
...
@@ -36,7 +38,7 @@ celery.conf.update(
...
@@ -36,7 +38,7 @@ celery.conf.update(
CELERY_CACHE_BACKEND
=
CACHE_URI
,
CELERY_CACHE_BACKEND
=
CACHE_URI
,
CELERY_TASK_RESULT_EXPIRES
=
300
,
CELERY_TASK_RESULT_EXPIRES
=
300
,
CELERY_QUEUES
=
(
CELERY_QUEUES
=
(
Queue
(
HOSTNAME
+
'.man.slow'
,
Exchange
(
'manager.slow'
,
type
=
'direct'
),
Queue
(
QUEUE_NAME
,
Exchange
(
'manager.slow'
,
type
=
'direct'
),
routing_key
=
"manager.slow"
),
routing_key
=
"manager.slow"
),
),
),
CELERYBEAT_SCHEDULE
=
{
CELERYBEAT_SCHEDULE
=
{
...
@@ -48,3 +50,10 @@ celery.conf.update(
...
@@ -48,3 +50,10 @@ celery.conf.update(
}
}
)
)
@worker_ready.connect
()
def
cleanup_tasks
(
conf
=
None
,
**
kwargs
):
'''Discard all task and clean up activity.'''
from
vm.models.activity
import
cleanup
cleanup
(
queue_name
=
QUEUE_NAME
)
circle/network/views.py
View file @
bf5b78a0
...
@@ -657,7 +657,7 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
...
@@ -657,7 +657,7 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
context
=
super
(
VlanDetail
,
self
)
.
get_context_data
(
**
kwargs
)
context
=
super
(
VlanDetail
,
self
)
.
get_context_data
(
**
kwargs
)
q
=
Host
.
objects
.
filter
(
interface__in
=
Interface
.
objects
.
filter
(
q
=
Host
.
objects
.
filter
(
interface__in
=
Interface
.
objects
.
filter
(
vlan
=
self
.
object
,
instance__destroyed_at
=
None
vlan
=
self
.
object
))
))
context
[
'host_list'
]
=
SmallHostTable
(
q
)
context
[
'host_list'
]
=
SmallHostTable
(
q
)
...
...
circle/storage/models.py
View file @
bf5b78a0
...
@@ -490,6 +490,9 @@ class Disk(TimeStampedModel):
...
@@ -490,6 +490,9 @@ class Disk(TimeStampedModel):
disk
.
destroy
()
disk
.
destroy
()
raise
humanize_exception
(
ugettext_noop
(
raise
humanize_exception
(
ugettext_noop
(
"Operation aborted by user."
),
e
)
"Operation aborted by user."
),
e
)
except
:
disk
.
destroy
()
raise
disk
.
is_ready
=
True
disk
.
is_ready
=
True
disk
.
save
()
disk
.
save
()
return
disk
return
disk
circle/vm/models/__init__.py
View file @
bf5b78a0
# flake8: noqa
# flake8: noqa
from
.activity
import
InstanceActivity
from
.activity
import
InstanceActivity
from
.activity
import
instance_activity
from
.activity
import
NodeActivity
from
.activity
import
NodeActivity
from
.activity
import
node_activity
from
.activity
import
node_activity
from
.common
import
BaseResourceConfigModel
from
.common
import
BaseResourceConfigModel
from
.common
import
Lease
from
.common
import
Lease
from
.common
import
NamedBaseResourceConfig
from
.common
import
NamedBaseResourceConfig
from
.common
import
Trait
from
.common
import
Trait
from
.instance
import
InstanceActiveManager
from
.instance
import
VirtualMachineDescModel
from
.instance
import
VirtualMachineDescModel
from
.instance
import
InstanceTemplate
from
.instance
import
InstanceTemplate
from
.instance
import
Instance
from
.instance
import
Instance
...
@@ -19,9 +17,9 @@ from .network import Interface
...
@@ -19,9 +17,9 @@ from .network import Interface
from
.node
import
Node
from
.node
import
Node
__all__
=
[
__all__
=
[
'InstanceActivity'
,
'
InstanceActiveManager'
,
'
BaseResourceConfigModel'
,
'InstanceActivity'
,
'BaseResourceConfigModel'
,
'NamedBaseResourceConfig'
,
'VirtualMachineDescModel'
,
'InstanceTemplate'
,
'NamedBaseResourceConfig'
,
'VirtualMachineDescModel'
,
'InstanceTemplate'
,
'Instance'
,
'
instance_activity'
,
'post_state_changed'
,
'pre_state_changed
'
,
'Instance'
,
'
post_state_changed'
,
'pre_state_changed'
,
'InterfaceTemplate
'
,
'Interface
Template'
,
'Interface'
,
'Trait'
,
'Node'
,
'NodeActivity'
,
'Lease
'
,
'Interface
'
,
'Trait'
,
'Node'
,
'NodeActivity'
,
'Lease'
,
'node_activity
'
,
'
node_activity'
,
'
pwgen'
'pwgen'
]
]
circle/vm/models/activity.py
View file @
bf5b78a0
...
@@ -20,7 +20,6 @@ from contextlib import contextmanager
...
@@ -20,7 +20,6 @@ from contextlib import contextmanager
from
logging
import
getLogger
from
logging
import
getLogger
from
warnings
import
warn
from
warnings
import
warn
from
celery.signals
import
worker_ready
from
celery.contrib.abortable
import
AbortableAsyncResult
from
celery.contrib.abortable
import
AbortableAsyncResult
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
...
@@ -206,21 +205,6 @@ class InstanceActivity(ActivityModel):
...
@@ -206,21 +205,6 @@ class InstanceActivity(ActivityModel):
self
.
activity_code
)
self
.
activity_code
)
@contextmanager
def
instance_activity
(
code_suffix
,
instance
,
on_abort
=
None
,
on_commit
=
None
,
task_uuid
=
None
,
user
=
None
,
concurrency_check
=
True
,
readable_name
=
None
,
resultant_state
=
None
):
"""Create a transactional context for an instance activity.
"""
if
not
readable_name
:
warn
(
"Set readable_name"
,
stacklevel
=
3
)
act
=
InstanceActivity
.
create
(
code_suffix
,
instance
,
task_uuid
,
user
,
concurrency_check
,
readable_name
=
readable_name
,
resultant_state
=
resultant_state
)
return
activitycontextimpl
(
act
,
on_abort
=
on_abort
,
on_commit
=
on_commit
)
class
NodeActivity
(
ActivityModel
):
class
NodeActivity
(
ActivityModel
):
ACTIVITY_CODE_BASE
=
join_activity_code
(
'vm'
,
'Node'
)
ACTIVITY_CODE_BASE
=
join_activity_code
(
'vm'
,
'Node'
)
node
=
ForeignKey
(
'Node'
,
related_name
=
'activity_log'
,
node
=
ForeignKey
(
'Node'
,
related_name
=
'activity_log'
,
...
@@ -278,15 +262,15 @@ def node_activity(code_suffix, node, task_uuid=None, user=None,
...
@@ -278,15 +262,15 @@ def node_activity(code_suffix, node, task_uuid=None, user=None,
return
activitycontextimpl
(
act
)
return
activitycontextimpl
(
act
)
@worker_ready.connect
()
def
cleanup
(
conf
=
None
,
**
kwargs
):
def
cleanup
(
conf
=
None
,
**
kwargs
):
# TODO check if other manager workers are running
# TODO check if other manager workers are running
from
celery.task.control
import
discard_all
discard_all
()
msg_txt
=
ugettext_noop
(
"Manager is restarted, activity is cleaned up. "
msg_txt
=
ugettext_noop
(
"Manager is restarted, activity is cleaned up. "
"You can try again now."
)
"You can try again now."
)
message
=
create_readable
(
msg_txt
,
msg_txt
)
message
=
create_readable
(
msg_txt
,
msg_txt
)
queue_name
=
kwargs
.
get
(
'queue_name'
,
None
)
for
i
in
InstanceActivity
.
objects
.
filter
(
finished__isnull
=
True
):
for
i
in
InstanceActivity
.
objects
.
filter
(
finished__isnull
=
True
):
op
=
i
.
get_operation
()
if
op
and
op
.
async_queue
==
queue_name
:
i
.
finish
(
False
,
result
=
message
)
i
.
finish
(
False
,
result
=
message
)
logger
.
error
(
'Forced finishing stale activity
%
s'
,
i
)
logger
.
error
(
'Forced finishing stale activity
%
s'
,
i
)
for
i
in
NodeActivity
.
objects
.
filter
(
finished__isnull
=
True
):
for
i
in
NodeActivity
.
objects
.
filter
(
finished__isnull
=
True
):
...
...
circle/vm/models/instance.py
View file @
bf5b78a0
...
@@ -16,15 +16,13 @@
...
@@ -16,15 +16,13 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
__future__
import
absolute_import
,
unicode_literals
from
__future__
import
absolute_import
,
unicode_literals
from
contextlib
import
contextmanager
from
datetime
import
timedelta
from
datetime
import
timedelta
from
functools
import
partial
from
functools
import
partial
from
importlib
import
import_module
from
importlib
import
import_module
from
logging
import
getLogger
from
logging
import
getLogger
from
string
import
ascii_lowercase
from
warnings
import
warn
from
warnings
import
warn
from
celery.exceptions
import
TimeoutError
from
celery.contrib.abortable
import
AbortableAsyncResult
import
django.conf
import
django.conf
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.core
import
signing
from
django.core
import
signing
...
@@ -38,17 +36,17 @@ from django.utils import timezone
...
@@ -38,17 +36,17 @@ from django.utils import timezone
from
django.utils.translation
import
ugettext_lazy
as
_
,
ugettext_noop
from
django.utils.translation
import
ugettext_lazy
as
_
,
ugettext_noop
from
model_utils
import
Choices
from
model_utils
import
Choices
from
model_utils.managers
import
QueryManager
from
model_utils.models
import
TimeStampedModel
,
StatusModel
from
model_utils.models
import
TimeStampedModel
,
StatusModel
from
taggit.managers
import
TaggableManager
from
taggit.managers
import
TaggableManager
from
acl.models
import
AclBase
from
acl.models
import
AclBase
from
common.models
import
(
from
common.models
import
(
create_readable
,
HumanReadableException
,
humanize_exception
activitycontextimpl
,
create_readable
,
HumanReadableException
,
)
)
from
common.operations
import
OperatedMixin
from
common.operations
import
OperatedMixin
from
..tasks
import
vm_tasks
,
agent_tasks
from
..tasks
import
agent_tasks
from
.activity
import
(
ActivityInProgressError
,
instance_activity
,
from
.activity
import
(
ActivityInProgressError
,
InstanceActivity
)
InstanceActivity
)
from
.common
import
BaseResourceConfigModel
,
Lease
from
.common
import
BaseResourceConfigModel
,
Lease
from
.network
import
Interface
from
.network
import
Interface
from
.node
import
Node
,
Trait
from
.node
import
Node
,
Trait
...
@@ -92,13 +90,6 @@ def find_unused_vnc_port():
...
@@ -92,13 +90,6 @@ def find_unused_vnc_port():
return
port
return
port
class
InstanceActiveManager
(
Manager
):
def
get_query_set
(
self
):
return
super
(
InstanceActiveManager
,
self
)
.
get_query_set
()
.
filter
(
destroyed_at
=
None
)
class
VirtualMachineDescModel
(
BaseResourceConfigModel
):
class
VirtualMachineDescModel
(
BaseResourceConfigModel
):
"""Abstract base for virtual machine describing models.
"""Abstract base for virtual machine describing models.
...
@@ -264,7 +255,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -264,7 +255,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
help_text
=
_
(
"The virtual machine's time of "
help_text
=
_
(
"The virtual machine's time of "
"destruction."
))
"destruction."
))
objects
=
Manager
()
objects
=
Manager
()
active
=
InstanceActiveManager
(
)
active
=
QueryManager
(
destroyed_at
=
None
)
class
Meta
:
class
Meta
:
app_label
=
'vm'
app_label
=
'vm'
...
@@ -275,6 +266,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -275,6 +266,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
(
'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.'
)),
(
'create_vm'
,
_
(
'Can create a new VM.'
)),
(
'redeploy'
,
_
(
'Can redeploy a 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.'
)),
(
'emergency_change_state'
,
_
(
'Can change VM state to NOSTATE.'
)),
(
'emergency_change_state'
,
_
(
'Can change VM state to NOSTATE.'
)),
...
@@ -373,7 +365,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -373,7 +365,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
def
__on_commit
(
activity
):
def
__on_commit
(
activity
):
activity
.
resultant_state
=
'PENDING'
activity
.
resultant_state
=
'PENDING'
with
inst
ance_activity
(
code_suffix
=
'create'
,
instance
=
inst
,
with
inst
.
activity
(
code_suffix
=
'create'
,
readable_name
=
ugettext_noop
(
"create instance"
),
readable_name
=
ugettext_noop
(
"create instance"
),
on_commit
=
__on_commit
,
user
=
inst
.
owner
)
as
act
:
on_commit
=
__on_commit
,
user
=
inst
.
owner
)
as
act
:
# create related entities
# create related entities
...
@@ -676,7 +668,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -676,7 +668,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
"
%(success)
s notifications succeeded."
),
"
%(success)
s notifications succeeded."
),
success
=
len
(
success
),
successes
=
success
)
success
=
len
(
success
),
successes
=
success
)
with
instance_activity
(
'notification_about_expiration'
,
instance
=
self
,
with
self
.
activity
(
'notification_about_expiration'
,
readable_name
=
ugettext_noop
(
readable_name
=
ugettext_noop
(
"notify owner about expiration"
),
"notify owner about expiration"
),
on_commit
=
on_commit
):
on_commit
=
on_commit
):
...
@@ -744,75 +736,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -744,75 +736,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
"""
"""
return
scheduler
.
select_node
(
self
,
Node
.
objects
.
all
())
return
scheduler
.
select_node
(
self
,
Node
.
objects
.
all
())
def
attach_disk
(
self
,
disk
,
timeout
=
15
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'fast'
)
return
vm_tasks
.
attach_disk
.
apply_async
(
args
=
[
self
.
vm_name
,
disk
.
get_vmdisk_desc
()],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
detach_disk
(
self
,
disk
,
timeout
=
15
):
try
:
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'fast'
)
return
vm_tasks
.
detach_disk
.
apply_async
(
args
=
[
self
.
vm_name
,
disk
.
get_vmdisk_desc
()],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
except
Exception
as
e
:
if
e
.
libvirtError
and
"not found"
in
str
(
e
):
logger
.
debug
(
"Disk
%
s was not found."
%
disk
.
name
)
else
:
raise
def
attach_network
(
self
,
network
,
timeout
=
15
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'fast'
)
return
vm_tasks
.
attach_network
.
apply_async
(
args
=
[
self
.
vm_name
,
network
.
get_vmnetwork_desc
()],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
detach_network
(
self
,
network
,
timeout
=
15
):
try
:
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'fast'
)
return
vm_tasks
.
detach_network
.
apply_async
(
args
=
[
self
.
vm_name
,
network
.
get_vmnetwork_desc
()],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
except
Exception
as
e
:
if
e
.
libvirtError
and
"not found"
in
str
(
e
):
logger
.
debug
(
"Interface
%
s was not found."
%
(
network
.
__unicode__
()))
else
:
raise
def
resize_disk_live
(
self
,
disk
,
size
,
timeout
=
15
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'slow'
)
result
=
vm_tasks
.
resize_disk
.
apply_async
(
args
=
[
self
.
vm_name
,
disk
.
path
,
size
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
disk
.
size
=
size
disk
.
save
()
return
result
def
deploy_disks
(
self
):
"""Deploy all associated disks.
"""
devnums
=
list
(
ascii_lowercase
)
# a-z
for
disk
in
self
.
disks
.
all
():
# assign device numbers
if
disk
.
dev_num
in
devnums
:
devnums
.
remove
(
disk
.
dev_num
)
else
:
disk
.
dev_num
=
devnums
.
pop
(
0
)
disk
.
save
()
# deploy disk
disk
.
deploy
()
def
destroy_disks
(
self
):
def
destroy_disks
(
self
):
"""Destroy all associated disks.
"""Destroy all associated disks.
"""
"""
...
@@ -837,92 +760,11 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -837,92 +760,11 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
for
net
in
self
.
interface_set
.
all
():
for
net
in
self
.
interface_set
.
all
():
net
.
shutdown
()
net
.
shutdown
()
def
delete_vm
(
self
,
timeout
=
15
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'fast'
)
try
:
return
vm_tasks
.
destroy
.
apply_async
(
args
=
[
self
.
vm_name
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
except
Exception
as
e
:
if
e
.
libvirtError
and
"Domain not found"
in
str
(
e
):
logger
.
debug
(
"Domain
%
s was not found at
%
s"
%
(
self
.
vm_name
,
queue_name
))
else
:
raise
def
deploy_vm
(
self
,
timeout
=
15
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'slow'
)
return
vm_tasks
.
deploy
.
apply_async
(
args
=
[
self
.
get_vm_desc
()],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
migrate_vm
(
self
,
to_node
,
timeout
=
120
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'slow'
)
return
vm_tasks
.
migrate
.
apply_async
(
args
=
[
self
.
vm_name
,
to_node
.
host
.
hostname
,
True
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
reboot_vm
(
self
,
timeout
=
5
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'fast'
)
return
vm_tasks
.
reboot
.
apply_async
(
args
=
[
self
.
vm_name
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
reset_vm
(
self
,
timeout
=
5
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'fast'
)
return
vm_tasks
.
reset
.
apply_async
(
args
=
[
self
.
vm_name
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
resume_vm
(
self
,
timeout
=
15
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'slow'
)
return
vm_tasks
.
resume
.
apply_async
(
args
=
[
self
.
vm_name
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
shutdown_vm
(
self
,
task
=
None
,
step
=
5
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'slow'
)
logger
.
debug
(
"RPC Shutdown at queue:
%
s, for vm:
%
s."
,
queue_name
,
self
.
vm_name
)
remote
=
vm_tasks
.
shutdown
.
apply_async
(
kwargs
=
{
'name'
:
self
.
vm_name
},
queue
=
queue_name
)
while
True
:
try
:
return
remote
.
get
(
timeout
=
step
)
except
TimeoutError
as
e
:
if
task
is
not
None
and
task
.
is_aborted
():
AbortableAsyncResult
(
remote
.
id
)
.
abort
()
raise
humanize_exception
(
ugettext_noop
(
"Operation aborted by user."
),
e
)
def
suspend_vm
(
self
,
timeout
=
230
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'slow'
)
return
vm_tasks
.
sleep
.
apply_async
(
args
=
[
self
.
vm_name
,
self
.
mem_dump
[
'path'
]],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
wake_up_vm
(
self
,
timeout
=
60
):
queue_name
=
self
.
get_remote_queue_name
(
'vm'
,
'slow'
)
return
vm_tasks
.
wake_up
.
apply_async
(
args
=
[
self
.
vm_name
,
self
.
mem_dump
[
'path'
]],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
delete_mem_dump
(
self
,
timeout
=
15
):
queue_name
=
self
.
mem_dump
[
'datastore'
]
.
get_remote_queue_name
(
'storage'
,
'fast'
)
from
storage.tasks.storage_tasks
import
delete_dump
delete_dump
.
apply_async
(
args
=
[
self
.
mem_dump
[
'path'
]],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
allocate_node
(
self
):
def
allocate_node
(
self
):
if
self
.
node
is
None
:
if
self
.
node
is
None
:
self
.
node
=
self
.
select_node
()
self
.
node
=
self
.
select_node
()
self
.
save
()
self
.
save
()
return
self
.
node
def
yield_node
(
self
):
def
yield_node
(
self
):
if
self
.
node
is
not
None
:
if
self
.
node
is
not
None
:
...
@@ -995,12 +837,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -995,12 +837,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
return
merged_acts
return
merged_acts
def
get_screenshot
(
self
,
timeout
=
5
):
queue_name
=
self
.
get_remote_queue_name
(
"vm"
,
"fast"
)
return
vm_tasks
.
screenshot
.
apply_async
(
args
=
[
self
.
vm_name
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
def
get_latest_activity_in_progress
(
self
):
def
get_latest_activity_in_progress
(
self
):
try
:
try
:
return
InstanceActivity
.
objects
.
filter
(
return
InstanceActivity
.
objects
.
filter
(
...
@@ -1016,3 +852,17 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -1016,3 +852,17 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
@property
@property
def
metric_prefix
(
self
):
def
metric_prefix
(
self
):
return
'vm.
%
s'
%
self
.
vm_name
return
'vm.
%
s'
%
self
.
vm_name
@contextmanager
def
activity
(
self
,
code_suffix
,
readable_name
,
on_abort
=
None
,
on_commit
=
None
,
task_uuid
=
None
,
user
=
None
,
concurrency_check
=
True
,
resultant_state
=
None
):
"""Create a transactional context for an instance activity.
"""
if
not
readable_name
:
warn
(
"Set readable_name"
,
stacklevel
=
3
)
act
=
InstanceActivity
.
create
(
code_suffix
=
code_suffix
,
instance
=
self
,
task_uuid
=
task_uuid
,
user
=
user
,
concurrency_check
=
concurrency_check
,
readable_name
=
readable_name
,
resultant_state
=
resultant_state
)
return
activitycontextimpl
(
act
,
on_abort
=
on_abort
,
on_commit
=
on_commit
)
circle/vm/models/network.py
View file @
bf5b78a0
...
@@ -26,7 +26,6 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
...
@@ -26,7 +26,6 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from
common.models
import
create_readable
from
common.models
import
create_readable
from
firewall.models
import
Vlan
,
Host
from
firewall.models
import
Vlan
,
Host
from
..tasks
import
net_tasks
from
..tasks
import
net_tasks
from
.activity
import
instance_activity
logger
=
getLogger
(
__name__
)
logger
=
getLogger
(
__name__
)
...
@@ -120,10 +119,10 @@ class Interface(Model):
...
@@ -120,10 +119,10 @@ class Interface(Model):
host
.
hostname
=
instance
.
vm_name
host
.
hostname
=
instance
.
vm_name
# Get addresses from firewall
# Get addresses from firewall
if
base_activity
is
None
:
if
base_activity
is
None
:
act_ctx
=
instance
_
activity
(
act_ctx
=
instance
.
activity
(
code_suffix
=
'allocating_ip'
,
code_suffix
=
'allocating_ip'
,
readable_name
=
ugettext_noop
(
"allocate IP address"
),
readable_name
=
ugettext_noop
(
"allocate IP address"
),
instance
=
instance
,
user
=
owner
)
user
=
owner
)
else
:
else
:
act_ctx
=
base_activity
.
sub_activity
(
act_ctx
=
base_activity
.
sub_activity
(
'allocating_ip'
,
'allocating_ip'
,
...
...
circle/vm/models/node.py
View file @
bf5b78a0
...
@@ -114,8 +114,8 @@ class Node(OperatedMixin, TimeStampedModel):
...
@@ -114,8 +114,8 @@ class Node(OperatedMixin, TimeStampedModel):
def
get_info
(
self
):
def
get_info
(
self
):
return
self
.
remote_query
(
vm_tasks
.
get_info
,
return
self
.
remote_query
(
vm_tasks
.
get_info
,
priority
=
'fast'
,
priority
=
'fast'
,
default
=
{
'core_num'
:
''
,
default
=
{
'core_num'
:
0
,
'ram_size'
:
'0'
,
'ram_size'
:
0
,
'architecture'
:
''
})
'architecture'
:
''
})
info
=
property
(
get_info
)
info
=
property
(
get_info
)
...
@@ -313,10 +313,11 @@ class Node(OperatedMixin, TimeStampedModel):
...
@@ -313,10 +313,11 @@ class Node(OperatedMixin, TimeStampedModel):
def
get_status_label
(
self
):
def
get_status_label
(
self
):
return
{
return
{
'OFFLINE'
:
'label-warning'
,
'OFFLINE'
:
'label-warning'
,
'DISABLED'
:
'label-
warning
'
,
'DISABLED'
:
'label-
danger
'
,
'MISSING'
:
'label-danger'
,
'MISSING'
:
'label-danger'
,
'ONLINE'
:
'label-success'
}
.
get
(
self
.
get_state
(),
'ACTIVE'
:
'label-success'
,
'label-danger'
)
'PASSIVE'
:
'label-warning'
,
}
.
get
(
self
.
get_state
(),
'label-danger'
)
@node_available
@node_available
def
update_vm_states
(
self
):
def
update_vm_states
(
self
):
...
...
circle/vm/operations.py
View file @
bf5b78a0
...
@@ -28,12 +28,13 @@ from django.conf import settings
...
@@ -28,12 +28,13 @@ from django.conf import settings
from
sizefield.utils
import
filesizeformat
from
sizefield.utils
import
filesizeformat
from
celery.exceptions
import
TimeLimitExceeded
from
celery.contrib.abortable
import
AbortableAsyncResult
from
celery.exceptions
import
TimeLimitExceeded
,
TimeoutError
from
common.models
import
(
from
common.models
import
(
create_readable
,
humanize_exception
,
HumanReadableException
create_readable
,
humanize_exception
,
HumanReadableException
)
)
from
common.operations
import
Operation
,
register_operation
from
common.operations
import
Operation
,
register_operation
,
SubOperationMixin
from
manager.scheduler
import
SchedulerError
from
manager.scheduler
import
SchedulerError
from
.tasks.local_tasks
import
(
from
.tasks.local_tasks
import
(
abortable_async_instance_operation
,
abortable_async_node_operation
,
abortable_async_instance_operation
,
abortable_async_node_operation
,
...
@@ -42,13 +43,46 @@ from .models import (
...
@@ -42,13 +43,46 @@ from .models import (
Instance
,
InstanceActivity
,
InstanceTemplate
,
Interface
,
Node
,
Instance
,
InstanceActivity
,
InstanceTemplate
,
Interface
,
Node
,
NodeActivity
,
pwgen
NodeActivity
,
pwgen
)
)
from
.tasks
import
agent_tasks
,
local_agent_tasks
from
.tasks
import
agent_tasks
,
local_agent_tasks
,
vm_tasks
from
dashboard.store_api
import
Store
,
NoStoreException
from
dashboard.store_api
import
Store
,
NoStoreException
from
storage.tasks
import
storage_tasks
logger
=
getLogger
(
__name__
)
logger
=
getLogger
(
__name__
)
class
RemoteOperationMixin
(
object
):
remote_timeout
=
30
def
_operation
(
self
,
**
kwargs
):
args
=
self
.
_get_remote_args
(
**
kwargs
)
return
self
.
task
.
apply_async
(
args
=
args
,
queue
=
self
.
_get_remote_queue
()
)
.
get
(
timeout
=
self
.
remote_timeout
)
def
check_precond
(
self
):
super
(
RemoteOperationMixin
,
self
)
.
check_precond
()
self
.
_get_remote_queue
()
class
AbortableRemoteOperationMixin
(
object
):
remote_step
=
property
(
lambda
self
:
self
.
remote_timeout
/
10
)
def
_operation
(
self
,
task
,
**
kwargs
):
args
=
self
.
_get_remote_args
(
**
kwargs
),
remote
=
self
.
task
.
apply_async
(
args
=
args
,
queue
=
self
.
_get_remote_queue
())
for
i
in
xrange
(
0
,
self
.
remote_timeout
,
self
.
remote_step
):
try
:
return
remote
.
get
(
timeout
=
self
.
remote_step
)
except
TimeoutError
as
e
:
if
task
is
not
None
and
task
.
is_aborted
():
AbortableAsyncResult
(
remote
.
id
)
.
abort
()
raise
humanize_exception
(
ugettext_noop
(
"Operation aborted by user."
),
e
)
class
InstanceOperation
(
Operation
):
class
InstanceOperation
(
Operation
):
acl_level
=
'owner'
acl_level
=
'owner'
async_operation
=
abortable_async_instance_operation
async_operation
=
abortable_async_instance_operation
...
@@ -100,12 +134,13 @@ class InstanceOperation(Operation):
...
@@ -100,12 +134,13 @@ class InstanceOperation(Operation):
"parent activity does not match the user "
"parent activity does not match the user "
"provided as parameter."
)
"provided as parameter."
)
return
parent
.
create_sub
(
code_suffix
=
self
.
activity_code_suffix
,
return
parent
.
create_sub
(
readable_name
=
name
,
code_suffix
=
self
.
get_activity_code_suffix
()
,
resultant_state
=
self
.
resultant_state
)
readable_name
=
name
,
resultant_state
=
self
.
resultant_state
)
else
:
else
:
return
InstanceActivity
.
create
(
return
InstanceActivity
.
create
(
code_suffix
=
self
.
activity_code_suffix
,
instance
=
self
.
instance
,
code_suffix
=
self
.
get_activity_code_suffix
(),
instance
=
self
.
instance
,
readable_name
=
name
,
user
=
user
,
readable_name
=
name
,
user
=
user
,
concurrency_check
=
self
.
concurrency_check
,
concurrency_check
=
self
.
concurrency_check
,
resultant_state
=
self
.
resultant_state
)
resultant_state
=
self
.
resultant_state
)
...
@@ -116,9 +151,19 @@ class InstanceOperation(Operation):
...
@@ -116,9 +151,19 @@ class InstanceOperation(Operation):
return
False
return
False
class
RemoteInstanceOperation
(
RemoteOperationMixin
,
InstanceOperation
):
remote_queue
=
(
'vm'
,
'fast'
)
def
_get_remote_queue
(
self
):
return
self
.
instance
.
get_remote_queue_name
(
*
self
.
remote_queue
)
def
_get_remote_args
(
self
,
**
kwargs
):
return
[
self
.
instance
.
vm_name
]
@register_operation
@register_operation
class
AddInterfaceOperation
(
InstanceOperation
):
class
AddInterfaceOperation
(
InstanceOperation
):
activity_code_suffix
=
'add_interface'
id
=
'add_interface'
id
=
'add_interface'
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 "
...
@@ -146,10 +191,8 @@ class AddInterfaceOperation(InstanceOperation):
...
@@ -146,10 +191,8 @@ class AddInterfaceOperation(InstanceOperation):
if
self
.
instance
.
is_running
:
if
self
.
instance
.
is_running
:
try
:
try
:
with
activity
.
sub_activity
(
self
.
instance
.
_attach_network
(
'attach_network'
,
interface
=
net
,
parent_activity
=
activity
)
readable_name
=
ugettext_noop
(
"attach network"
)):
self
.
instance
.
attach_network
(
net
)
except
Exception
as
e
:
except
Exception
as
e
:
if
hasattr
(
e
,
'libvirtError'
):
if
hasattr
(
e
,
'libvirtError'
):
self
.
rollback
(
net
,
activity
)
self
.
rollback
(
net
,
activity
)
...
@@ -165,7 +208,6 @@ class AddInterfaceOperation(InstanceOperation):
...
@@ -165,7 +208,6 @@ class AddInterfaceOperation(InstanceOperation):
@register_operation
@register_operation
class
CreateDiskOperation
(
InstanceOperation
):
class
CreateDiskOperation
(
InstanceOperation
):
activity_code_suffix
=
'create_disk'
id
=
'create_disk'
id
=
'create_disk'
name
=
_
(
"create disk"
)
name
=
_
(
"create disk"
)
description
=
_
(
"Create and attach empty disk to the virtual machine."
)
description
=
_
(
"Create and attach empty disk to the virtual machine."
)
...
@@ -192,11 +234,7 @@ class CreateDiskOperation(InstanceOperation):
...
@@ -192,11 +234,7 @@ class CreateDiskOperation(InstanceOperation):
readable_name
=
ugettext_noop
(
"deploying disk"
)
readable_name
=
ugettext_noop
(
"deploying disk"
)
):
):
disk
.
deploy
()
disk
.
deploy
()
with
activity
.
sub_activity
(
self
.
instance
.
_attach_disk
(
parent_activity
=
activity
,
disk
=
disk
)
'attach_disk'
,
readable_name
=
ugettext_noop
(
"attach disk"
)
):
self
.
instance
.
attach_disk
(
disk
)
def
get_activity_name
(
self
,
kwargs
):
def
get_activity_name
(
self
,
kwargs
):
return
create_readable
(
return
create_readable
(
...
@@ -205,9 +243,8 @@ class CreateDiskOperation(InstanceOperation):
...
@@ -205,9 +243,8 @@ class CreateDiskOperation(InstanceOperation):
@register_operation
@register_operation
class
ResizeDiskOperation
(
InstanceOperation
):
class
ResizeDiskOperation
(
Remote
InstanceOperation
):
activity_code_suffix
=
'resize_disk'
id
=
'resize_disk'
id
=
'resize_disk'
name
=
_
(
"resize disk"
)
name
=
_
(
"resize disk"
)
description
=
_
(
"Resize the virtual disk image. "
description
=
_
(
"Resize the virtual disk image. "
...
@@ -215,9 +252,12 @@ class ResizeDiskOperation(InstanceOperation):
...
@@ -215,9 +252,12 @@ class ResizeDiskOperation(InstanceOperation):
required_perms
=
(
'storage.resize_disk'
,
)
required_perms
=
(
'storage.resize_disk'
,
)
accept_states
=
(
'RUNNING'
,
)
accept_states
=
(
'RUNNING'
,
)
async_queue
=
"localhost.man.slow"
async_queue
=
"localhost.man.slow"
remote_queue
=
(
'vm'
,
'slow'
)
task
=
vm_tasks
.
resize_disk
def
_operation
(
self
,
user
,
disk
,
size
,
activity
):
def
_get_remote_args
(
self
,
disk
,
size
,
**
kwargs
):
self
.
instance
.
resize_disk_live
(
disk
,
size
)
return
(
super
(
ResizeDiskOperation
,
self
)
.
_get_remote_args
(
**
kwargs
)
+
[
disk
.
path
,
size
])
def
get_activity_name
(
self
,
kwargs
):
def
get_activity_name
(
self
,
kwargs
):
return
create_readable
(
return
create_readable
(
...
@@ -227,7 +267,6 @@ class ResizeDiskOperation(InstanceOperation):
...
@@ -227,7 +267,6 @@ class ResizeDiskOperation(InstanceOperation):
@register_operation
@register_operation
class
DownloadDiskOperation
(
InstanceOperation
):
class
DownloadDiskOperation
(
InstanceOperation
):
activity_code_suffix
=
'download_disk'
id
=
'download_disk'
id
=
'download_disk'
name
=
_
(
"download disk"
)
name
=
_
(
"download disk"
)
description
=
_
(
"Download and attach disk image (ISO file) for the "
description
=
_
(
"Download and attach disk image (ISO file) for the "
...
@@ -257,16 +296,11 @@ class DownloadDiskOperation(InstanceOperation):
...
@@ -257,16 +296,11 @@ class DownloadDiskOperation(InstanceOperation):
# TODO iso (cd) hot-plug is not supported by kvm/guests
# TODO iso (cd) hot-plug is not supported by kvm/guests
if
self
.
instance
.
is_running
and
disk
.
type
not
in
[
"iso"
]:
if
self
.
instance
.
is_running
and
disk
.
type
not
in
[
"iso"
]:
with
activity
.
sub_activity
(
self
.
instance
.
_attach_disk
(
parent_activity
=
activity
,
disk
=
disk
)
'attach_disk'
,
readable_name
=
ugettext_noop
(
"attach disk"
)
):
self
.
instance
.
attach_disk
(
disk
)
@register_operation
@register_operation
class
DeployOperation
(
InstanceOperation
):
class
DeployOperation
(
InstanceOperation
):
activity_code_suffix
=
'deploy'
id
=
'deploy'
id
=
'deploy'
name
=
_
(
"deploy"
)
name
=
_
(
"deploy"
)
description
=
_
(
"Deploy and start the virtual machine (including storage "
description
=
_
(
"Deploy and start the virtual machine (including storage "
...
@@ -290,25 +324,17 @@ class DeployOperation(InstanceOperation):
...
@@ -290,25 +324,17 @@ class DeployOperation(InstanceOperation):
"deployed to node:
%(node)
s"
),
"deployed to node:
%(node)
s"
),
node
=
self
.
instance
.
node
)
node
=
self
.
instance
.
node
)
def
_operation
(
self
,
activity
,
timeout
=
15
):
def
_operation
(
self
,
activity
):
# Allocate VNC port and host node
# Allocate VNC port and host node
self
.
instance
.
allocate_vnc_port
()
self
.
instance
.
allocate_vnc_port
()
self
.
instance
.
allocate_node
()
self
.
instance
.
allocate_node
()
# Deploy virtual images
# Deploy virtual images
with
activity
.
sub_activity
(
self
.
instance
.
_deploy_disks
(
parent_activity
=
activity
)
'deploying_disks'
,
readable_name
=
ugettext_noop
(
"deploy disks"
)):
self
.
instance
.
deploy_disks
()
# Deploy VM on remote machine
# Deploy VM on remote machine
if
self
.
instance
.
state
not
in
[
'PAUSED'
]:
if
self
.
instance
.
state
not
in
[
'PAUSED'
]:
rn
=
create_readable
(
ugettext_noop
(
"deploy virtual machine"
),
self
.
instance
.
_deploy_vm
(
parent_activity
=
activity
)
ugettext_noop
(
"deploy vm to
%(node)
s"
),
node
=
self
.
instance
.
node
)
with
activity
.
sub_activity
(
'deploying_vm'
,
readable_name
=
rn
)
as
deploy_act
:
deploy_act
.
result
=
self
.
instance
.
deploy_vm
(
timeout
=
timeout
)
# Establish network connection (vmdriver)
# Establish network connection (vmdriver)
with
activity
.
sub_activity
(
with
activity
.
sub_activity
(
...
@@ -321,20 +347,57 @@ class DeployOperation(InstanceOperation):
...
@@ -321,20 +347,57 @@ class DeployOperation(InstanceOperation):
except
:
except
:
pass
pass
# Resume vm
self
.
instance
.
_resume_vm
(
parent_activity
=
activity
)
with
activity
.
sub_activity
(
'booting'
,
readable_name
=
ugettext_noop
(
"boot virtual machine"
)):
self
.
instance
.
resume_vm
(
timeout
=
timeout
)
if
self
.
instance
.
has_agent
:
if
self
.
instance
.
has_agent
:
activity
.
sub_activity
(
'os_boot'
,
readable_name
=
ugettext_noop
(
activity
.
sub_activity
(
'os_boot'
,
readable_name
=
ugettext_noop
(
"wait operating system loading"
),
interruptible
=
True
)
"wait operating system loading"
),
interruptible
=
True
)
@register_operation
class
DeployVmOperation
(
SubOperationMixin
,
RemoteInstanceOperation
):
id
=
"_deploy_vm"
name
=
_
(
"deploy vm"
)
description
=
_
(
"Deploy virtual machine."
)
remote_queue
=
(
"vm"
,
"slow"
)
task
=
vm_tasks
.
deploy
def
_get_remote_args
(
self
,
**
kwargs
):
return
[
self
.
instance
.
get_vm_desc
()]
# intentionally not calling super
def
get_activity_name
(
self
,
kwargs
):
return
create_readable
(
ugettext_noop
(
"deploy virtual machine"
),
ugettext_noop
(
"deploy vm to
%(node)
s"
),
node
=
self
.
instance
.
node
)
@register_operation
class
DeployDisksOperation
(
SubOperationMixin
,
InstanceOperation
):
id
=
"_deploy_disks"
name
=
_
(
"deploy disks"
)
description
=
_
(
"Deploy all associated disks."
)
def
_operation
(
self
):
devnums
=
list
(
ascii_lowercase
)
# a-z
for
disk
in
self
.
instance
.
disks
.
all
():
# assign device numbers
if
disk
.
dev_num
in
devnums
:
devnums
.
remove
(
disk
.
dev_num
)
else
:
disk
.
dev_num
=
devnums
.
pop
(
0
)
disk
.
save
()
# deploy disk
disk
.
deploy
()
@register_operation
class
ResumeVmOperation
(
SubOperationMixin
,
RemoteInstanceOperation
):
id
=
"_resume_vm"
name
=
_
(
"boot virtual machine"
)
remote_queue
=
(
"vm"
,
"slow"
)
task
=
vm_tasks
.
resume
@register_operation
@register_operation
class
DestroyOperation
(
InstanceOperation
):
class
DestroyOperation
(
InstanceOperation
):
activity_code_suffix
=
'destroy'
id
=
'destroy'
id
=
'destroy'
name
=
_
(
"destroy"
)
name
=
_
(
"destroy"
)
description
=
_
(
"Permanently destroy virtual machine, its network "
description
=
_
(
"Permanently destroy virtual machine, its network "
...
@@ -342,7 +405,7 @@ class DestroyOperation(InstanceOperation):
...
@@ -342,7 +405,7 @@ class DestroyOperation(InstanceOperation):
required_perms
=
()
required_perms
=
()
resultant_state
=
'DESTROYED'
resultant_state
=
'DESTROYED'
def
_operation
(
self
,
activity
):
def
_operation
(
self
,
activity
,
system
):
# Destroy networks
# Destroy networks
with
activity
.
sub_activity
(
with
activity
.
sub_activity
(
'destroying_net'
,
'destroying_net'
,
...
@@ -352,11 +415,7 @@ class DestroyOperation(InstanceOperation):
...
@@ -352,11 +415,7 @@ class DestroyOperation(InstanceOperation):
self
.
instance
.
destroy_net
()
self
.
instance
.
destroy_net
()
if
self
.
instance
.
node
:
if
self
.
instance
.
node
:
# Delete virtual machine
self
.
instance
.
_delete_vm
(
parent_activity
=
activity
)
with
activity
.
sub_activity
(
'destroying_vm'
,
readable_name
=
ugettext_noop
(
"destroy virtual machine"
)):
self
.
instance
.
delete_vm
()
# Destroy disks
# Destroy disks
with
activity
.
sub_activity
(
with
activity
.
sub_activity
(
...
@@ -366,7 +425,7 @@ class DestroyOperation(InstanceOperation):
...
@@ -366,7 +425,7 @@ class DestroyOperation(InstanceOperation):
# Delete mem. dump if exists
# Delete mem. dump if exists
try
:
try
:
self
.
instance
.
delete_mem_dump
(
)
self
.
instance
.
_delete_mem_dump
(
parent_activity
=
activity
)
except
:
except
:
pass
pass
...
@@ -377,10 +436,30 @@ class DestroyOperation(InstanceOperation):
...
@@ -377,10 +436,30 @@ class DestroyOperation(InstanceOperation):
self
.
instance
.
destroyed_at
=
timezone
.
now
()
self
.
instance
.
destroyed_at
=
timezone
.
now
()
self
.
instance
.
save
()
self
.
instance
.
save
()
@register_operation
class
DeleteVmOperation
(
SubOperationMixin
,
RemoteInstanceOperation
):
id
=
"_delete_vm"
name
=
_
(
"destroy virtual machine"
)
task
=
vm_tasks
.
destroy
# if e.libvirtError and "Domain not found" in str(e):
@register_operation
class
DeleteMemDumpOperation
(
RemoteOperationMixin
,
SubOperationMixin
,
InstanceOperation
):
id
=
"_delete_mem_dump"
name
=
_
(
"removing memory dump"
)
task
=
storage_tasks
.
delete_dump
def
_get_remote_queue
(
self
):
return
self
.
instance
.
mem_dump
[
'datastore'
]
.
get_remote_queue_name
(
"storage"
,
"fast"
)
def
_get_remote_args
(
self
,
**
kwargs
):
return
[
self
.
instance
.
mem_dump
[
'path'
]]
@register_operation
@register_operation
class
MigrateOperation
(
InstanceOperation
):
class
MigrateOperation
(
RemoteInstanceOperation
):
activity_code_suffix
=
'migrate'
id
=
'migrate'
id
=
'migrate'
name
=
_
(
"migrate"
)
name
=
_
(
"migrate"
)
description
=
_
(
"Move virtual machine to an other worker node with a few "
description
=
_
(
"Move virtual machine to an other worker node with a few "
...
@@ -389,6 +468,13 @@ class MigrateOperation(InstanceOperation):
...
@@ -389,6 +468,13 @@ class MigrateOperation(InstanceOperation):
superuser_required
=
True
superuser_required
=
True
accept_states
=
(
'RUNNING'
,
)
accept_states
=
(
'RUNNING'
,
)
async_queue
=
"localhost.man.slow"
async_queue
=
"localhost.man.slow"
task
=
vm_tasks
.
migrate
remote_queue
=
(
"vm"
,
"slow"
)
remote_timeout
=
1000
def
_get_remote_args
(
self
,
to_node
,
live_migration
,
**
kwargs
):
return
(
super
(
MigrateOperation
,
self
)
.
_get_remote_args
(
**
kwargs
)
+
[
to_node
.
host
.
hostname
,
live_migration
])
def
rollback
(
self
,
activity
):
def
rollback
(
self
,
activity
):
with
activity
.
sub_activity
(
with
activity
.
sub_activity
(
...
@@ -396,7 +482,7 @@ class MigrateOperation(InstanceOperation):
...
@@ -396,7 +482,7 @@ class MigrateOperation(InstanceOperation):
"redeploy network (rollback)"
)):
"redeploy network (rollback)"
)):
self
.
instance
.
deploy_net
()
self
.
instance
.
deploy_net
()
def
_operation
(
self
,
activity
,
to_node
=
None
,
timeout
=
120
):
def
_operation
(
self
,
activity
,
to_node
=
None
,
live_migration
=
True
):
if
not
to_node
:
if
not
to_node
:
with
activity
.
sub_activity
(
'scheduling'
,
with
activity
.
sub_activity
(
'scheduling'
,
readable_name
=
ugettext_noop
(
readable_name
=
ugettext_noop
(
...
@@ -408,7 +494,8 @@ class MigrateOperation(InstanceOperation):
...
@@ -408,7 +494,8 @@ class MigrateOperation(InstanceOperation):
with
activity
.
sub_activity
(
with
activity
.
sub_activity
(
'migrate_vm'
,
readable_name
=
create_readable
(
'migrate_vm'
,
readable_name
=
create_readable
(
ugettext_noop
(
"migrate to
%(node)
s"
),
node
=
to_node
)):
ugettext_noop
(
"migrate to
%(node)
s"
),
node
=
to_node
)):
self
.
instance
.
migrate_vm
(
to_node
=
to_node
,
timeout
=
timeout
)
super
(
MigrateOperation
,
self
)
.
_operation
(
to_node
=
to_node
,
live_migration
=
live_migration
)
except
Exception
as
e
:
except
Exception
as
e
:
if
hasattr
(
e
,
'libvirtError'
):
if
hasattr
(
e
,
'libvirtError'
):
self
.
rollback
(
activity
)
self
.
rollback
(
activity
)
...
@@ -423,6 +510,7 @@ class MigrateOperation(InstanceOperation):
...
@@ -423,6 +510,7 @@ class MigrateOperation(InstanceOperation):
# Refresh node information
# Refresh node information
self
.
instance
.
node
=
to_node
self
.
instance
.
node
=
to_node
self
.
instance
.
save
()
self
.
instance
.
save
()
# Estabilish network connection (vmdriver)
# Estabilish network connection (vmdriver)
with
activity
.
sub_activity
(
with
activity
.
sub_activity
(
'deploying_net'
,
readable_name
=
ugettext_noop
(
'deploying_net'
,
readable_name
=
ugettext_noop
(
...
@@ -431,17 +519,17 @@ class MigrateOperation(InstanceOperation):
...
@@ -431,17 +519,17 @@ class MigrateOperation(InstanceOperation):
@register_operation
@register_operation
class
RebootOperation
(
InstanceOperation
):
class
RebootOperation
(
RemoteInstanceOperation
):
activity_code_suffix
=
'reboot'
id
=
'reboot'
id
=
'reboot'
name
=
_
(
"reboot"
)
name
=
_
(
"reboot"
)
description
=
_
(
"Warm reboot virtual machine by sending Ctrl+Alt+Del "
description
=
_
(
"Warm reboot virtual machine by sending Ctrl+Alt+Del "
"signal to its console."
)
"signal to its console."
)
required_perms
=
()
required_perms
=
()
accept_states
=
(
'RUNNING'
,
)
accept_states
=
(
'RUNNING'
,
)
task
=
vm_tasks
.
reboot
def
_operation
(
self
,
activity
,
timeout
=
5
):
def
_operation
(
self
,
activity
):
s
elf
.
instance
.
reboot_vm
(
timeout
=
timeout
)
s
uper
(
RebootOperation
,
self
)
.
_operation
(
)
if
self
.
instance
.
has_agent
:
if
self
.
instance
.
has_agent
:
activity
.
sub_activity
(
'os_boot'
,
readable_name
=
ugettext_noop
(