Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gyuricska Milán
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
A prog2-höz tartozó friss repo anyagok itt elérhetőek:
https://git.iit.bme.hu/
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