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
15add231
authored
Apr 26, 2014
by
cloud
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into szakdoga-br2
parents
1abc2a1f
7b7c0ff4
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
662 additions
and
159 deletions
+662
-159
circle/acl/models.py
+4
-3
circle/acl/tests/test_acl.py
+14
-0
circle/circle/settings/base.py
+8
-1
circle/common/operations.py
+6
-4
circle/dashboard/admin.py
+17
-0
circle/dashboard/forms.py
+13
-1
circle/dashboard/static/dashboard/dashboard.css
+26
-2
circle/dashboard/static/dashboard/dashboard.js
+2
-1
circle/dashboard/static/dashboard/vm-details.js
+121
-29
circle/dashboard/templates/dashboard/profile_form.html
+14
-1
circle/dashboard/templates/dashboard/vm-detail.html
+8
-4
circle/dashboard/templates/dashboard/vm-detail/home.html
+40
-2
circle/dashboard/templates/dashboard/vm-detail/network.html
+41
-30
circle/dashboard/tests/test_views.py
+143
-0
circle/dashboard/urls.py
+4
-1
circle/dashboard/views.py
+120
-13
circle/firewall/fw.py
+3
-2
circle/network/templates/network/base.html
+3
-1
circle/storage/tasks/local_tasks.py
+7
-12
circle/vm/models/network.py
+6
-27
circle/vm/operations.py
+43
-11
circle/vm/tests/test_models.py
+16
-11
requirements/base.txt
+3
-3
No files found.
circle/acl/models.py
View file @
15add231
...
...
@@ -177,14 +177,15 @@ class AclBase(Model):
@classmethod
def
get_objects_with_level
(
cls
,
level
,
user
,
group_also
=
True
,
owner_also
=
False
):
group_also
=
True
,
owner_also
=
False
,
disregard_superuser
=
False
):
logger
.
debug
(
'
%
s.get_objects_with_level(
%
s,
%
s) called'
,
unicode
(
cls
),
unicode
(
level
),
unicode
(
user
))
if
user
is
None
or
not
user
.
is_authenticated
():
return
cls
.
objects
.
none
()
if
getattr
(
user
,
'is_superuser'
,
False
):
if
getattr
(
user
,
'is_superuser'
,
False
)
and
not
disregard_superuser
:
logger
.
debug
(
'- superuser granted'
)
return
cls
.
objects
return
cls
.
objects
.
all
()
if
isinstance
(
level
,
basestring
):
level
=
cls
.
get_level_object
(
level
)
logger
.
debug
(
"- level set by str:
%
s"
,
unicode
(
level
))
...
...
circle/acl/tests/test_acl.py
View file @
15add231
...
...
@@ -161,6 +161,20 @@ class AclUserTest(TestCase):
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
u2
),
[
i2
])
def
test_get_objects_with_level_for_superuser
(
self
):
i1
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello1'
)
i2
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello2'
)
i1
.
set_level
(
self
.
u1
,
'alfa'
)
i2
.
set_level
(
self
.
us
,
'alfa'
)
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
u1
),
[
i1
])
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
us
),
[
i1
,
i2
])
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
us
,
disregard_superuser
=
True
),
[
i2
])
def
test_get_objects_with_level_for_group
(
self
):
i1
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello1'
)
i2
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello2'
)
...
...
circle/circle/settings/base.py
View file @
15add231
...
...
@@ -5,6 +5,7 @@ from os.path import abspath, basename, dirname, join, normpath, isfile
from
sys
import
path
from
django.core.exceptions
import
ImproperlyConfigured
from
django.utils.translation
import
ugettext_lazy
as
_
from
json
import
loads
...
...
@@ -93,7 +94,13 @@ except:
TIME_ZONE
=
get_env_variable
(
'DJANGO_TIME_ZONE'
,
default
=
systz
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
LANGUAGE_CODE
=
'en-us'
LANGUAGE_CODE
=
get_env_variable
(
"DJANGO_LANGUAGE_CODE"
,
"en"
)
# https://docs.djangoproject.com/en/dev/ref/settings/#languages
LANGUAGES
=
(
(
'en'
,
_
(
'English'
)),
(
'hu'
,
_
(
'Hungarian'
)),
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID
=
1
...
...
circle/common/operations.py
View file @
15add231
...
...
@@ -62,8 +62,9 @@ class Operation(object):
For more information, check the synchronous call's documentation.
"""
logger
.
info
(
"
%
s called asynchronously with the following parameters: "
"
%
r"
,
self
.
__class__
.
__name__
,
kwargs
)
logger
.
info
(
"
%
s called asynchronously on
%
s with the following "
"parameters:
%
r"
,
self
.
__class__
.
__name__
,
self
.
subject
,
kwargs
)
activity
=
self
.
__prelude
(
kwargs
)
return
self
.
async_operation
.
apply_async
(
args
=
(
self
.
id
,
self
.
subject
.
pk
,
...
...
@@ -84,8 +85,9 @@ class Operation(object):
* user: The User invoking the operation. If this argument is not
present, it'll be provided with a default value of None.
"""
logger
.
info
(
"
%
s called (synchronously) with the following parameters: "
"
%
r"
,
self
.
__class__
.
__name__
,
kwargs
)
logger
.
info
(
"
%
s called (synchronously) on
%
s with the following "
"parameters:
%
r"
,
self
.
__class__
.
__name__
,
self
.
subject
,
kwargs
)
activity
=
self
.
__prelude
(
kwargs
)
return
self
.
_exec_op
(
activity
=
activity
,
**
kwargs
)
...
...
circle/dashboard/admin.py
0 → 100644
View file @
15add231
# -*- coding: utf-8 -*-
from
django
import
contrib
from
django.contrib.auth.admin
import
UserAdmin
from
django.contrib.auth.models
import
User
from
dashboard.models
import
Profile
class
ProfileInline
(
contrib
.
admin
.
TabularInline
):
model
=
Profile
UserAdmin
.
inlines
=
(
ProfileInline
,
)
contrib
.
admin
.
site
.
unregister
(
User
)
contrib
.
admin
.
site
.
register
(
User
,
UserAdmin
)
circle/dashboard/forms.py
View file @
15add231
...
...
@@ -5,6 +5,7 @@ from datetime import timedelta
from
django.contrib.auth.models
import
User
from
django.contrib.auth.forms
import
(
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
PasswordChangeForm
,
)
from
crispy_forms.helper
import
FormHelper
...
...
@@ -1015,9 +1016,20 @@ class MyProfileForm(forms.ModelForm):
def
helper
(
self
):
helper
=
FormHelper
()
helper
.
layout
=
Layout
(
'preferred_language'
,
)
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"
Sav
e"
)))
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"
Change languag
e"
)))
return
helper
def
save
(
self
,
*
args
,
**
kwargs
):
value
=
super
(
MyProfileForm
,
self
)
.
save
(
*
args
,
**
kwargs
)
return
value
class
CirclePasswordChangeForm
(
PasswordChangeForm
):
@property
def
helper
(
self
):
helper
=
FormHelper
()
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"Change password"
),
css_class
=
"btn btn-primary"
,
css_id
=
"submit-password-button"
))
return
helper
circle/dashboard/static/dashboard/dashboard.css
View file @
15add231
...
...
@@ -187,7 +187,7 @@ html {
height
:
300px
;
}
#vm-details-rename
,
#vm-details-
rename
*,
#vm-details-h1-name
,
#vm-list-rename
,
#vm-list-rename
*
,
#vm-details-rename
,
#vm-details-
h1-name
,
#vm-details-rename
,
#node-details-rename
,
#node-details-rename
*,
#node-details-h1-name
,
#node-list-rename
,
#node-list-rename
*
#group-details-rename
,
#group-details-rename
*,
#group-details-h1-name
,
#group-list-rename
,
#group-list-rename
*
{
display
:
inline
;
...
...
@@ -197,7 +197,11 @@ html {
display
:
none
;
}
#vm-details-rename-name
,
#node-details-rename-name
,
#group-details-rename-name
{
.vm-details-home-name
{
max-width
:
401px
;
}
#node-details-rename-name
,
#group-details-rename-name
{
max-width
:
160px
;
}
...
...
@@ -467,3 +471,23 @@ footer a, footer a:hover, footer a:visited {
overflow
:
hidden
;
padding-left
:
10px
;
}
#vm-details-home-description
{
display
:
inline-block
;
position
:
relative
;
}
#vm-details-home-description
textarea
{
min-width
:
240px
;
min-height
:
250px
;
}
.vm-details-home-edit-description-click
,
.vm-details-home-edit-name-click
{
cursor
:
pointer
;
}
.vm-details-description-submit
{
position
:
absolute
;
bottom
:
10px
;
right
:
20px
;
}
circle/dashboard/static/dashboard/dashboard.js
View file @
15add231
...
...
@@ -303,7 +303,8 @@ function deleteObject(data) {
// no need to remove them from DOM
$
(
'a[data-disk-pk="'
+
data
.
pk
+
'"]'
).
parent
(
"li"
).
fadeOut
();
$
(
'a[data-disk-pk="'
+
data
.
pk
+
'"]'
).
parent
(
"h4"
).
fadeOut
();
}
else
{
}
else
{
$
(
'a[data-'
+
data
[
'type'
]
+
'-pk="'
+
data
[
'pk'
]
+
'"]'
).
closest
(
'tr'
).
fadeOut
(
function
()
{
$
(
this
).
remove
();
});
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
15add231
...
...
@@ -31,33 +31,6 @@ $(function() {
return
false
;
});
/* rename */
$
(
"#vm-details-h1-name, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-details-h1-name"
).
hide
();
$
(
"#vm-details-rename"
).
css
(
'display'
,
'inline'
);
$
(
"#vm-details-rename-name"
).
focus
();
});
/* rename ajax */
$
(
'#vm-details-rename-submit'
).
click
(
function
()
{
var
name
=
$
(
'#vm-details-rename-name'
).
val
();
$
.
ajax
({
method
:
'POST'
,
url
:
location
.
href
,
data
:
{
'new_name'
:
name
},
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
data
,
textStatus
,
xhr
)
{
$
(
"#vm-details-h1-name"
).
html
(
data
[
'new_name'
]).
show
();
$
(
'#vm-details-rename'
).
hide
();
// addMessage(data['message'], "success");
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
"Error during renaming!"
,
"danger"
);
}
});
return
false
;
});
/* remove tag */
$
(
'.vm-details-remove-tag'
).
click
(
function
()
{
var
to_remove
=
$
.
trim
(
$
(
this
).
parent
(
'div'
).
text
());
...
...
@@ -150,8 +123,7 @@ $(function() {
/* add network button */
$
(
"#vm-details-network-add"
).
click
(
function
()
{
$
(
"#vm-details-network-add-for-form"
).
html
(
$
(
"#vm-details-network-add-form"
).
html
());
$
(
'input[name="new_network_managed"]'
).
tooltip
();
$
(
"#vm-details-network-add-form"
).
toggle
();
return
false
;
});
...
...
@@ -165,6 +137,126 @@ $(function() {
$
(
".vm-details-help-button"
).
click
(
function
()
{
$
(
".vm-details-help"
).
stop
().
slideToggle
();
});
/* for interface remove buttons */
$
(
'.interface-remove'
).
click
(
function
()
{
var
interface_pk
=
$
(
this
).
data
(
'interface-pk'
);
addModalConfirmation
(
removeInterface
,
{
'url'
:
'/dashboard/interface/'
+
interface_pk
+
'/delete/'
,
'data'
:
[],
'pk'
:
interface_pk
,
'type'
:
"interface"
,
});
return
false
;
});
/* removing interface post */
function
removeInterface
(
data
)
{
$
.
ajax
({
type
:
'POST'
,
url
:
data
[
'url'
],
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
re
,
textStatus
,
xhr
)
{
/* remove the html element */
$
(
'a[data-interface-pk="'
+
data
.
pk
+
'"]'
).
closest
(
"div"
).
fadeOut
();
/* add the removed element to the list */
network_select
=
$
(
'select[name="new_network_vlan"]'
);
name_html
=
(
re
.
removed_network
.
managed
?
""
:
""
)
+
" "
+
re
.
removed_network
.
vlan
;
option_html
=
'<option value="'
+
re
.
removed_network
.
vlan_pk
+
'">'
+
name_html
+
'</option>'
;
// if it's -1 then it's a dummy placeholder so we can use .html
if
(
$
(
"option"
,
network_select
)[
0
].
value
===
"-1"
)
{
network_select
.
html
(
option_html
);
network_select
.
next
(
"div"
).
children
(
"button"
).
prop
(
"disabled"
,
false
);
}
else
{
network_select
.
append
(
option_html
);
}
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
'Uh oh :('
,
'danger'
)
}
});
}
/* rename */
$
(
"#vm-details-h1-name, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-details-h1-name"
).
hide
();
$
(
"#vm-details-rename"
).
css
(
'display'
,
'inline'
);
$
(
"#vm-details-rename-name"
).
focus
();
});
/* rename in home tab */
$
(
".vm-details-home-edit-name-click"
).
click
(
function
()
{
$
(
".vm-details-home-edit-name-click"
).
hide
();
$
(
"#vm-details-home-rename"
).
show
();
$
(
"input"
,
$
(
"#vm-details-home-rename"
)).
focus
();
});
/* rename ajax */
$
(
'.vm-details-rename-submit'
).
click
(
function
()
{
var
name
=
$
(
this
).
parent
(
"span"
).
prev
(
"input"
).
val
();
$
.
ajax
({
method
:
'POST'
,
url
:
location
.
href
,
data
:
{
'new_name'
:
name
},
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
data
,
textStatus
,
xhr
)
{
$
(
".vm-details-home-edit-name"
).
text
(
data
[
'new_name'
]).
show
();
$
(
".vm-details-home-edit-name"
).
parent
(
"div"
).
show
();
$
(
".vm-details-home-edit-name-click"
).
show
();
$
(
".vm-details-home-rename-form-div"
).
hide
();
// update the inputs too
$
(
".vm-details-rename-submit"
).
parent
(
"span"
).
prev
(
"input"
).
val
(
data
[
'new_name'
]);
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
"Error during renaming!"
,
"danger"
);
}
});
return
false
;
});
/* update description click */
$
(
".vm-details-home-edit-description-click"
).
click
(
function
()
{
$
(
".vm-details-home-edit-description-click"
).
hide
();
$
(
"#vm-details-home-description"
).
show
();
return
false
;
});
/* description update ajax */
$
(
'.vm-details-description-submit'
).
click
(
function
()
{
var
description
=
$
(
this
).
prev
(
"textarea"
).
val
();
$
.
ajax
({
method
:
'POST'
,
url
:
location
.
href
,
data
:
{
'new_description'
:
description
},
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
data
,
textStatus
,
xhr
)
{
var
new_desc
=
data
[
'new_description'
];
/* we can't simply use $.text, because we need new lines */
var
tagsToReplace
=
{
'&'
:
"&"
,
'<'
:
"<"
,
'>'
:
">"
,
};
new_desc
=
new_desc
.
replace
(
/
[
&<>
]
/g
,
function
(
tag
)
{
return
tagsToReplace
[
tag
]
||
tag
;
});
$
(
".vm-details-home-edit-description"
)
.
html
(
new_desc
.
replace
(
/
\n
/g
,
"<br />"
));
$
(
".vm-details-home-edit-description-click"
).
show
();
$
(
"#vm-details-home-description"
).
hide
();
// update the textareia
$
(
"vm-details-home-description textarea"
).
text
(
data
[
'new_description'
]);
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
"Error during renaming!"
,
"danger"
);
}
});
return
false
;
});
});
...
...
circle/dashboard/templates/dashboard/profile_form.html
View file @
15add231
...
...
@@ -14,7 +14,20 @@
<h3
class=
"no-margin"
><i
class=
"icon-desktop"
></i>
{% trans "My profile" %}
</h3>
</div>
<div
class=
"panel-body"
>
{% crispy form %}
<div
class=
"row"
>
<div
class=
"col-sm-4"
style=
"margin-bottom: 50px;"
>
<fieldset>
<legend>
{% trans "Password change" %}
</legend>
{% crispy forms.change_password %}
</fieldset>
</div>
<div
class=
"col-sm-offset-5 col-sm-3"
>
<fieldset>
<legend>
{% trans "Language selection" %}
</legend>
{% crispy forms.change_language %}
</fieldset>
</div>
</div>
</div>
</div>
</div>
...
...
circle/dashboard/templates/dashboard/vm-detail.html
View file @
15add231
...
...
@@ -18,14 +18,18 @@ btn-xs" type="submit"><i class="icon-chevron-right"></i></button>
</div>
</div>
<h1>
<div
id=
"vm-details-rename"
>
<div
id=
"vm-details-rename"
class=
"vm-details-home-rename-form-div"
>
<form
action=
""
method=
"POST"
id=
"vm-details-rename-form"
>
{% csrf_token %}
<input
id=
"vm-details-rename-name"
class=
"form-control"
name=
"new_name"
type=
"text"
value=
"{{ instance.name }}"
/>
<button
type=
"submit"
id=
"vm-details-rename-submit"
class=
"btn"
>
{% trans "Rename" %}
</button>
<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 }}"
/>
<span
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-sm vm-details-rename-submit"
>
{% trans "Rename" %}
</button>
</span>
</div>
</form>
</div>
<div
id=
"vm-details-h1-name"
>
<div
id=
"vm-details-h1-name"
class=
"vm-details-home-edit-name"
>
{{ instance.name }}
</div>
<small>
{{ instance.primary_host.get_fqdn }}
</small>
...
...
circle/dashboard/templates/dashboard/vm-detail/home.html
View file @
15add231
...
...
@@ -4,8 +4,46 @@
<dl>
<dt>
{% trans "System" %}:
</dt>
<dd><i
class=
"icon-{{ os_type_icon }}"
></i>
{{ instance.system }}
</dd>
<dt
style=
"margin-top: 5px;"
>
{% trans "Description" %}:
</dt>
<dd><small>
{{ instance.description }}
</small></dd>
<dt
style=
"margin-top: 5px;"
>
{% trans "Name" %}:
<a
href=
"#"
class=
"vm-details-home-edit-name-click"
><i
class=
"icon-pencil"
></i></a>
</dt>
<dd>
<div
class=
"vm-details-home-edit-name-click"
>
<small
class=
"vm-details-home-edit-name"
>
{{ instance.name }}
</small>
</div>
<div
class=
"js-hidden vm-details-home-rename-form-div"
id=
"vm-details-home-rename"
>
<form
method=
"POST"
>
{% csrf_token %}
<div
class=
"input-group"
>
<input
type=
"text"
name=
"new_name"
value=
"{{ instance.name }}"
class=
"form-control input-sm"
/>
<span
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-success btn-sm vm-details-rename-submit"
>
<i
class=
"icon-pencil"
></i>
{% trans "Rename" %}
</button>
</span>
</div>
</form>
</div>
</dd>
<dt
style=
"margin-top: 5px;"
>
{% trans "Description" %}:
<a
href=
"#"
class=
"vm-details-home-edit-description-click"
><i
class=
"icon-pencil"
></i></a>
</dt>
<dd>
{% csrf_token %}
<div
class=
"vm-details-home-edit-description-click"
>
<small
class=
"vm-details-home-edit-description"
>
{{ instance.description|linebreaks }}
</small>
</div>
<div
id=
"vm-details-home-description"
class=
"js-hidden"
>
<form
method=
"POST"
>
<textarea
name=
"new_description"
class=
"form-control"
>
{{ instance.description }}
</textarea>
<button
type=
"submit"
class=
"btn btn-xs btn-success vm-details-description-submit"
>
<i
class=
"icon-pencil"
></i>
{% trans "Update" %}
</button>
</form>
</div>
</dd>
</dl>
<h4>
{% trans "Expiration" %} {% if instance.is_expiring %}
<i
class=
"icon-warning-sign text-danger"
></i>
{% endif %}
...
...
circle/dashboard/templates/dashboard/vm-detail/network.html
View file @
15add231
...
...
@@ -6,13 +6,50 @@
{% trans "Interfaces" %}
</h2>
<div
class=
"row"
id=
"vm-details-network-add-for-form"
>
<div
class=
"js-hidden row"
id=
"vm-details-network-add-form"
>
<div
class=
"col-md-12"
>
<div>
<hr
/>
<h3>
{% trans "Add new network interface!" %}
</h3>
<form
method=
"POST"
action=
""
>
{% csrf_token %}
<div
class=
"input-group"
style=
"max-width: 330px;"
>
<select
name=
"new_network_vlan"
class=
"form-control font-awesome-font"
>
{% for v in vlans %}
<option
value=
"{{ v.pk }}"
>
{% if v.managed %}

{% else %}

{% endif %}
{{ v.name }}
</option>
{% empty %}
<option
value=
"-1"
>
No more networks!
</option>
{% endfor %}
</select>
<div
class=
"input-group-btn"
>
<button
{%
if
vlans
|
length =
=
0
%}
disabled
{%
endif
%}
type=
"submit"
class=
"btn btn-success"
><i
class=
"icon-plus-sign"
></i></button>
</div>
</div>
</form>
<hr
/>
</div>
</div>
</div>
{% for i in instance.interface_set.all %}
<div>
<h3
class=
"list-group-item-heading dashboard-vm-details-network-h3"
>
<i
class=
"icon-{% if i.host %}globe{% else %}link{% endif %}"
></i>
{{ i.vlan.name }}
{% if not i.host %}(unmanaged)
<a
href=
"#"
class=
"btn btn-danger btn-xs"
>
{% trans "remove" %}
</a>
{% endif %}
{% if not i.host%}({% trans "unmanaged" %}){% endif %}
<a
href=
"{% url "
dashboard
.
views
.
interface-delete
"
pk=
i.pk
%}?
next=
{{
request
.
path
}}"
class=
"btn btn-danger btn-xs interface-remove"
data-interface-pk=
"{{ i.pk }}"
>
{% trans "remove" %}
</a>
</h3>
{% if i.host %}
<div
class=
"row"
>
...
...
@@ -109,31 +146,5 @@
</div>
</div>
{% endif %}
{% endfor %}
<div
class=
"js-hidden row"
id=
"vm-details-network-add-form"
>
<div
class=
"col-md-12"
>
<div>
<hr
/>
<h3>
{% trans "Add new network interface!" %}
</h3>
<form
method=
"POST"
action=
""
>
{% csrf_token %}
<div
class=
"input-group"
style=
"max-width: 330px;"
>
<select
name=
"new_network_vlan"
class=
"form-control"
>
{% if vlans|length == 0 %}
<option
value=
"-1"
>
No more networks!
</option>
{% endif %}
{% for v in vlans %}
<option
value=
"{{ v.pk }}"
>
{{ v.name }}
</option>
{% endfor %}
</select>
<div
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-success"
><i
class=
"icon-plus-sign"
></i></button>
</div>
</div>
</form>
<hr
/>
</div>
</div>
</div>
{% endfor %}
circle/dashboard/tests/test_views.py
View file @
15add231
import
json
from
unittest
import
skip
from
django.test
import
TestCase
from
django.test.client
import
Client
...
...
@@ -5,6 +7,7 @@ from django.contrib.auth.models import User, Group
from
django.core.exceptions
import
SuspiciousOperation
from
django.core.urlresolvers
import
reverse
from
django.contrib.auth.models
import
Permission
from
django.contrib.auth
import
authenticate
from
vm.models
import
Instance
,
InstanceTemplate
,
Lease
,
Node
,
Trait
from
vm.operations
import
WakeUpOperation
...
...
@@ -173,6 +176,46 @@ class VmDetailTest(LoginMixin, TestCase):
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
inst
.
interface_set
.
count
(),
interface_count
+
1
)
def
test_permitted_network_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
inst
.
add_interface
(
vlan
=
Vlan
.
objects
.
get
(
pk
=
1
),
user
=
self
.
us
)
iface_count
=
inst
.
interface_set
.
count
()
c
.
post
(
"/dashboard/interface/1/delete/"
)
self
.
assertEqual
(
inst
.
interface_set
.
count
(),
iface_count
-
1
)
def
test_permitted_network_delete_w_ajax
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
vlan
=
Vlan
.
objects
.
get
(
pk
=
1
)
inst
.
add_interface
(
vlan
=
vlan
,
user
=
self
.
us
)
iface_count
=
inst
.
interface_set
.
count
()
response
=
c
.
post
(
"/dashboard/interface/1/delete/"
,
HTTP_X_REQUESTED_WITH
=
'XMLHttpRequest'
)
removed_network
=
json
.
loads
(
response
.
content
)[
'removed_network'
]
self
.
assertEqual
(
removed_network
[
'vlan'
],
vlan
.
name
)
self
.
assertEqual
(
removed_network
[
'vlan_pk'
],
vlan
.
pk
)
self
.
assertEqual
(
removed_network
[
'managed'
],
vlan
.
managed
)
self
.
assertEqual
(
inst
.
interface_set
.
count
(),
iface_count
-
1
)
def
test_unpermitted_network_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'user'
)
inst
.
add_interface
(
vlan
=
Vlan
.
objects
.
get
(
pk
=
1
),
user
=
self
.
us
)
iface_count
=
inst
.
interface_set
.
count
()
response
=
c
.
post
(
"/dashboard/interface/1/delete/"
)
self
.
assertEqual
(
iface_count
,
inst
.
interface_set
.
count
())
self
.
assertEqual
(
response
.
status_code
,
403
)
def
test_create_vm_w_unpermitted_network
(
self
):
c
=
Client
()
self
.
login
(
c
,
'user2'
)
...
...
@@ -558,6 +601,41 @@ class VmDetailTest(LoginMixin, TestCase):
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
instance_count
+
2
,
Instance
.
objects
.
all
()
.
count
())
def
test_unpermitted_description_update
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u2
,
'owner'
)
inst
.
set_level
(
self
.
u1
,
'user'
)
old_desc
=
inst
.
description
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'new_description'
:
'test1234'
})
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
Instance
.
objects
.
get
(
pk
=
1
)
.
description
,
old_desc
)
def
test_permitted_description_update_w_ajax
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'new_description'
:
"naonyo"
},
HTTP_X_REQUESTED_WITH
=
'XMLHttpRequest'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
json
.
loads
(
response
.
content
)[
'new_description'
],
"naonyo"
)
self
.
assertEqual
(
Instance
.
objects
.
get
(
pk
=
1
)
.
description
,
"naonyo"
)
def
test_permitted_description_update
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'new_description'
:
"naonyo"
})
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
Instance
.
objects
.
get
(
pk
=
1
)
.
description
,
"naonyo"
)
class
NodeDetailTest
(
LoginMixin
,
TestCase
):
fixtures
=
[
'test-vm-fixture.json'
,
'node.json'
]
...
...
@@ -991,3 +1069,68 @@ class IndexViewTest(LoginMixin, TestCase):
self
.
u1
.
profile
.
notify
(
"urgent"
,
"dashboard/test_message.txt"
,
)
response
=
c
.
get
(
"/dashboard/"
)
self
.
assertEqual
(
response
.
context
[
'NEW_NOTIFICATIONS_COUNT'
],
1
)
class
ProfileViewTest
(
LoginMixin
,
TestCase
):
def
setUp
(
self
):
self
.
u1
=
User
.
objects
.
create
(
username
=
'user1'
)
self
.
u1
.
set_password
(
'password'
)
self
.
u1
.
save
()
self
.
p1
=
Profile
.
objects
.
create
(
user
=
self
.
u1
)
self
.
p1
.
save
()
def
test_permitted_language_change
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
old_language_cookie_value
=
c
.
cookies
[
'django_language'
]
.
value
old_language_db_value
=
self
.
u1
.
profile
.
preferred_language
response
=
c
.
post
(
"/dashboard/profile/"
,
{
'preferred_language'
:
"hu"
,
})
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertNotEqual
(
old_language_cookie_value
,
c
.
cookies
[
'django_language'
]
.
value
)
self
.
assertNotEqual
(
old_language_db_value
,
User
.
objects
.
get
(
username
=
"user1"
)
.
profile
.
preferred_language
)
def
test_permitted_valid_password_change
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
c
.
post
(
"/dashboard/profile/"
,
{
'old_password'
:
"password"
,
'new_password1'
:
"asd"
,
'new_password2'
:
"asd"
,
})
self
.
assertIsNone
(
authenticate
(
username
=
"user1"
,
password
=
"password"
))
self
.
assertIsNotNone
(
authenticate
(
username
=
"user1"
,
password
=
"asd"
))
def
test_permitted_invalid_password_changes
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
# wrong current password
c
.
post
(
"/dashboard/profile/"
,
{
'old_password'
:
"password1"
,
'new_password1'
:
"asd"
,
'new_password2'
:
"asd"
,
})
self
.
assertIsNotNone
(
authenticate
(
username
=
"user1"
,
password
=
"password"
))
self
.
assertIsNone
(
authenticate
(
username
=
"user1"
,
password
=
"asd"
))
# wrong pw confirmation
c
.
post
(
"/dashboard/profile/"
,
{
'old_password'
:
"password"
,
'new_password1'
:
"asd"
,
'new_password2'
:
"asd1"
,
})
self
.
assertIsNotNone
(
authenticate
(
username
=
"user1"
,
password
=
"password"
))
self
.
assertIsNone
(
authenticate
(
username
=
"user1"
,
password
=
"asd"
))
circle/dashboard/urls.py
View file @
15add231
...
...
@@ -12,7 +12,7 @@ from .views import (
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TransferOwnershipConfirmView
,
TransferOwnershipView
,
vm_activity
,
VmCreate
,
VmDelete
,
VmDetailView
,
VmDetailVncTokenView
,
VmGraphView
,
VmList
,
VmMassDelete
,
VmMigrateView
,
VmRenewView
,
DiskRemoveView
,
get_disk_download_status
,
VmRenewView
,
DiskRemoveView
,
get_disk_download_status
,
InterfaceDeleteView
,
)
urlpatterns
=
patterns
(
...
...
@@ -108,6 +108,9 @@ urlpatterns = patterns(
url
(
r'^disk/(?P<pk>\d+)/status/$'
,
get_disk_download_status
,
name
=
"dashboard.views.disk-status"
),
url
(
r'^interface/(?P<pk>\d+)/delete/$'
,
InterfaceDeleteView
.
as_view
(),
name
=
"dashboard.views.interface-delete"
),
url
(
r'^profile/$'
,
MyPreferencesView
.
as_view
(),
name
=
"dashboard.views.profile"
),
)
circle/dashboard/views.py
View file @
15add231
...
...
@@ -37,6 +37,7 @@ from braces.views import (
from
.forms
import
(
CircleAuthenticationForm
,
DiskAddForm
,
HostForm
,
LeaseForm
,
MyProfileForm
,
NodeForm
,
TemplateForm
,
TraitForm
,
VmCustomizeForm
,
CirclePasswordChangeForm
)
from
.tables
import
(
NodeListTable
,
NodeVmListTable
,
TemplateListTable
,
LeaseListTable
,
GroupListTable
,)
...
...
@@ -89,7 +90,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
# instances
favs
=
Instance
.
objects
.
filter
(
favourite__user
=
self
.
request
.
user
)
instances
=
Instance
.
get_objects_with_level
(
'user'
,
user
)
.
filter
(
destroyed_at
=
None
)
'user'
,
user
,
disregard_superuser
=
True
)
.
filter
(
destroyed_at
=
None
)
display
=
list
(
favs
)
+
list
(
set
(
instances
)
-
set
(
favs
))
for
d
in
display
:
d
.
fav
=
True
if
d
in
favs
else
False
...
...
@@ -215,7 +216,7 @@ class VmDetailView(CheckedDetailView):
context
[
'vlans'
]
=
Vlan
.
get_objects_with_level
(
'user'
,
self
.
request
.
user
)
.
exclude
(
)
.
exclude
(
# exclude already added interfaces
pk__in
=
Interface
.
objects
.
filter
(
instance
=
self
.
get_object
())
.
values_list
(
"vlan"
,
flat
=
True
)
)
.
all
()
...
...
@@ -238,6 +239,7 @@ class VmDetailView(CheckedDetailView):
options
=
{
'change_password'
:
self
.
__change_password
,
'new_name'
:
self
.
__set_name
,
'new_description'
:
self
.
__set_description
,
'new_tag'
:
self
.
__add_tag
,
'deploy_local'
:
self
.
__deploy_local
,
'to_remove'
:
self
.
__remove_tag
,
...
...
@@ -318,8 +320,30 @@ class VmDetailView(CheckedDetailView):
)
else
:
messages
.
success
(
request
,
success_message
)
return
redirect
(
reverse_lazy
(
"dashboard.views.detail"
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
}))
return
redirect
(
self
.
object
.
get_absolute_url
())
def
__set_description
(
self
,
request
):
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
new_description
=
request
.
POST
.
get
(
"new_description"
)
Instance
.
objects
.
filter
(
pk
=
self
.
object
.
pk
)
.
update
(
**
{
'description'
:
new_description
})
success_message
=
_
(
"VM description successfully updated!"
)
if
request
.
is_ajax
():
response
=
{
'message'
:
success_message
,
'new_description'
:
new_description
,
}
return
HttpResponse
(
json
.
dumps
(
response
),
content_type
=
"application/json"
)
else
:
messages
.
success
(
request
,
success_message
)
return
redirect
(
self
.
object
.
get_absolute_url
())
def
__add_tag
(
self
,
request
):
new_tag
=
request
.
POST
.
get
(
'new_tag'
)
...
...
@@ -402,12 +426,11 @@ class VmDetailView(CheckedDetailView):
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
vlan
=
Vlan
.
objects
.
get
(
pk
=
request
.
POST
.
get
(
"new_network_vlan"
))
vlan
=
get_object_or_404
(
Vlan
,
pk
=
request
.
POST
.
get
(
"new_network_vlan"
))
if
not
vlan
.
has_level
(
request
.
user
,
'user'
):
raise
PermissionDenied
()
try
:
Interface
.
create
(
vlan
=
vlan
,
instance
=
self
.
object
,
managed
=
vlan
.
managed
,
owner
=
request
.
user
)
self
.
object
.
add_interface
(
vlan
=
vlan
,
user
=
request
.
user
)
messages
.
success
(
request
,
_
(
"Successfully added new interface!"
))
except
Exception
,
e
:
error
=
u' '
.
join
(
e
.
messages
)
...
...
@@ -2135,9 +2158,17 @@ class DiskAddView(TemplateView):
class
MyPreferencesView
(
UpdateView
):
model
=
Profile
form_class
=
MyProfileForm
def
get_context_data
(
self
,
*
args
,
**
kwargs
):
context
=
super
(
MyPreferencesView
,
self
)
.
get_context_data
(
*
args
,
**
kwargs
)
context
[
'forms'
]
=
{
'change_password'
:
CirclePasswordChangeForm
(
user
=
self
.
request
.
user
),
'change_language'
:
MyProfileForm
(
instance
=
self
.
get_object
()),
}
return
context
def
get_object
(
self
,
queryset
=
None
):
if
self
.
request
.
user
.
is_anonymous
():
...
...
@@ -2147,10 +2178,36 @@ class MyPreferencesView(UpdateView):
except
Profile
.
DoesNotExist
:
raise
Http404
(
_
(
"You don't have a profile."
))
def
form_valid
(
self
,
form
):
response
=
super
(
MyPreferencesView
,
self
)
.
form_valid
(
form
)
set_language_cookie
(
self
.
request
,
response
)
return
response
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
ojbect
=
self
.
get_object
()
redirect_response
=
HttpResponseRedirect
(
reverse
(
"dashboard.views.profile"
))
if
"preferred_language"
in
request
.
POST
:
form
=
MyProfileForm
(
request
.
POST
,
instance
=
self
.
get_object
())
if
form
.
is_valid
():
lang
=
form
.
cleaned_data
.
get
(
"preferred_language"
)
set_language_cookie
(
self
.
request
,
redirect_response
,
lang
)
form
.
save
()
else
:
form
=
CirclePasswordChangeForm
(
user
=
request
.
user
,
data
=
request
.
POST
)
if
form
.
is_valid
():
form
.
save
()
if
form
.
is_valid
():
return
redirect_response
else
:
return
self
.
get
(
request
,
form
=
form
,
*
args
,
**
kwargs
)
def
get
(
self
,
request
,
form
=
None
,
*
args
,
**
kwargs
):
# if this is not here, it won't work
self
.
object
=
self
.
get_object
()
context
=
self
.
get_context_data
(
*
args
,
**
kwargs
)
if
form
is
not
None
:
# a little cheating, users can't post invalid
# language selection forms (without modifying the HTML)
context
[
'forms'
][
'change_password'
]
=
form
return
self
.
render_to_response
(
context
)
def
set_language_cookie
(
request
,
response
,
lang
=
None
):
...
...
@@ -2236,3 +2293,53 @@ class InstanceActivityDetail(SuperuserRequiredMixin, DetailView):
order_by
(
'-started'
)
.
select_related
(
'user'
)
.
prefetch_related
(
'children'
))
return
ctx
class
InterfaceDeleteView
(
DeleteView
):
model
=
Interface
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
(
InterfaceDeleteView
,
self
)
.
get_context_data
(
**
kwargs
)
interface
=
self
.
get_object
()
context
[
'text'
]
=
_
(
"Are you sure you want to remove this interface "
"from <strong>
%(vm)
s</strong>?"
%
{
'vm'
:
interface
.
instance
.
name
})
return
context
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
()
instance
=
self
.
object
.
instance
if
not
instance
.
has_level
(
request
.
user
,
"owner"
):
raise
PermissionDenied
()
instance
.
remove_interface
(
interface
=
self
.
object
,
user
=
request
.
user
)
success_url
=
self
.
get_success_url
()
success_message
=
_
(
"Interface successfully deleted!"
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
(
{
'message'
:
success_message
,
'removed_network'
:
{
'vlan'
:
self
.
object
.
vlan
.
name
,
'vlan_pk'
:
self
.
object
.
vlan
.
pk
,
'managed'
:
self
.
object
.
host
is
not
None
,
}}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
"
%
s#network"
%
success_url
)
def
get_success_url
(
self
):
redirect
=
self
.
request
.
POST
.
get
(
"next"
)
if
redirect
:
return
redirect
self
.
object
.
instance
.
get_absolute_url
()
circle/firewall/fw.py
View file @
15add231
...
...
@@ -2,7 +2,7 @@ import re
import
logging
from
collections
import
OrderedDict
from
netaddr
import
IPAddress
,
AddrFormatError
from
datetime
import
datetime
,
timedelta
from
datetime
import
timedelta
from
itertools
import
product
from
.models
import
(
Host
,
Rule
,
Vlan
,
Domain
,
Record
,
BlacklistItem
,
...
...
@@ -11,6 +11,7 @@ from .iptables import IptRule, IptChain
import
django.conf
from
django.db.models
import
Q
from
django.template
import
loader
,
Context
from
django.utils
import
timezone
settings
=
django
.
conf
.
settings
.
FIREWALL_SETTINGS
...
...
@@ -134,7 +135,7 @@ class BuildFirewall:
def
ipset
():
week
=
datetim
e
.
now
()
-
timedelta
(
days
=
2
)
week
=
timezon
e
.
now
()
-
timedelta
(
days
=
2
)
filter_ban
=
(
Q
(
type
=
'tempban'
,
modified_at__gte
=
week
)
|
Q
(
type
=
'permban'
))
return
BlacklistItem
.
objects
.
filter
(
filter_ban
)
.
values
(
'ipv4'
,
'reason'
)
...
...
circle/network/templates/network/base.html
View file @
15add231
...
...
@@ -52,6 +52,8 @@
<ul
class=
"nav navbar-nav"
>
{% include "network/menu.html" %}
</ul>
<a
class=
"navbar-brand pull-right"
href=
"{% url "
dashboard
.
index
"
%}"
style=
"color: white; font-size: 10px;"
><i
class=
"icon-dashboard"
></i>
{% trans "dashboard" %}
</a>
</div>
<!-- .collapse .navbar-collapse -->
</div>
<!-- navbar navbar-inverse navbar-fixed-top -->
<div
class=
"container"
>
...
...
@@ -75,7 +77,7 @@
<div
class=
"footer-container container"
>
<footer>
<p
class=
"pull-right"
><a
href=
"#"
>
Vissza az oldal tetejére
</a></p>
<p>
©
2013 BME Közigazgatási Informatikai Központ
<p>
©
{{ COMPANY_NAME }}
</footer>
</div>
<!-- .footer-container .container -->
...
...
circle/storage/tasks/local_tasks.py
View file @
15add231
...
...
@@ -46,18 +46,13 @@ def restore(disk, user):
disk
.
restore
(
task_uuid
=
restore
.
request
.
id
,
user
=
user
)
class
CreateFromURLTask
(
AbortableTask
):
def
__init__
(
self
):
self
.
bind
(
celery
)
def
run
(
self
,
**
kwargs
):
Disk
=
kwargs
.
pop
(
'cls'
)
Disk
.
create_from_url
(
url
=
kwargs
.
pop
(
'url'
),
task_uuid
=
create_from_url
.
request
.
id
,
abortable_task
=
self
,
**
kwargs
)
create_from_url
=
CreateFromURLTask
()
@celery.task
(
base
=
AbortableTask
,
bind
=
True
)
def
create_from_url
(
self
,
**
kwargs
):
Disk
=
kwargs
.
pop
(
'cls'
)
Disk
.
create_from_url
(
url
=
kwargs
.
pop
(
'url'
),
task_uuid
=
self
.
request
.
id
,
abortable_task
=
self
,
**
kwargs
)
@celery.task
...
...
circle/vm/models/network.py
View file @
15add231
...
...
@@ -53,15 +53,12 @@ class Interface(Model):
class
Meta
:
app_label
=
'vm'
db_table
=
'vm_interface'
ordering
=
(
"-vlan__managed"
,
)
def
__unicode__
(
self
):
return
'cloud-'
+
str
(
self
.
instance
.
id
)
+
'-'
+
str
(
self
.
vlan
.
vid
)
@property
def
destroyed
(
self
):
return
self
.
instance
.
destroyed_at
@property
def
mac
(
self
):
try
:
return
self
.
host
.
mac
...
...
@@ -138,34 +135,16 @@ class Interface(Model):
return
iface
def
deploy
(
self
):
if
self
.
destroyed
:
from
.instance
import
Instance
raise
Instance
.
InstanceDestroyedError
(
self
.
instance
,
"The associated instance "
"(
%
s) has already been "
"destroyed"
%
self
.
instance
)
net_tasks
.
create
.
apply_async
(
args
=
[
self
.
get_vmnetwork_desc
()],
queue
=
self
.
instance
.
get_remote_queue_name
(
'net'
))
queue_name
=
self
.
instance
.
get_remote_queue_name
(
'net'
)
return
net_tasks
.
create
.
apply_async
(
args
=
[
self
.
get_vmnetwork_desc
()],
queue
=
queue_name
)
.
get
()
def
shutdown
(
self
):
if
self
.
destroyed
:
from
.instance
import
Instance
raise
Instance
.
InstanceDestroyedError
(
self
.
instance
,
"The associated instance "
"(
%
s) has already been "
"destroyed"
%
self
.
instance
)
queue_name
=
self
.
instance
.
get_remote_queue_name
(
'net'
)
net_tasks
.
destroy
.
apply_async
(
args
=
[
self
.
get_vmnetwork_desc
()],
queue
=
queue_name
)
return
net_tasks
.
destroy
.
apply_async
(
args
=
[
self
.
get_vmnetwork_desc
()],
queue
=
queue_name
)
.
get
(
)
def
destroy
(
self
):
if
self
.
destroyed
:
return
self
.
shutdown
()
if
self
.
host
is
not
None
:
self
.
host
.
delete
()
...
...
circle/vm/operations.py
View file @
15add231
...
...
@@ -11,7 +11,8 @@ from celery.exceptions import TimeLimitExceeded
from
common.operations
import
Operation
,
register_operation
from
.tasks.local_tasks
import
async_instance_operation
,
async_node_operation
from
.models
import
(
Instance
,
InstanceActivity
,
InstanceTemplate
,
Node
,
NodeActivity
,
Instance
,
InstanceActivity
,
InstanceTemplate
,
Interface
,
Node
,
NodeActivity
,
)
...
...
@@ -56,12 +57,34 @@ class InstanceOperation(Operation):
user
=
user
)
class
AddInterfaceOperation
(
InstanceOperation
):
activity_code_suffix
=
'add_interface'
id
=
'add_interface'
name
=
_
(
"add interface"
)
description
=
_
(
"Add a new network interface for the specified VLAN to "
"the VM."
)
def
_operation
(
self
,
activity
,
user
,
system
,
vlan
,
managed
=
None
):
if
managed
is
None
:
managed
=
vlan
.
managed
net
=
Interface
.
create
(
base_activity
=
activity
,
instance
=
self
.
instance
,
managed
=
managed
,
owner
=
user
,
vlan
=
vlan
)
if
self
.
instance
.
is_running
:
net
.
deploy
()
return
net
register_operation
(
AddInterfaceOperation
)
class
DeployOperation
(
InstanceOperation
):
activity_code_suffix
=
'deploy'
id
=
'deploy'
name
=
_
(
"deploy"
)
description
=
_
(
"Deploy new virtual machine with network."
)
icon
=
'play'
def
on_commit
(
self
,
activity
):
activity
.
resultant_state
=
'RUNNING'
...
...
@@ -98,7 +121,6 @@ class DestroyOperation(InstanceOperation):
id
=
'destroy'
name
=
_
(
"destroy"
)
description
=
_
(
"Destroy virtual machine and its networks."
)
icon
=
'remove'
def
on_commit
(
self
,
activity
):
activity
.
resultant_state
=
'DESTROYED'
...
...
@@ -107,6 +129,7 @@ class DestroyOperation(InstanceOperation):
if
self
.
instance
.
node
:
# Destroy networks
with
activity
.
sub_activity
(
'destroying_net'
):
self
.
instance
.
shutdown_net
()
self
.
instance
.
destroy_net
()
# Delete virtual machine
...
...
@@ -139,7 +162,6 @@ class MigrateOperation(InstanceOperation):
id
=
'migrate'
name
=
_
(
"migrate"
)
description
=
_
(
"Live migrate running VM to another node."
)
icon
=
'truck'
def
_operation
(
self
,
activity
,
user
,
system
,
to_node
=
None
,
timeout
=
120
):
if
not
to_node
:
...
...
@@ -170,7 +192,6 @@ class RebootOperation(InstanceOperation):
id
=
'reboot'
name
=
_
(
"reboot"
)
description
=
_
(
"Reboot virtual machine with Ctrl+Alt+Del signal."
)
icon
=
'refresh'
def
_operation
(
self
,
activity
,
user
,
system
,
timeout
=
5
):
self
.
instance
.
reboot_vm
(
timeout
=
timeout
)
...
...
@@ -179,12 +200,28 @@ class RebootOperation(InstanceOperation):
register_operation
(
RebootOperation
)
class
RemoveInterfaceOperation
(
InstanceOperation
):
activity_code_suffix
=
'remove_interface'
id
=
'remove_interface'
name
=
_
(
"remove interface"
)
description
=
_
(
"Remove the specified network interface from the VM."
)
def
_operation
(
self
,
activity
,
user
,
system
,
interface
):
if
self
.
instance
.
is_running
:
interface
.
shutdown
()
interface
.
destroy
()
interface
.
delete
()
register_operation
(
RemoveInterfaceOperation
)
class
ResetOperation
(
InstanceOperation
):
activity_code_suffix
=
'reset'
id
=
'reset'
name
=
_
(
"reset"
)
description
=
_
(
"Reset virtual machine (reset button)."
)
icon
=
'bolt'
def
_operation
(
self
,
activity
,
user
,
system
,
timeout
=
5
):
self
.
instance
.
reset_vm
(
timeout
=
timeout
)
...
...
@@ -201,7 +238,6 @@ class SaveAsTemplateOperation(InstanceOperation):
Template can be shared with groups and users.
Users can instantiate Virtual Machines from Templates.
"""
)
icon
=
'save'
@staticmethod
def
_rename
(
name
):
...
...
@@ -277,7 +313,6 @@ class ShutdownOperation(InstanceOperation):
id
=
'shutdown'
name
=
_
(
"shutdown"
)
description
=
_
(
"Shutdown virtual machine with ACPI signal."
)
icon
=
'off'
def
check_precond
(
self
):
super
(
ShutdownOperation
,
self
)
.
check_precond
()
...
...
@@ -307,7 +342,6 @@ class ShutOffOperation(InstanceOperation):
id
=
'shut_off'
name
=
_
(
"shut off"
)
description
=
_
(
"Shut off VM (plug-out)."
)
icon
=
'ban-circle'
def
on_commit
(
self
,
activity
):
activity
.
resultant_state
=
'STOPPED'
...
...
@@ -334,7 +368,6 @@ class SleepOperation(InstanceOperation):
id
=
'sleep'
name
=
_
(
"sleep"
)
description
=
_
(
"Suspend virtual machine with memory dump."
)
icon
=
'moon'
def
check_precond
(
self
):
super
(
SleepOperation
,
self
)
.
check_precond
()
...
...
@@ -374,7 +407,6 @@ class WakeUpOperation(InstanceOperation):
Power on Virtual Machine and load its memory from dump.
"""
)
icon
=
'sun'
def
check_precond
(
self
):
super
(
WakeUpOperation
,
self
)
.
check_precond
()
...
...
circle/vm/tests/test_models.py
View file @
15add231
from
datetime
import
datetime
from
mock
import
Mock
,
MagicMock
,
patch
,
call
import
types
from
django.contrib.auth.models
import
User
from
django.test
import
TestCase
...
...
@@ -180,14 +181,18 @@ class InstanceActivityTestCase(TestCase):
def
test_create_no_concurrency_check
(
self
):
instance
=
MagicMock
(
spec
=
Instance
)
instance
.
activity_log
.
filter
.
return_value
.
exists
.
return_value
=
True
with
patch
.
object
(
InstanceActivity
,
'__new__'
):
try
:
InstanceActivity
.
create
(
'test'
,
instance
,
concurrency_check
=
False
)
except
ActivityInProgressError
:
raise
AssertionError
(
"'create' method checked for concurrent "
"activities."
)
mock_instance_activity_cls
=
MagicMock
(
spec
=
InstanceActivity
,
ACTIVITY_CODE_BASE
=
'test'
)
original_create
=
InstanceActivity
.
create
mocked_create
=
types
.
MethodType
(
original_create
.
im_func
,
mock_instance_activity_cls
,
original_create
.
im_class
)
try
:
mocked_create
(
'test'
,
instance
,
concurrency_check
=
False
)
except
ActivityInProgressError
:
raise
AssertionError
(
"'create' method checked for concurrent "
"activities."
)
def
test_create_sub_concurrency_check
(
self
):
iaobj
=
MagicMock
(
spec
=
InstanceActivity
)
...
...
@@ -201,10 +206,10 @@ class InstanceActivityTestCase(TestCase):
iaobj
.
activity_code
=
'test'
iaobj
.
children
.
filter
.
return_value
.
exists
.
return_value
=
True
with
patch
.
object
(
InstanceActivity
,
'__new__'
):
create_sub_func
=
InstanceActivity
.
create_sub
with
patch
(
'vm.models.activity.InstanceActivity'
):
try
:
InstanceActivity
.
create_sub
(
iaobj
,
'test'
,
concurrency_check
=
False
)
create_sub_func
(
iaobj
,
'test'
,
concurrency_check
=
False
)
except
ActivityInProgressError
:
raise
AssertionError
(
"'create_sub' method checked for "
"concurrent activities."
)
...
...
requirements/base.txt
View file @
15add231
Django==1.5.
2
Django==1.5.
6
bpython==0.12
celery==3.
0.23
celery==3.
1.11
django-braces==1.2.2
django-celery==3.
0.23
django-celery==3.
1.10
django-crispy-forms==1.4.0
django-model-utils==1.4.0
django-sizefield==0.4
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment