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
01f0294e
authored
Jul 14, 2016
by
Czémán Arnold
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into issue_439
parents
d989daf2
3cff165b
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
410 additions
and
74 deletions
+410
-74
circle/bower.json
+1
-1
circle/common/models.py
+8
-0
circle/dashboard/forms.py
+1
-1
circle/dashboard/migrations/0004_profile_desktop_notifications.py
+19
-0
circle/dashboard/models.py
+4
-0
circle/dashboard/static/dashboard/activity.js
+36
-2
circle/dashboard/static/dashboard/dashboard.js
+8
-0
circle/dashboard/static/dashboard/dashboard.less
+13
-2
circle/dashboard/templates/dashboard/_display-name.html
+0
-1
circle/dashboard/templates/dashboard/_manage_access.html
+1
-1
circle/dashboard/templates/dashboard/_vm-renew.html
+1
-1
circle/dashboard/templates/dashboard/base.html
+3
-0
circle/dashboard/templates/dashboard/node-detail.html
+5
-0
circle/dashboard/templates/dashboard/node-detail/_activity-timeline.html
+5
-3
circle/dashboard/templates/dashboard/nodeactivity_detail.html
+83
-0
circle/dashboard/templates/dashboard/vm-detail.html
+4
-4
circle/dashboard/templates/dashboard/vm-detail/home.html
+2
-2
circle/dashboard/tests/test_views.py
+86
-27
circle/dashboard/urls.py
+3
-1
circle/dashboard/views/node.py
+40
-3
circle/dashboard/views/user.py
+2
-0
circle/dashboard/views/vm.py
+13
-0
circle/firewall/models.py
+5
-1
circle/firewall/tests/test_firewall.py
+14
-5
circle/request/forms.py
+4
-1
circle/request/tables.py
+2
-1
circle/request/templates/request/detail.html
+3
-0
circle/request/views.py
+6
-0
circle/vm/models/activity.py
+7
-8
circle/vm/models/instance.py
+9
-4
circle/vm/models/node.py
+2
-0
circle/vm/operations.py
+3
-1
circle/vm/tasks/vm_tasks.py
+1
-1
miscellaneous/portal-uwsgi.service
+13
-0
requirements/base.txt
+3
-3
No files found.
circle/bower.json
View file @
01f0294e
...
@@ -14,7 +14,7 @@
...
@@ -14,7 +14,7 @@
"bootstrap"
:
"~3.2.0"
,
"bootstrap"
:
"~3.2.0"
,
"fontawesome"
:
"~4.3.0"
,
"fontawesome"
:
"~4.3.0"
,
"jquery"
:
"~2.1.1"
,
"jquery"
:
"~2.1.1"
,
"no-vnc"
:
"
*
"
,
"no-vnc"
:
"
0.5.1
"
,
"jquery-knob"
:
"~1.2.9"
,
"jquery-knob"
:
"~1.2.9"
,
"jquery-simple-slider"
:
"https://github.com/BME-IK/jquery-simple-slider.git"
,
"jquery-simple-slider"
:
"https://github.com/BME-IK/jquery-simple-slider.git"
,
"bootbox"
:
"~4.3.0"
,
"bootbox"
:
"~4.3.0"
,
...
...
circle/common/models.py
View file @
01f0294e
...
@@ -232,6 +232,14 @@ class ActivityModel(TimeStampedModel):
...
@@ -232,6 +232,14 @@ class ActivityModel(TimeStampedModel):
else
:
else
:
return
code
return
code
def
get_status_id
(
self
):
if
self
.
succeeded
is
None
:
return
'wait'
elif
self
.
succeeded
:
return
'success'
else
:
return
'failed'
@celery.task
()
@celery.task
()
def
compute_cached
(
method
,
instance
,
memcached_seconds
,
def
compute_cached
(
method
,
instance
,
memcached_seconds
,
...
...
circle/dashboard/forms.py
View file @
01f0294e
...
@@ -1223,7 +1223,7 @@ class MyProfileForm(forms.ModelForm):
...
@@ -1223,7 +1223,7 @@ class MyProfileForm(forms.ModelForm):
class
Meta
:
class
Meta
:
fields
=
(
'preferred_language'
,
'email_notifications'
,
fields
=
(
'preferred_language'
,
'email_notifications'
,
'use_gravatar'
,
)
'
desktop_notifications'
,
'
use_gravatar'
,
)
model
=
Profile
model
=
Profile
@property
@property
...
...
circle/dashboard/migrations/0004_profile_desktop_notifications.py
0 → 100644
View file @
01f0294e
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
models
,
migrations
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'dashboard'
,
'0003_message'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'profile'
,
name
=
'desktop_notifications'
,
field
=
models
.
BooleanField
(
default
=
False
,
help_text
=
'Whether user wants to get desktop notification when an activity has finished and the window is not in focus.'
,
verbose_name
=
'Desktop notifications'
),
),
]
circle/dashboard/models.py
View file @
01f0294e
...
@@ -184,6 +184,10 @@ class Profile(Model):
...
@@ -184,6 +184,10 @@ class Profile(Model):
email_notifications
=
BooleanField
(
email_notifications
=
BooleanField
(
verbose_name
=
_
(
"Email notifications"
),
default
=
True
,
verbose_name
=
_
(
"Email notifications"
),
default
=
True
,
help_text
=
_
(
'Whether user wants to get digested email notifications.'
))
help_text
=
_
(
'Whether user wants to get digested email notifications.'
))
desktop_notifications
=
BooleanField
(
verbose_name
=
_
(
"Desktop notifications"
),
default
=
False
,
help_text
=
_
(
'Whether user wants to get desktop notification when an '
'activity has finished and the window is not in focus.'
))
smb_password
=
CharField
(
smb_password
=
CharField
(
max_length
=
20
,
max_length
=
20
,
verbose_name
=
_
(
'Samba password'
),
verbose_name
=
_
(
'Samba password'
),
...
...
circle/dashboard/static/dashboard/activity.js
View file @
01f0294e
...
@@ -169,6 +169,9 @@ $(function() {
...
@@ -169,6 +169,9 @@ $(function() {
);
);
}
else
{
}
else
{
in_progress
=
false
;
in_progress
=
false
;
if
(
document
.
hasFocus
()
===
false
&&
userWantNotifications
()){
sendNotification
(
generateMessageFromLastActivity
());
}
if
(
reload_vm_detail
)
location
.
reload
();
if
(
reload_vm_detail
)
location
.
reload
();
if
(
runs
>
1
)
addConnectText
();
if
(
runs
>
1
)
addConnectText
();
}
}
...
@@ -181,18 +184,49 @@ $(function() {
...
@@ -181,18 +184,49 @@ $(function() {
}
}
});
});
// Notification init
$
(
function
(){
if
(
userWantNotifications
())
Notification
.
requestPermission
();
});
function
generateMessageFromLastActivity
(){
var
ac
=
$
(
"div.activity"
).
first
();
var
error
=
ac
.
children
(
".timeline-icon-failed"
).
length
;
var
sign
=
(
error
===
1
)
?
"❌ "
:
"✓ "
;
var
msg
=
ac
.
children
(
"strong"
).
text
().
replace
(
/
\s
+/g
,
" "
);
return
sign
+
msg
;
}
function
sendNotification
(
message
)
{
var
options
=
{
icon
:
"/static/dashboard/img/favicon.png"
};
if
(
Notification
.
permission
===
"granted"
)
{
var
notification
=
new
Notification
(
message
,
options
);
}
else
if
(
Notification
.
permission
!==
"denied"
)
{
Notification
.
requestPermission
(
function
(
permission
)
{
if
(
permission
===
"granted"
)
{
var
notification
=
new
Notification
(
message
,
options
);
}
});
}
}
function
userWantNotifications
(){
var
dn
=
$
(
"#user-options"
).
data
(
"desktop_notifications"
);
return
dn
===
"True"
;
}
function
addConnectText
()
{
function
addConnectText
()
{
var
activities
=
$
(
".timeline .activity"
);
var
activities
=
$
(
".timeline .activity"
);
if
(
activities
.
length
>
1
)
{
if
(
activities
.
length
>
1
)
{
if
(
activities
.
eq
(
0
).
data
(
"activity-code"
)
==
"vm.Instance.wake_up"
||
if
(
activities
.
eq
(
0
).
data
(
"activity-code"
)
==
"vm.Instance.wake_up"
||
activities
.
eq
(
0
).
data
(
"activity-code"
)
==
"vm.Instance.agent"
)
{
activities
.
eq
(
0
).
data
(
"activity-code"
)
==
"vm.Instance.agent"
)
{
$
(
"#vm-detail-successful
l
-boot"
).
slideDown
(
500
);
$
(
"#vm-detail-successful-boot"
).
slideDown
(
500
);
}
}
}
}
}
}
String
.
prototype
.
hashCode
=
function
()
{
String
.
prototype
.
hashCode
=
function
()
{
var
hash
=
0
,
i
,
chr
,
len
;
var
hash
=
0
,
i
,
chr
,
len
;
if
(
this
.
length
===
0
)
return
hash
;
if
(
this
.
length
===
0
)
return
hash
;
...
...
circle/dashboard/static/dashboard/dashboard.js
View file @
01f0294e
...
@@ -557,3 +557,11 @@ $(function () {
...
@@ -557,3 +557,11 @@ $(function () {
"alert-"
+
$
(
this
).
val
());
"alert-"
+
$
(
this
).
val
());
});
});
});
});
/* select all in template list */
$
(
function
()
{
$
(
"#manage-access-select-all"
).
click
(
function
(
e
)
{
var
inputs
=
$
(
this
).
closest
(
"table"
).
find
(
'input[type="checkbox"]'
);
inputs
.
prop
(
"checked"
,
!
inputs
.
prop
(
"checked"
));
});
});
circle/dashboard/static/dashboard/dashboard.less
View file @
01f0294e
...
@@ -284,7 +284,7 @@ a.hover-black {
...
@@ -284,7 +284,7 @@ a.hover-black {
}
}
.hover-black:hover {
.hover-black:hover {
color: black
/*#d9534f*/;
color: black
; /*#d9534f*/
text-decoration: none;
text-decoration: none;
}
}
...
@@ -1285,9 +1285,16 @@ textarea[name="new_members"] {
...
@@ -1285,9 +1285,16 @@ textarea[name="new_members"] {
}
}
}
}
#vm-detail-successful
l
-boot {
#vm-detail-successful-boot {
margin-bottom: 20px;
margin-bottom: 20px;
display: none;
display: none;
.label {
width: 100%;
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
#vm-detail-access-help {
#vm-detail-access-help {
...
@@ -1523,3 +1530,7 @@ textarea[name="new_members"] {
...
@@ -1523,3 +1530,7 @@ textarea[name="new_members"] {
text-align: center;
text-align: center;
width: 100%;
width: 100%;
}
}
#manage-access-select-all {
cursor: pointer;
}
circle/dashboard/templates/dashboard/_display-name.html
View file @
01f0294e
...
@@ -14,5 +14,4 @@
...
@@ -14,5 +14,4 @@
({% trans "username" %}: {{ user.username }})
({% trans "username" %}: {{ user.username }})
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
circle/dashboard/templates/dashboard/_manage_access.html
View file @
01f0294e
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
<th></th>
<th></th>
<th>
{% trans "Who" %}
</th>
<th>
{% trans "Who" %}
</th>
<th>
{% trans "What" %}
</th>
<th>
{% trans "What" %}
</th>
<th><i
class=
"fa fa-times"
></i></th>
<th><i
id=
"manage-access-select-all"
class=
"fa fa-times"
></i></th>
</tr>
</tr>
</thead>
</thead>
<tbody>
<tbody>
...
...
circle/dashboard/templates/dashboard/_vm-renew.html
View file @
01f0294e
...
@@ -8,7 +8,7 @@
...
@@ -8,7 +8,7 @@
<a
class=
"btn btn-default"
href=
"{{object.get_absolute_url}}"
data-dismiss=
"modal"
>
<a
class=
"btn btn-default"
href=
"{{object.get_absolute_url}}"
data-dismiss=
"modal"
>
{% trans "Cancel" %}
{% trans "Cancel" %}
</a>
</a>
{% if lease_types and not request.token_user %}
{% if
object.active and
lease_types and not request.token_user %}
<a
class=
"btn btn-primary"
id=
"vm-renew-request-lease-button"
<a
class=
"btn btn-primary"
id=
"vm-renew-request-lease-button"
href=
"{% url "
request
.
views
.
request-lease
"
vm_pk=
object.pk
%}"
>
href=
"{% url "
request
.
views
.
request-lease
"
vm_pk=
object.pk
%}"
>
<i
class=
"fa fa-forward"
></i>
<i
class=
"fa fa-forward"
></i>
...
...
circle/dashboard/templates/dashboard/base.html
View file @
01f0294e
...
@@ -12,6 +12,9 @@
...
@@ -12,6 +12,9 @@
{% block navbar %}
{% block navbar %}
{% if request.user.is_authenticated and request.user.pk and not request.token_user %}
{% if request.user.is_authenticated and request.user.pk and not request.token_user %}
<span
id=
"user-options"
data-desktop_notifications=
"{{ request.user.profile.desktop_notifications }}"
><span>
<ul
class=
"nav navbar-nav navbar-right"
id=
"dashboard-menu"
>
<ul
class=
"nav navbar-nav navbar-right"
id=
"dashboard-menu"
>
{% if request.user.is_superuser %}
{% if request.user.is_superuser %}
{% if ADMIN_ENABLED %}
{% if ADMIN_ENABLED %}
...
...
circle/dashboard/templates/dashboard/node-detail.html
View file @
01f0294e
...
@@ -56,6 +56,11 @@
...
@@ -56,6 +56,11 @@
<span
class=
"label label-warning"
>
{% trans "Offline" %}
</span>
<span
class=
"label label-warning"
>
{% trans "Offline" %}
</span>
{% endif %}
{% endif %}
</div>
</div>
<div>
{% for k, v in queues.iteritems %}
<span
class=
"label label-{% if v %}success{% else %}danger{% endif %}"
>
{{ k }}
</span>
{% endfor %}
</div>
</div>
</div>
<div
class=
"col-md-10"
id=
"node-detail-pane"
>
<div
class=
"col-md-10"
id=
"node-detail-pane"
>
<div
class=
"panel panel-default"
id=
"node-detail-panel"
>
<div
class=
"panel panel-default"
id=
"node-detail-panel"
>
...
...
circle/dashboard/templates/dashboard/node-detail/_activity-timeline.html
View file @
01f0294e
...
@@ -5,10 +5,11 @@
...
@@ -5,10 +5,11 @@
{% for a in activities %}
{% for a in activities %}
<div
class=
"activity"
data-activity-id=
"{{ a.pk }}"
>
<div
class=
"activity"
data-activity-id=
"{{ a.pk }}"
>
<span
class=
"timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}"
>
<span
class=
"timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}"
>
<i
class=
"fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-
plus
{% endif %}"
></i>
<i
class=
"fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-
{{a.icon}}
{% endif %}"
></i>
</span>
</span>
<strong
title=
"{{ a.result.get_admin_text }}"
>
<strong
title=
"{{ a.result.get_admin_text }}"
>
{{ a.readable_name.get_admin_text|capfirst }}
<a
href=
"{{ a.get_absolute_url }}"
>
{{ a.readable_name.get_admin_text|capfirst }}
</a>
</strong>
</strong>
<span
title=
"{{ a.started }}"
>
{{ a.started|arrowfilter:LANGUAGE_CODE }}
</span>
{% if a.user %}, {{ a.user }}{% endif %}
<span
title=
"{{ a.started }}"
>
{{ a.started|arrowfilter:LANGUAGE_CODE }}
</span>
{% if a.user %}, {{ a.user }}{% endif %}
...
@@ -19,7 +20,8 @@
...
@@ -19,7 +20,8 @@
<div
data-activity-id=
"{{ s.pk }}"
<div
data-activity-id=
"{{ s.pk }}"
class=
"sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}"
>
class=
"sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}"
>
<span
title=
"{{ s.result.get_admin_text }}"
>
<span
title=
"{{ s.result.get_admin_text }}"
>
{{ s.readable_name|get_text:user }}
<a
href=
"{{ s.get_absolute_url }}"
>
{{ s.readable_name|get_text:user }}
</a>
</span>
</span>
–
–
{% if s.finished %}
{% if s.finished %}
...
...
circle/dashboard/templates/dashboard/nodeactivity_detail.html
0 → 100644
View file @
01f0294e
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load hro %}
{% block content %}
<div
class=
"body-content"
>
<div
class=
"page-header"
>
<h1>
<div
class=
"pull-right"
id=
"vm-activity-state"
>
<span
class=
"label label-{% if object.get_status_id == 'wait' %}info{% else %}{% if object.succeeded %}success{% else %}danger{% endif %}{% endif %}"
>
<span>
{{ object.get_status_id|upper }}
</span>
</span>
</div>
<i
class=
"fa fa-{{icon}}"
></i>
{{ object.node.name }}: {{object.readable_name|get_text:user}}
</h1>
</div>
<div
class=
"row"
>
<div
class=
"col-md-6"
id=
"vm-info-pane"
>
{% include "dashboard/vm-detail/_activity-timeline.html" with active=object %}
</div>
<div
class=
"col-md-6"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-body"
>
<dl>
<dt>
{% trans "activity code" %}
</dt>
<dd>
{{object.activity_code}}
</dd>
<dt>
{% trans "node" %}
</dt>
<dd><a
href=
"{{object.node.get_absolute_url}}"
>
{{object.node}}
</a></dd>
<dt>
{% trans "time" %}
</dt>
<dd>
{{object.started|default:'n/a'}} → {{object.finished|default:'n/a'}}
</dd>
<dt>
{% trans "user" %}
</dt>
<dd>
<a
href=
"{{ object.user.profile.get_absolute_url }}"
>
{{object.user|default:'(system)'}}
</a></dd>
<dt>
{% trans "type" %}
</dt>
<dd>
{% if object.parent %}
{% blocktrans with url=object.parent.get_absolute_url name=object.parent %}
subactivity of
<a
href=
"{{url}}"
>
{{name}}
</a>
{% endblocktrans %}
{% else %}{% trans "top level activity" %}{% endif %}
</dd>
<dt>
{% trans "task uuid" %}
</dt>
<dd>
{{ object.task_uuid|default:'n/a' }}
</dd>
<dt>
{% trans "status" %}
</dt>
<dd
id=
"activity_status"
>
{{ object.get_status_id }}
</dd>
<dt>
{% trans "result" %}
</dt>
<dd><textarea
class=
"form-control"
id=
"activity_result_text"
>
{{object.result|get_text:user}}
</textarea></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>
{% endblock %}
circle/dashboard/templates/dashboard/vm-detail.html
View file @
01f0294e
...
@@ -132,7 +132,7 @@
...
@@ -132,7 +132,7 @@
<dd>
<dd>
<div
class=
"input-group"
>
<div
class=
"input-group"
>
<input
type=
"text"
id=
"vm-details-pw-input"
class=
"form-control input-sm input-tags"
<input
type=
"text"
id=
"vm-details-pw-input"
class=
"form-control input-sm input-tags"
value=
"{{ instance.pw }}"
spellcheck=
"false"
/>
value=
"{{ instance.pw }}"
spellcheck=
"false"
autocomplete=
"new-password"
/>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-pw-show"
<span
class=
"input-group-addon input-tags"
id=
"vm-details-pw-show"
title=
"{% trans "
Show
password
"
%}"
data-container=
"body"
>
title=
"{% trans "
Show
password
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-eye"
id=
"vm-details-pw-eye"
></i>
<i
class=
"fa fa-eye"
id=
"vm-details-pw-eye"
></i>
...
@@ -192,11 +192,11 @@
...
@@ -192,11 +192,11 @@
{% endif %}
{% endif %}
</div>
</div>
<div
class=
"col-md-8"
id=
"vm-detail-pane"
>
<div
class=
"col-md-8"
id=
"vm-detail-pane"
>
<div
class=
"big"
id=
"vm-detail-successful
l
-boot"
>
<div
class=
"big"
id=
"vm-detail-successful-boot"
>
<
span
class=
"label label-info"
data-status=
"{{ instance.status }}"
>
<
div
class=
"label label-info"
data-status=
"{{ instance.status }}"
>
<i
class=
"fa fa-check"
></i>
<i
class=
"fa fa-check"
></i>
{% trans "The virtual machine successfully started, you can connect now." %}
{% trans "The virtual machine successfully started, you can connect now." %}
</
span
>
</
div
>
</div>
</div>
<div
class=
"panel panel-default"
id=
"vm-detail-panel"
>
<div
class=
"panel panel-default"
id=
"vm-detail-panel"
>
<ul
class=
"nav nav-pills panel-heading"
>
<ul
class=
"nav nav-pills panel-heading"
>
...
...
circle/dashboard/templates/dashboard/vm-detail/home.html
View file @
01f0294e
...
@@ -59,8 +59,8 @@
...
@@ -59,8 +59,8 @@
{% if instance.is_expiring %}
<i
class=
"fa fa-warning-sign text-danger"
></i>
{% endif %}
{% if instance.is_expiring %}
<i
class=
"fa fa-warning-sign text-danger"
></i>
{% endif %}
<span
id=
"vm-details-renew-op"
>
<span
id=
"vm-details-renew-op"
>
{% with op=op.renew %}{% if op %}
{% with op=op.renew %}{% if op %}
<a
href=
"{{op.get_url}}"
class=
"btn btn-
{{op.effect}} btn-xs
<a
href=
"{{op.get_url}}"
class=
"btn btn-
xs operation operation-{{ op.op }}
operation operation-{{op.op}
}"
>
{% if op.disabled %}btn-default disabled{% else %}btn-{{op.effect}}{% endif %
}"
>
<i
class=
"fa fa-{{op.icon}}"
></i>
<i
class=
"fa fa-{{op.icon}}"
></i>
{{op.name}}
{{op.name}}
</a>
</a>
...
...
circle/dashboard/tests/test_views.py
View file @
01f0294e
...
@@ -270,33 +270,6 @@ class VmDetailTest(LoginMixin, MockCeleryMixin, TestCase):
...
@@ -270,33 +270,6 @@ class VmDetailTest(LoginMixin, MockCeleryMixin, TestCase):
self
.
assertEqual
(
InstanceTemplate
.
objects
.
get
(
id
=
1
)
.
raw_data
,
self
.
assertEqual
(
InstanceTemplate
.
objects
.
get
(
id
=
1
)
.
raw_data
,
"<devices></devices>"
)
"<devices></devices>"
)
def
test_permitted_lease_delete_w_template_using_it
(
self
):
c
=
Client
()
self
.
login
(
c
,
'superuser'
)
leases
=
Lease
.
objects
.
count
()
response
=
c
.
post
(
"/dashboard/lease/delete/1/"
)
self
.
assertEqual
(
response
.
status_code
,
400
)
self
.
assertEqual
(
leases
,
Lease
.
objects
.
count
())
def
test_permitted_lease_delete_w_template_not_using_it
(
self
):
c
=
Client
()
self
.
login
(
c
,
'superuser'
)
lease
=
Lease
.
objects
.
create
(
name
=
"yay"
)
leases
=
Lease
.
objects
.
count
()
response
=
c
.
post
(
"/dashboard/lease/delete/
%
d/"
%
lease
.
pk
)
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
leases
-
1
,
Lease
.
objects
.
count
())
def
test_unpermitted_lease_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
'user1'
)
leases
=
Lease
.
objects
.
count
()
response
=
c
.
post
(
"/dashboard/lease/delete/1/"
)
# redirect to the login page
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
leases
,
Lease
.
objects
.
count
())
def
test_notification_read
(
self
):
def
test_notification_read
(
self
):
c
=
Client
()
c
=
Client
()
self
.
login
(
c
,
"user1"
)
self
.
login
(
c
,
"user1"
)
...
@@ -615,6 +588,12 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
...
@@ -615,6 +588,12 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
node
=
Node
.
objects
.
get
(
pk
=
1
)
node
=
Node
.
objects
.
get
(
pk
=
1
)
trait
,
created
=
Trait
.
objects
.
get_or_create
(
name
=
'testtrait'
)
trait
,
created
=
Trait
.
objects
.
get_or_create
(
name
=
'testtrait'
)
node
.
traits
.
add
(
trait
)
node
.
traits
.
add
(
trait
)
self
.
patcher
=
patch
(
"vm.tasks.vm_tasks.get_queues"
,
return_value
=
{
'x'
:
[{
'name'
:
"devenv.vm.fast"
}],
'y'
:
[{
'name'
:
"devenv.vm.slow"
}],
'z'
:
[{
'name'
:
"devenv.net.fast"
}],
})
self
.
patcher
.
start
()
def
tearDown
(
self
):
def
tearDown
(
self
):
super
(
NodeDetailTest
,
self
)
.
tearDown
()
super
(
NodeDetailTest
,
self
)
.
tearDown
()
...
@@ -622,6 +601,7 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
...
@@ -622,6 +601,7 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
self
.
u2
.
delete
()
self
.
u2
.
delete
()
self
.
us
.
delete
()
self
.
us
.
delete
()
self
.
g1
.
delete
()
self
.
g1
.
delete
()
self
.
patcher
.
stop
()
def
test_404_superuser_node_page
(
self
):
def
test_404_superuser_node_page
(
self
):
c
=
Client
()
c
=
Client
()
...
@@ -629,6 +609,12 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
...
@@ -629,6 +609,12 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
response
=
c
.
get
(
'/dashboard/node/25555/'
)
response
=
c
.
get
(
'/dashboard/node/25555/'
)
self
.
assertEqual
(
response
.
status_code
,
404
)
self
.
assertEqual
(
response
.
status_code
,
404
)
def
test_200_superuser_node_page
(
self
):
c
=
Client
()
self
.
login
(
c
,
'superuser'
)
response
=
c
.
get
(
'/dashboard/node/1/'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_302_user_node_page
(
self
):
def
test_302_user_node_page
(
self
):
c
=
Client
()
c
=
Client
()
self
.
login
(
c
,
'user1'
)
self
.
login
(
c
,
'user1'
)
...
@@ -1758,3 +1744,76 @@ class SshKeyTest(LoginMixin, TestCase):
...
@@ -1758,3 +1744,76 @@ class SshKeyTest(LoginMixin, TestCase):
resp
=
c
.
post
(
"/dashboard/sshkey/delete/1/"
)
resp
=
c
.
post
(
"/dashboard/sshkey/delete/1/"
)
self
.
assertEqual
(
403
,
resp
.
status_code
)
self
.
assertEqual
(
403
,
resp
.
status_code
)
class
LeaseDetailTest
(
LoginMixin
,
TestCase
):
fixtures
=
[
'test-vm-fixture.json'
,
]
def
setUp
(
self
):
self
.
u1
=
User
.
objects
.
create
(
username
=
'user1'
)
self
.
u1
.
set_password
(
'password'
)
self
.
u1
.
save
()
self
.
u2
=
User
.
objects
.
create
(
username
=
'user2'
,
is_staff
=
True
)
self
.
u2
.
set_password
(
'password'
)
self
.
u2
.
save
()
self
.
us
=
User
.
objects
.
create
(
username
=
'superuser'
,
is_superuser
=
True
)
self
.
us
.
set_password
(
'password'
)
self
.
us
.
save
()
def
tearDown
(
self
):
super
(
LeaseDetailTest
,
self
)
.
tearDown
()
self
.
u1
.
delete
()
self
.
u2
.
delete
()
self
.
us
.
delete
()
def
test_anon_view
(
self
):
c
=
Client
()
response
=
c
.
get
(
"/dashboard/lease/1/"
)
self
.
assertEqual
(
response
.
status_code
,
302
)
def
test_unpermitted_view
(
self
):
c
=
Client
()
self
.
login
(
c
,
'user1'
)
response
=
c
.
get
(
"/dashboard/lease/1/"
)
self
.
assertEqual
(
response
.
status_code
,
302
)
def
test_operator_view
(
self
):
c
=
Client
()
self
.
login
(
c
,
'user2'
)
lease
=
Lease
.
objects
.
get
()
lease
.
set_level
(
self
.
u2
,
"owner"
)
response
=
c
.
get
(
"/dashboard/lease/1/"
)
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_superuser_view
(
self
):
c
=
Client
()
self
.
login
(
c
,
'superuser'
)
response
=
c
.
get
(
"/dashboard/lease/1/"
)
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_permitted_lease_delete_w_template_using_it
(
self
):
c
=
Client
()
self
.
login
(
c
,
'superuser'
)
leases
=
Lease
.
objects
.
count
()
response
=
c
.
post
(
"/dashboard/lease/delete/1/"
)
self
.
assertEqual
(
response
.
status_code
,
400
)
self
.
assertEqual
(
leases
,
Lease
.
objects
.
count
())
def
test_permitted_lease_delete_w_template_not_using_it
(
self
):
c
=
Client
()
self
.
login
(
c
,
'superuser'
)
lease
=
Lease
.
objects
.
create
(
name
=
"yay"
)
leases
=
Lease
.
objects
.
count
()
response
=
c
.
post
(
"/dashboard/lease/delete/
%
d/"
%
lease
.
pk
)
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
leases
-
1
,
Lease
.
objects
.
count
())
def
test_unpermitted_lease_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
'user1'
)
leases
=
Lease
.
objects
.
count
()
response
=
c
.
post
(
"/dashboard/lease/delete/1/"
)
# redirect to the login page
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
leases
,
Lease
.
objects
.
count
())
circle/dashboard/urls.py
View file @
01f0294e
...
@@ -25,7 +25,7 @@ from .views import (
...
@@ -25,7 +25,7 @@ from .views import (
GroupDetailView
,
GroupList
,
IndexView
,
GroupDetailView
,
GroupList
,
IndexView
,
InstanceActivityDetail
,
LeaseCreate
,
LeaseDelete
,
LeaseDetail
,
InstanceActivityDetail
,
LeaseCreate
,
LeaseDelete
,
LeaseDetail
,
MyPreferencesView
,
NodeAddTraitView
,
NodeCreate
,
NodeDelete
,
MyPreferencesView
,
NodeAddTraitView
,
NodeCreate
,
NodeDelete
,
NodeDetailView
,
NodeList
,
NodeDetailView
,
NodeList
,
NodeActivityDetail
,
NotificationView
,
TemplateAclUpdateView
,
TemplateCreate
,
NotificationView
,
TemplateAclUpdateView
,
TemplateCreate
,
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TemplateDelete
,
TemplateDetail
,
TemplateList
,
vm_activity
,
VmCreate
,
VmDetailView
,
vm_activity
,
VmCreate
,
VmDetailView
,
...
@@ -136,6 +136,8 @@ urlpatterns = patterns(
...
@@ -136,6 +136,8 @@ urlpatterns = patterns(
name
=
'dashboard.views.node-activity-list'
),
name
=
'dashboard.views.node-activity-list'
),
url
(
r'^node/create/$'
,
NodeCreate
.
as_view
(),
url
(
r'^node/create/$'
,
NodeCreate
.
as_view
(),
name
=
'dashboard.views.node-create'
),
name
=
'dashboard.views.node-create'
),
url
(
r'^node/activity/(?P<pk>\d+)/$'
,
NodeActivityDetail
.
as_view
(),
name
=
'dashboard.views.node-activity'
),
url
(
r'^favourite/$'
,
FavouriteView
.
as_view
(),
url
(
r'^favourite/$'
,
FavouriteView
.
as_view
(),
name
=
'dashboard.views.favourite'
),
name
=
'dashboard.views.favourite'
),
...
...
circle/dashboard/views/node.py
View file @
01f0294e
...
@@ -37,6 +37,7 @@ from django_tables2 import SingleTableView
...
@@ -37,6 +37,7 @@ from django_tables2 import SingleTableView
from
firewall.models
import
Host
from
firewall.models
import
Host
from
vm.models
import
Node
,
NodeActivity
,
Trait
from
vm.models
import
Node
,
NodeActivity
,
Trait
from
vm.tasks.vm_tasks
import
check_queue
from
..forms
import
TraitForm
,
HostForm
,
NodeForm
from
..forms
import
TraitForm
,
HostForm
,
NodeForm
from
..tables
import
NodeListTable
from
..tables
import
NodeListTable
...
@@ -81,6 +82,20 @@ node_ops = OrderedDict([
...
@@ -81,6 +82,20 @@ node_ops = OrderedDict([
])
])
def
_get_activity_icon
(
act
):
op
=
act
.
get_operation
()
if
op
and
op
.
id
in
node_ops
:
return
node_ops
[
op
.
id
]
.
icon
else
:
return
"cog"
def
_format_activities
(
acts
):
for
i
in
acts
:
i
.
icon
=
_get_activity_icon
(
i
)
return
acts
class
NodeDetailView
(
LoginRequiredMixin
,
class
NodeDetailView
(
LoginRequiredMixin
,
GraphMixin
,
DetailView
):
GraphMixin
,
DetailView
):
template_name
=
"dashboard/node-detail.html"
template_name
=
"dashboard/node-detail.html"
...
@@ -103,10 +118,17 @@ class NodeDetailView(LoginRequiredMixin,
...
@@ -103,10 +118,17 @@ class NodeDetailView(LoginRequiredMixin,
context
[
'ops'
]
=
get_operations
(
self
.
object
,
self
.
request
.
user
)
context
[
'ops'
]
=
get_operations
(
self
.
object
,
self
.
request
.
user
)
context
[
'op'
]
=
{
i
.
op
:
i
for
i
in
context
[
'ops'
]}
context
[
'op'
]
=
{
i
.
op
:
i
for
i
in
context
[
'ops'
]}
context
[
'show_show_all'
]
=
len
(
na
)
>
10
context
[
'show_show_all'
]
=
len
(
na
)
>
10
context
[
'activities'
]
=
na
[:
10
]
context
[
'activities'
]
=
_format_activities
(
na
[:
10
])
context
[
'trait_form'
]
=
form
context
[
'trait_form'
]
=
form
context
[
'graphite_enabled'
]
=
(
context
[
'graphite_enabled'
]
=
(
settings
.
GRAPHITE_URL
is
not
None
)
settings
.
GRAPHITE_URL
is
not
None
)
node_hostname
=
self
.
object
.
host
.
hostname
context
[
'queues'
]
=
{
'vmcelery.fast'
:
check_queue
(
node_hostname
,
"vm"
,
"fast"
),
'vmcelery.slow'
:
check_queue
(
node_hostname
,
"vm"
,
"slow"
),
'netcelery.fast'
:
check_queue
(
node_hostname
,
"net"
,
"fast"
),
}
return
context
return
context
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
...
@@ -298,8 +320,8 @@ class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
...
@@ -298,8 +320,8 @@ class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
show_all
=
request
.
GET
.
get
(
"show_all"
,
"false"
)
==
"true"
show_all
=
request
.
GET
.
get
(
"show_all"
,
"false"
)
==
"true"
node
=
Node
.
objects
.
get
(
pk
=
pk
)
node
=
Node
.
objects
.
get
(
pk
=
pk
)
activities
=
NodeActivity
.
objects
.
filter
(
activities
=
_format_activities
(
NodeActivity
.
objects
.
filter
(
node
=
node
,
parent
=
None
)
.
order_by
(
'-started'
)
.
select_related
()
node
=
node
,
parent
=
None
)
.
order_by
(
'-started'
)
.
select_related
()
)
show_show_all
=
len
(
activities
)
>
10
show_show_all
=
len
(
activities
)
>
10
if
not
show_all
:
if
not
show_all
:
...
@@ -316,3 +338,18 @@ class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
...
@@ -316,3 +338,18 @@ class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
json
.
dumps
(
response
),
json
.
dumps
(
response
),
content_type
=
"application/json"
content_type
=
"application/json"
)
)
class
NodeActivityDetail
(
LoginRequiredMixin
,
SuperuserRequiredMixin
,
DetailView
):
model
=
NodeActivity
context_object_name
=
'nodeactivity'
# much simpler to mock object
template_name
=
'dashboard/nodeactivity_detail.html'
def
get_context_data
(
self
,
**
kwargs
):
ctx
=
super
(
NodeActivityDetail
,
self
)
.
get_context_data
(
**
kwargs
)
ctx
[
'activities'
]
=
_format_activities
(
NodeActivity
.
objects
.
filter
(
node
=
self
.
object
.
node
,
parent
=
None
)
.
order_by
(
'-started'
)
.
select_related
())
ctx
[
'icon'
]
=
_get_activity_icon
(
self
.
object
)
return
ctx
circle/dashboard/views/user.py
View file @
01f0294e
...
@@ -200,6 +200,8 @@ class MyPreferencesView(UpdateView):
...
@@ -200,6 +200,8 @@ class MyPreferencesView(UpdateView):
data
=
request
.
POST
)
data
=
request
.
POST
)
if
form
.
is_valid
():
if
form
.
is_valid
():
form
.
save
()
form
.
save
()
messages
.
success
(
self
.
request
,
_
(
"Password successfully changed."
))
if
form
.
is_valid
():
if
form
.
is_valid
():
return
redirect_response
return
redirect_response
...
...
circle/dashboard/views/vm.py
View file @
01f0294e
...
@@ -105,6 +105,19 @@ class VmDetailView(GraphMixin, CheckedDetailView):
...
@@ -105,6 +105,19 @@ class VmDetailView(GraphMixin, CheckedDetailView):
template_name
=
"dashboard/vm-detail.html"
template_name
=
"dashboard/vm-detail.html"
model
=
Instance
model
=
Instance
def
get
(
self
,
*
args
,
**
kwargs
):
if
self
.
request
.
is_ajax
():
return
JsonResponse
(
self
.
get_json_data
())
else
:
return
super
(
VmDetailView
,
self
)
.
get
(
*
args
,
**
kwargs
)
def
get_json_data
(
self
):
instance
=
self
.
get_object
()
return
{
"status"
:
instance
.
status
,
"host"
:
instance
.
get_connect_host
(),
"port"
:
instance
.
get_connect_port
(),
"password"
:
instance
.
pw
}
def
get_context_data
(
self
,
**
kwargs
):
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
VmDetailView
,
self
)
.
get_context_data
(
**
kwargs
)
context
=
super
(
VmDetailView
,
self
)
.
get_context_data
(
**
kwargs
)
instance
=
context
[
'instance'
]
instance
=
context
[
'instance'
]
...
...
circle/firewall/models.py
View file @
01f0294e
...
@@ -499,7 +499,11 @@ class Vlan(AclBase, models.Model):
...
@@ -499,7 +499,11 @@ class Vlan(AclBase, models.Model):
def
get_new_address
(
self
):
def
get_new_address
(
self
):
hosts
=
self
.
host_set
hosts
=
self
.
host_set
used_v4
=
IPSet
(
hosts
.
values_list
(
'ipv4'
,
flat
=
True
))
used_ext_addrs
=
Host
.
objects
.
filter
(
external_ipv4__isnull
=
False
)
.
values_list
(
'external_ipv4'
,
flat
=
True
)
used_v4
=
IPSet
(
hosts
.
values_list
(
'ipv4'
,
flat
=
True
))
.
union
(
used_ext_addrs
)
.
union
([
self
.
network4
.
ip
])
used_v6
=
IPSet
(
hosts
.
exclude
(
ipv6__isnull
=
True
)
used_v6
=
IPSet
(
hosts
.
exclude
(
ipv6__isnull
=
True
)
.
values_list
(
'ipv6'
,
flat
=
True
))
.
values_list
(
'ipv6'
,
flat
=
True
))
...
...
circle/firewall/tests/test_firewall.py
View file @
01f0294e
...
@@ -77,13 +77,13 @@ class GetNewAddressTestCase(MockCeleryMixin, TestCase):
...
@@ -77,13 +77,13 @@ class GetNewAddressTestCase(MockCeleryMixin, TestCase):
d
=
Domain
(
name
=
'example.org'
,
owner
=
self
.
u1
)
d
=
Domain
(
name
=
'example.org'
,
owner
=
self
.
u1
)
d
.
save
()
d
.
save
()
# /29 = .1-.6 = 6 hosts/subnet + broadcast + network id
# /29 = .1-.6 = 6 hosts/subnet + broadcast + network id
self
.
vlan
=
Vlan
(
vid
=
1
,
name
=
'test'
,
network4
=
'10.0.0.
0
/29'
,
self
.
vlan
=
Vlan
(
vid
=
1
,
name
=
'test'
,
network4
=
'10.0.0.
1
/29'
,
network6
=
'2001:738:2001:4031::/80'
,
domain
=
d
,
network6
=
'2001:738:2001:4031::/80'
,
domain
=
d
,
owner
=
self
.
u1
)
owner
=
self
.
u1
)
self
.
vlan
.
clean
()
self
.
vlan
.
clean
()
self
.
vlan
.
save
()
self
.
vlan
.
save
()
self
.
vlan
.
host_set
.
all
()
.
delete
()
self
.
vlan
.
host_set
.
all
()
.
delete
()
for
i
in
[
1
]
+
range
(
3
,
6
):
for
i
in
range
(
3
,
6
):
Host
(
hostname
=
'h-
%
d'
%
i
,
mac
=
'01:02:03:04:05:
%02
d'
%
i
,
Host
(
hostname
=
'h-
%
d'
%
i
,
mac
=
'01:02:03:04:05:
%02
d'
%
i
,
ipv4
=
'10.0.0.
%
d'
%
i
,
vlan
=
self
.
vlan
,
ipv4
=
'10.0.0.
%
d'
%
i
,
vlan
=
self
.
vlan
,
owner
=
self
.
u1
)
.
save
()
owner
=
self
.
u1
)
.
save
()
...
@@ -102,6 +102,15 @@ class GetNewAddressTestCase(MockCeleryMixin, TestCase):
...
@@ -102,6 +102,15 @@ class GetNewAddressTestCase(MockCeleryMixin, TestCase):
owner
=
self
.
u1
)
.
save
()
owner
=
self
.
u1
)
.
save
()
self
.
assertRaises
(
ValidationError
,
self
.
vlan
.
get_new_address
)
self
.
assertRaises
(
ValidationError
,
self
.
vlan
.
get_new_address
)
def
test_all_addr_in_use2
(
self
):
Host
(
hostname
=
'h-xd'
,
mac
=
'01:02:03:04:05:06'
,
ipv4
=
'10.0.0.6'
,
vlan
=
self
.
vlan
,
owner
=
self
.
u1
)
.
save
()
Host
(
hostname
=
'h-arni'
,
mac
=
'01:02:03:04:05:02'
,
ipv4
=
'100.0.0.1'
,
vlan
=
self
.
vlan
,
external_ipv4
=
'10.0.0.2'
,
owner
=
self
.
u1
)
.
save
()
self
.
assertRaises
(
ValidationError
,
self
.
vlan
.
get_new_address
)
def
test_new_addr
(
self
):
def
test_new_addr
(
self
):
used_v4
=
IPSet
(
self
.
vlan
.
host_set
.
values_list
(
'ipv4'
,
flat
=
True
))
used_v4
=
IPSet
(
self
.
vlan
.
host_set
.
values_list
(
'ipv4'
,
flat
=
True
))
assert
self
.
vlan
.
get_new_address
()[
'ipv4'
]
not
in
used_v4
assert
self
.
vlan
.
get_new_address
()[
'ipv4'
]
not
in
used_v4
...
@@ -114,7 +123,7 @@ class HostGetHostnameTestCase(MockCeleryMixin, TestCase):
...
@@ -114,7 +123,7 @@ class HostGetHostnameTestCase(MockCeleryMixin, TestCase):
self
.
d
=
Domain
(
name
=
'example.org'
,
owner
=
self
.
u1
)
self
.
d
=
Domain
(
name
=
'example.org'
,
owner
=
self
.
u1
)
self
.
d
.
save
()
self
.
d
.
save
()
Record
.
objects
.
all
()
.
delete
()
Record
.
objects
.
all
()
.
delete
()
self
.
vlan
=
Vlan
(
vid
=
1
,
name
=
'test'
,
network4
=
'10.0.0.
0
/24'
,
self
.
vlan
=
Vlan
(
vid
=
1
,
name
=
'test'
,
network4
=
'10.0.0.
1
/24'
,
network6
=
'2001:738:2001:4031::/80'
,
domain
=
self
.
d
,
network6
=
'2001:738:2001:4031::/80'
,
domain
=
self
.
d
,
owner
=
self
.
u1
,
network_type
=
'portforward'
,
owner
=
self
.
u1
,
network_type
=
'portforward'
,
snat_ip
=
'10.1.1.1'
)
snat_ip
=
'10.1.1.1'
)
...
@@ -194,13 +203,13 @@ class ReloadTestCase(MockCeleryMixin, TestCase):
...
@@ -194,13 +203,13 @@ class ReloadTestCase(MockCeleryMixin, TestCase):
self
.
u1
=
User
.
objects
.
create
(
username
=
'user1'
)
self
.
u1
=
User
.
objects
.
create
(
username
=
'user1'
)
self
.
u1
.
save
()
self
.
u1
.
save
()
d
=
Domain
.
objects
.
create
(
name
=
'example.org'
,
owner
=
self
.
u1
)
d
=
Domain
.
objects
.
create
(
name
=
'example.org'
,
owner
=
self
.
u1
)
self
.
vlan
=
Vlan
(
vid
=
1
,
name
=
'test'
,
network4
=
'10.0.0.
0
/29'
,
self
.
vlan
=
Vlan
(
vid
=
1
,
name
=
'test'
,
network4
=
'10.0.0.
1
/29'
,
snat_ip
=
'152.66.243.99'
,
snat_ip
=
'152.66.243.99'
,
network6
=
'2001:738:2001:4031::/80'
,
domain
=
d
,
network6
=
'2001:738:2001:4031::/80'
,
domain
=
d
,
owner
=
self
.
u1
,
network_type
=
'portforward'
,
owner
=
self
.
u1
,
network_type
=
'portforward'
,
dhcp_pool
=
'manual'
)
dhcp_pool
=
'manual'
)
self
.
vlan
.
save
()
self
.
vlan
.
save
()
self
.
vlan2
=
Vlan
(
vid
=
2
,
name
=
'pub'
,
network4
=
'10.1.0.
0
/29'
,
self
.
vlan2
=
Vlan
(
vid
=
2
,
name
=
'pub'
,
network4
=
'10.1.0.
1
/29'
,
network6
=
'2001:738:2001:4032::/80'
,
domain
=
d
,
network6
=
'2001:738:2001:4032::/80'
,
domain
=
d
,
owner
=
self
.
u1
,
network_type
=
'public'
)
owner
=
self
.
u1
,
network_type
=
'public'
)
self
.
vlan2
.
save
()
self
.
vlan2
.
save
()
...
...
circle/request/forms.py
View file @
01f0294e
...
@@ -73,8 +73,11 @@ class InitialFromFileMixin(object):
...
@@ -73,8 +73,11 @@ class InitialFromFileMixin(object):
)
)
def
clean_message
(
self
):
def
clean_message
(
self
):
def
comp
(
x
):
return
""
.
join
(
x
.
strip
()
.
splitlines
())
message
=
self
.
cleaned_data
[
'message'
]
message
=
self
.
cleaned_data
[
'message'
]
if
message
.
strip
()
==
self
.
initial
[
'message'
]
.
strip
(
):
if
comp
(
message
)
==
comp
(
self
.
initial
[
'message'
]
):
raise
ValidationError
(
_
(
"Fill in the message."
),
code
=
"invalid"
)
raise
ValidationError
(
_
(
"Fill in the message."
),
code
=
"invalid"
)
return
message
.
strip
()
return
message
.
strip
()
...
...
circle/request/tables.py
View file @
01f0294e
...
@@ -38,6 +38,7 @@ class RequestTable(Table):
...
@@ -38,6 +38,7 @@ class RequestTable(Table):
template_name
=
"request/columns/user.html"
,
template_name
=
"request/columns/user.html"
,
verbose_name
=
_
(
"User"
),
verbose_name
=
_
(
"User"
),
)
)
created
=
Column
(
verbose_name
=
_
(
"Date"
))
type
=
TemplateColumn
(
type
=
TemplateColumn
(
template_name
=
"request/columns/type.html"
,
template_name
=
"request/columns/type.html"
,
verbose_name
=
_
(
"Type"
),
verbose_name
=
_
(
"Type"
),
...
@@ -48,7 +49,7 @@ class RequestTable(Table):
...
@@ -48,7 +49,7 @@ class RequestTable(Table):
template
=
"django_tables2/with_pagination.html"
template
=
"django_tables2/with_pagination.html"
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
),
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
),
'id'
:
"request-list-table"
}
'id'
:
"request-list-table"
}
fields
=
(
"pk"
,
"status"
,
"type"
,
"user"
,
)
fields
=
(
"pk"
,
"status"
,
"type"
,
"
created"
,
"
user"
,
)
order_by
=
(
"-pk"
,
)
order_by
=
(
"-pk"
,
)
empty_text
=
_
(
"No more requests."
)
empty_text
=
_
(
"No more requests."
)
per_page
=
10
per_page
=
10
...
...
circle/request/templates/request/detail.html
View file @
01f0294e
...
@@ -38,6 +38,9 @@
...
@@ -38,6 +38,9 @@
<pre>
{{ object.message }}
</pre>
<pre>
{{ object.message }}
</pre>
</p>
</p>
<hr
/>
<hr
/>
<div
class=
"pull-right"
>
<strong>
{% trans "Submitted" %}:
</strong>
{{ object.created }}
</div>
{% if object.type == "lease" %}
{% if object.type == "lease" %}
<dl>
<dl>
<dt>
{% trans "VM name" %}
</dt>
<dt>
{% trans "VM name" %}
</dt>
...
...
circle/request/views.py
View file @
01f0294e
...
@@ -208,6 +208,12 @@ class VmRequestMixin(LoginRequiredMixin, object):
...
@@ -208,6 +208,12 @@ class VmRequestMixin(LoginRequiredMixin, object):
user
=
self
.
request
.
user
user
=
self
.
request
.
user
if
not
vm
.
has_level
(
user
,
self
.
user_level
):
if
not
vm
.
has_level
(
user
,
self
.
user_level
):
raise
PermissionDenied
()
raise
PermissionDenied
()
if
vm
.
destroyed_at
:
message
=
_
(
"Instance
%(instance)
s has already been destroyed."
)
messages
.
error
(
self
.
request
,
message
%
{
'instance'
:
vm
.
name
})
return
redirect
(
vm
.
get_absolute_url
())
return
super
(
VmRequestMixin
,
self
)
.
dispatch
(
*
args
,
**
kwargs
)
return
super
(
VmRequestMixin
,
self
)
.
dispatch
(
*
args
,
**
kwargs
)
def
get_context_data
(
self
,
**
kwargs
):
def
get_context_data
(
self
,
**
kwargs
):
...
...
circle/vm/models/activity.py
View file @
01f0294e
...
@@ -135,14 +135,6 @@ class InstanceActivity(ActivityModel):
...
@@ -135,14 +135,6 @@ class InstanceActivity(ActivityModel):
def
get_absolute_url
(
self
):
def
get_absolute_url
(
self
):
return
reverse
(
'dashboard.views.vm-activity'
,
args
=
[
self
.
pk
])
return
reverse
(
'dashboard.views.vm-activity'
,
args
=
[
self
.
pk
])
def
get_status_id
(
self
):
if
self
.
succeeded
is
None
:
return
'wait'
elif
self
.
succeeded
:
return
'success'
else
:
return
'failed'
def
has_percentage
(
self
):
def
has_percentage
(
self
):
op
=
self
.
instance
.
get_operation_from_activity_code
(
self
.
activity_code
)
op
=
self
.
instance
.
get_operation_from_activity_code
(
self
.
activity_code
)
return
(
self
.
task_uuid
and
op
and
op
.
has_percentage
and
return
(
self
.
task_uuid
and
op
and
op
.
has_percentage
and
...
@@ -215,6 +207,13 @@ class NodeActivity(ActivityModel):
...
@@ -215,6 +207,13 @@ class NodeActivity(ActivityModel):
app_label
=
'vm'
app_label
=
'vm'
db_table
=
'vm_nodeactivity'
db_table
=
'vm_nodeactivity'
def
get_operation
(
self
):
return
self
.
node
.
get_operation_from_activity_code
(
self
.
activity_code
)
def
get_absolute_url
(
self
):
return
reverse
(
'dashboard.views.node-activity'
,
args
=
[
self
.
pk
])
def
__unicode__
(
self
):
def
__unicode__
(
self
):
if
self
.
parent
:
if
self
.
parent
:
return
'{}({})->{}'
.
format
(
self
.
parent
.
activity_code
,
return
'{}({})->{}'
.
format
(
self
.
parent
.
activity_code
,
...
...
circle/vm/models/instance.py
View file @
01f0294e
...
@@ -448,12 +448,17 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -448,12 +448,17 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
if
new_node
is
False
:
# None would be a valid value
if
new_node
is
False
:
# None would be a valid value
new_node
=
self
.
node
new_node
=
self
.
node
# log state change
# log state change
if
new_node
:
msg
=
ugettext_noop
(
"vm state changed to
%(state)
s on
%(node)
s"
)
else
:
msg
=
ugettext_noop
(
"vm state changed to
%(state)
s"
)
try
:
try
:
act
=
InstanceActivity
.
create
(
act
=
InstanceActivity
.
create
(
code_suffix
=
'vm_state_changed'
,
code_suffix
=
'vm_state_changed'
,
readable_name
=
create_readable
(
readable_name
=
create_readable
(
msg
,
state
=
new_state
,
ugettext_noop
(
"vm state changed to
%(state)
s on
%(node)
s"
),
node
=
new_node
),
state
=
new_state
,
node
=
new_node
),
instance
=
self
)
instance
=
self
)
except
ActivityInProgressError
:
except
ActivityInProgressError
:
pass
# discard state change if another activity is in progress.
pass
# discard state change if another activity is in progress.
...
@@ -676,7 +681,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
...
@@ -676,7 +681,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
with
self
.
activity
(
'notification_about_expiration'
,
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
,
concurrency_check
=
False
):
from
dashboard.views
import
VmRenewView
,
absolute_url
from
dashboard.views
import
VmRenewView
,
absolute_url
level
=
self
.
get_level_object
(
"owner"
)
level
=
self
.
get_level_object
(
"owner"
)
for
u
,
ulevel
in
self
.
get_users_with_level
(
level__pk
=
level
.
pk
):
for
u
,
ulevel
in
self
.
get_users_with_level
(
level__pk
=
level
.
pk
):
...
...
circle/vm/models/node.py
View file @
01f0294e
...
@@ -160,6 +160,8 @@ class Node(OperatedMixin, TimeStampedModel):
...
@@ -160,6 +160,8 @@ class Node(OperatedMixin, TimeStampedModel):
"""
"""
try
:
try
:
self
.
get_remote_queue_name
(
"vm"
,
"fast"
)
self
.
get_remote_queue_name
(
"vm"
,
"fast"
)
self
.
get_remote_queue_name
(
"vm"
,
"slow"
)
self
.
get_remote_queue_name
(
"net"
,
"fast"
)
except
:
except
:
return
False
return
False
else
:
else
:
...
...
circle/vm/operations.py
View file @
01f0294e
...
@@ -861,7 +861,9 @@ class ShutOffOperation(InstanceOperation):
...
@@ -861,7 +861,9 @@ class ShutOffOperation(InstanceOperation):
def
_operation
(
self
,
activity
):
def
_operation
(
self
,
activity
):
# Shutdown networks
# Shutdown networks
with
activity
.
sub_activity
(
'shutdown_net'
):
with
activity
.
sub_activity
(
'shutdown_net'
,
readable_name
=
ugettext_noop
(
"shutdown network"
)):
self
.
instance
.
shutdown_net
()
self
.
instance
.
shutdown_net
()
self
.
instance
.
_delete_vm
(
parent_activity
=
activity
)
self
.
instance
.
_delete_vm
(
parent_activity
=
activity
)
...
...
circle/vm/tasks/vm_tasks.py
View file @
01f0294e
...
@@ -57,7 +57,7 @@ def get_queues():
...
@@ -57,7 +57,7 @@ def get_queues():
inspect
=
celery
.
control
.
inspect
()
inspect
=
celery
.
control
.
inspect
()
inspect
.
timeout
=
0.5
inspect
.
timeout
=
0.5
result
=
inspect
.
active_queues
()
result
=
inspect
.
active_queues
()
logger
.
debug
(
'Queue list of length
%
d cached.'
,
len
(
result
))
logger
.
debug
(
'Queue list of length
%
d cached.'
,
result
and
len
(
result
))
cache
.
set
(
key
,
result
,
10
)
cache
.
set
(
key
,
result
,
10
)
return
result
return
result
...
...
miscellaneous/portal-uwsgi.service
0 → 100644
View file @
01f0294e
[Unit]
Description=CIRCLE portal
After=network.target
[Service]
User=cloud
Group=cloud
WorkingDirectory=/home/cloud/circle/circle
ExecStart=/bin/bash -c "source /etc/profile; workon circle; exec /home/cloud/.virtualenvs/circle/bin/uwsgi --chdir=/home/cloud/circle/circle -H /home/cloud/.virtualenvs/circle --socket /tmp/uwsgi.sock --wsgi-file circle/wsgi.py --chmod-socket=666"
Restart=always
[Install]
WantedBy=multi-user.target
requirements/base.txt
View file @
01f0294e
amqp==1.4.6
amqp==1.4.6
anyjson==0.3.3
anyjson==0.3.3
arrow==0.
6
.0
arrow==0.
7
.0
billiard==3.3.0.20
billiard==3.3.0.20
bpython==0.14.1
bpython==0.14.1
celery==3.1.18
celery==3.1.18
Django==1.8.2
Django==1.8.
1
2
django-appconf==1.0.1
django-appconf==1.0.1
django-autocomplete-light==2.1.1
django-autocomplete-light==2.1.1
django-braces==1.8.0
django-braces==1.8.0
django-crispy-forms==1.
4
.0
django-crispy-forms==1.
6
.0
django-model-utils==2.2
django-model-utils==2.2
djangosaml2==0.13.0
djangosaml2==0.13.0
django-sizefield==0.7
django-sizefield==0.7
...
...
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