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
Commit
01f0294e
authored
8 years ago
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 @@
"bootstrap"
:
"~3.2.0"
,
"fontawesome"
:
"~4.3.0"
,
"jquery"
:
"~2.1.1"
,
"no-vnc"
:
"
*
"
,
"no-vnc"
:
"
0.5.1
"
,
"jquery-knob"
:
"~1.2.9"
,
"jquery-simple-slider"
:
"https://github.com/BME-IK/jquery-simple-slider.git"
,
"bootbox"
:
"~4.3.0"
,
...
...
This diff is collapsed.
Click to expand it.
circle/common/models.py
View file @
01f0294e
...
...
@@ -232,6 +232,14 @@ class ActivityModel(TimeStampedModel):
else
:
return
code
def
get_status_id
(
self
):
if
self
.
succeeded
is
None
:
return
'wait'
elif
self
.
succeeded
:
return
'success'
else
:
return
'failed'
@celery.task
()
def
compute_cached
(
method
,
instance
,
memcached_seconds
,
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/forms.py
View file @
01f0294e
...
...
@@ -1223,7 +1223,7 @@ class MyProfileForm(forms.ModelForm):
class
Meta
:
fields
=
(
'preferred_language'
,
'email_notifications'
,
'use_gravatar'
,
)
'
desktop_notifications'
,
'
use_gravatar'
,
)
model
=
Profile
@property
...
...
This diff is collapsed.
Click to expand it.
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'
),
),
]
This diff is collapsed.
Click to expand it.
circle/dashboard/models.py
View file @
01f0294e
...
...
@@ -184,6 +184,10 @@ class Profile(Model):
email_notifications
=
BooleanField
(
verbose_name
=
_
(
"Email notifications"
),
default
=
True
,
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
(
max_length
=
20
,
verbose_name
=
_
(
'Samba password'
),
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/static/dashboard/activity.js
View file @
01f0294e
...
...
@@ -169,6 +169,9 @@ $(function() {
);
}
else
{
in_progress
=
false
;
if
(
document
.
hasFocus
()
===
false
&&
userWantNotifications
()){
sendNotification
(
generateMessageFromLastActivity
());
}
if
(
reload_vm_detail
)
location
.
reload
();
if
(
runs
>
1
)
addConnectText
();
}
...
...
@@ -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
()
{
var
activities
=
$
(
".timeline .activity"
);
if
(
activities
.
length
>
1
)
{
if
(
activities
.
eq
(
0
).
data
(
"activity-code"
)
==
"vm.Instance.wake_up"
||
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
()
{
var
hash
=
0
,
i
,
chr
,
len
;
if
(
this
.
length
===
0
)
return
hash
;
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/static/dashboard/dashboard.js
View file @
01f0294e
...
...
@@ -557,3 +557,11 @@ $(function () {
"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"
));
});
});
This diff is collapsed.
Click to expand it.
circle/dashboard/static/dashboard/dashboard.less
View file @
01f0294e
...
...
@@ -284,7 +284,7 @@ a.hover-black {
}
.hover-black:hover {
color: black
/*#d9534f*/;
color: black
; /*#d9534f*/
text-decoration: none;
}
...
...
@@ -1285,9 +1285,16 @@ textarea[name="new_members"] {
}
}
#vm-detail-successful
l
-boot {
#vm-detail-successful-boot {
margin-bottom: 20px;
display: none;
.label {
width: 100%;
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
}
#vm-detail-access-help {
...
...
@@ -1523,3 +1530,7 @@ textarea[name="new_members"] {
text-align: center;
width: 100%;
}
#manage-access-select-all {
cursor: pointer;
}
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/_display-name.html
View file @
01f0294e
...
...
@@ -14,5 +14,4 @@
({% trans "username" %}: {{ user.username }})
{% endif %}
{% endif %}
{% endif %}
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/_manage_access.html
View file @
01f0294e
...
...
@@ -6,7 +6,7 @@
<th></th>
<th>
{% trans "Who" %}
</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>
</thead>
<tbody>
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/_vm-renew.html
View file @
01f0294e
...
...
@@ -8,7 +8,7 @@
<a
class=
"btn btn-default"
href=
"{{object.get_absolute_url}}"
data-dismiss=
"modal"
>
{% trans "Cancel" %}
</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"
href=
"{% url "
request
.
views
.
request-lease
"
vm_pk=
object.pk
%}"
>
<i
class=
"fa fa-forward"
></i>
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/base.html
View file @
01f0294e
...
...
@@ -12,6 +12,9 @@
{% block navbar %}
{% 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"
>
{% if request.user.is_superuser %}
{% if ADMIN_ENABLED %}
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/node-detail.html
View file @
01f0294e
...
...
@@ -56,6 +56,11 @@
<span
class=
"label label-warning"
>
{% trans "Offline" %}
</span>
{% endif %}
</div>
<div>
{% for k, v in queues.iteritems %}
<span
class=
"label label-{% if v %}success{% else %}danger{% endif %}"
>
{{ k }}
</span>
{% endfor %}
</div>
</div>
<div
class=
"col-md-10"
id=
"node-detail-pane"
>
<div
class=
"panel panel-default"
id=
"node-detail-panel"
>
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/node-detail/_activity-timeline.html
View file @
01f0294e
...
...
@@ -5,10 +5,11 @@
{% for a in activities %}
<div
class=
"activity"
data-activity-id=
"{{ a.pk }}"
>
<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>
<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>
<span
title=
"{{ a.started }}"
>
{{ a.started|arrowfilter:LANGUAGE_CODE }}
</span>
{% if a.user %}, {{ a.user }}{% endif %}
...
...
@@ -19,7 +20,8 @@
<div
data-activity-id=
"{{ s.pk }}"
class=
"sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}"
>
<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>
–
{% if s.finished %}
...
...
This diff is collapsed.
Click to expand it.
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 %}
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/vm-detail.html
View file @
01f0294e
...
...
@@ -132,7 +132,7 @@
<dd>
<div
class=
"input-group"
>
<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"
title=
"{% trans "
Show
password
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-eye"
id=
"vm-details-pw-eye"
></i>
...
...
@@ -192,11 +192,11 @@
{% endif %}
</div>
<div
class=
"col-md-8"
id=
"vm-detail-pane"
>
<div
class=
"big"
id=
"vm-detail-successful
l
-boot"
>
<
span
class=
"label label-info"
data-status=
"{{ instance.status }}"
>
<div
class=
"big"
id=
"vm-detail-successful-boot"
>
<
div
class=
"label label-info"
data-status=
"{{ instance.status }}"
>
<i
class=
"fa fa-check"
></i>
{% trans "The virtual machine successfully started, you can connect now." %}
</
span
>
</
div
>
</div>
<div
class=
"panel panel-default"
id=
"vm-detail-panel"
>
<ul
class=
"nav nav-pills panel-heading"
>
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/vm-detail/home.html
View file @
01f0294e
...
...
@@ -59,8 +59,8 @@
{% if instance.is_expiring %}
<i
class=
"fa fa-warning-sign text-danger"
></i>
{% endif %}
<span
id=
"vm-details-renew-op"
>
{% with op=op.renew %}{% if op %}
<a
href=
"{{op.get_url}}"
class=
"btn btn-
{{op.effect}} btn-xs
operation operation-{{op.op}
}"
>
<a
href=
"{{op.get_url}}"
class=
"btn btn-
xs operation operation-{{ op.op }}
{% if op.disabled %}btn-default disabled{% else %}btn-{{op.effect}}{% endif %
}"
>
<i
class=
"fa fa-{{op.icon}}"
></i>
{{op.name}}
</a>
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/tests/test_views.py
View file @
01f0294e
...
...
@@ -270,33 +270,6 @@ class VmDetailTest(LoginMixin, MockCeleryMixin, TestCase):
self
.
assertEqual
(
InstanceTemplate
.
objects
.
get
(
id
=
1
)
.
raw_data
,
"<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
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
...
...
@@ -615,6 +588,12 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
node
=
Node
.
objects
.
get
(
pk
=
1
)
trait
,
created
=
Trait
.
objects
.
get_or_create
(
name
=
'testtrait'
)
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
):
super
(
NodeDetailTest
,
self
)
.
tearDown
()
...
...
@@ -622,6 +601,7 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
self
.
u2
.
delete
()
self
.
us
.
delete
()
self
.
g1
.
delete
()
self
.
patcher
.
stop
()
def
test_404_superuser_node_page
(
self
):
c
=
Client
()
...
...
@@ -629,6 +609,12 @@ class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
response
=
c
.
get
(
'/dashboard/node/25555/'
)
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
):
c
=
Client
()
self
.
login
(
c
,
'user1'
)
...
...
@@ -1758,3 +1744,76 @@ class SshKeyTest(LoginMixin, TestCase):
resp
=
c
.
post
(
"/dashboard/sshkey/delete/1/"
)
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
())
This diff is collapsed.
Click to expand it.
circle/dashboard/urls.py
View file @
01f0294e
...
...
@@ -25,7 +25,7 @@ from .views import (
GroupDetailView
,
GroupList
,
IndexView
,
InstanceActivityDetail
,
LeaseCreate
,
LeaseDelete
,
LeaseDetail
,
MyPreferencesView
,
NodeAddTraitView
,
NodeCreate
,
NodeDelete
,
NodeDetailView
,
NodeList
,
NodeDetailView
,
NodeList
,
NodeActivityDetail
,
NotificationView
,
TemplateAclUpdateView
,
TemplateCreate
,
TemplateDelete
,
TemplateDetail
,
TemplateList
,
vm_activity
,
VmCreate
,
VmDetailView
,
...
...
@@ -136,6 +136,8 @@ urlpatterns = patterns(
name
=
'dashboard.views.node-activity-list'
),
url
(
r'^node/create/$'
,
NodeCreate
.
as_view
(),
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
(),
name
=
'dashboard.views.favourite'
),
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/views/node.py
View file @
01f0294e
...
...
@@ -37,6 +37,7 @@ from django_tables2 import SingleTableView
from
firewall.models
import
Host
from
vm.models
import
Node
,
NodeActivity
,
Trait
from
vm.tasks.vm_tasks
import
check_queue
from
..forms
import
TraitForm
,
HostForm
,
NodeForm
from
..tables
import
NodeListTable
...
...
@@ -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
,
GraphMixin
,
DetailView
):
template_name
=
"dashboard/node-detail.html"
...
...
@@ -103,10 +118,17 @@ class NodeDetailView(LoginRequiredMixin,
context
[
'ops'
]
=
get_operations
(
self
.
object
,
self
.
request
.
user
)
context
[
'op'
]
=
{
i
.
op
:
i
for
i
in
context
[
'ops'
]}
context
[
'show_show_all'
]
=
len
(
na
)
>
10
context
[
'activities'
]
=
na
[:
10
]
context
[
'activities'
]
=
_format_activities
(
na
[:
10
])
context
[
'trait_form'
]
=
form
context
[
'graphite_enabled'
]
=
(
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
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
...
...
@@ -298,8 +320,8 @@ class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
show_all
=
request
.
GET
.
get
(
"show_all"
,
"false"
)
==
"true"
node
=
Node
.
objects
.
get
(
pk
=
pk
)
activities
=
NodeActivity
.
objects
.
filter
(
node
=
node
,
parent
=
None
)
.
order_by
(
'-started'
)
.
select_related
()
activities
=
_format_activities
(
NodeActivity
.
objects
.
filter
(
node
=
node
,
parent
=
None
)
.
order_by
(
'-started'
)
.
select_related
()
)
show_show_all
=
len
(
activities
)
>
10
if
not
show_all
:
...
...
@@ -316,3 +338,18 @@ class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
json
.
dumps
(
response
),
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
This diff is collapsed.
Click to expand it.
circle/dashboard/views/user.py
View file @
01f0294e
...
...
@@ -200,6 +200,8 @@ class MyPreferencesView(UpdateView):
data
=
request
.
POST
)
if
form
.
is_valid
():
form
.
save
()
messages
.
success
(
self
.
request
,
_
(
"Password successfully changed."
))
if
form
.
is_valid
():
return
redirect_response
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/views/vm.py
View file @
01f0294e
...
...
@@ -105,6 +105,19 @@ class VmDetailView(GraphMixin, CheckedDetailView):
template_name
=
"dashboard/vm-detail.html"
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
):
context
=
super
(
VmDetailView
,
self
)
.
get_context_data
(
**
kwargs
)
instance
=
context
[
'instance'
]
...
...
This diff is collapsed.
Click to expand it.
circle/firewall/models.py
View file @
01f0294e
...
...
@@ -499,7 +499,11 @@ class Vlan(AclBase, models.Model):
def
get_new_address
(
self
):
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
)
.
values_list
(
'ipv6'
,
flat
=
True
))
...
...
This diff is collapsed.
Click to expand it.
circle/firewall/tests/test_firewall.py
View file @
01f0294e
...
...
@@ -77,13 +77,13 @@ class GetNewAddressTestCase(MockCeleryMixin, TestCase):
d
=
Domain
(
name
=
'example.org'
,
owner
=
self
.
u1
)
d
.
save
()
# /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
,
owner
=
self
.
u1
)
self
.
vlan
.
clean
()
self
.
vlan
.
save
()
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
,
ipv4
=
'10.0.0.
%
d'
%
i
,
vlan
=
self
.
vlan
,
owner
=
self
.
u1
)
.
save
()
...
...
@@ -102,6 +102,15 @@ class GetNewAddressTestCase(MockCeleryMixin, TestCase):
owner
=
self
.
u1
)
.
save
()
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
):
used_v4
=
IPSet
(
self
.
vlan
.
host_set
.
values_list
(
'ipv4'
,
flat
=
True
))
assert
self
.
vlan
.
get_new_address
()[
'ipv4'
]
not
in
used_v4
...
...
@@ -114,7 +123,7 @@ class HostGetHostnameTestCase(MockCeleryMixin, TestCase):
self
.
d
=
Domain
(
name
=
'example.org'
,
owner
=
self
.
u1
)
self
.
d
.
save
()
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
,
owner
=
self
.
u1
,
network_type
=
'portforward'
,
snat_ip
=
'10.1.1.1'
)
...
...
@@ -194,13 +203,13 @@ class ReloadTestCase(MockCeleryMixin, TestCase):
self
.
u1
=
User
.
objects
.
create
(
username
=
'user1'
)
self
.
u1
.
save
()
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'
,
network6
=
'2001:738:2001:4031::/80'
,
domain
=
d
,
owner
=
self
.
u1
,
network_type
=
'portforward'
,
dhcp_pool
=
'manual'
)
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
,
owner
=
self
.
u1
,
network_type
=
'public'
)
self
.
vlan2
.
save
()
...
...
This diff is collapsed.
Click to expand it.
circle/request/forms.py
View file @
01f0294e
...
...
@@ -73,8 +73,11 @@ class InitialFromFileMixin(object):
)
def
clean_message
(
self
):
def
comp
(
x
):
return
""
.
join
(
x
.
strip
()
.
splitlines
())
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"
)
return
message
.
strip
()
...
...
This diff is collapsed.
Click to expand it.
circle/request/tables.py
View file @
01f0294e
...
...
@@ -38,6 +38,7 @@ class RequestTable(Table):
template_name
=
"request/columns/user.html"
,
verbose_name
=
_
(
"User"
),
)
created
=
Column
(
verbose_name
=
_
(
"Date"
))
type
=
TemplateColumn
(
template_name
=
"request/columns/type.html"
,
verbose_name
=
_
(
"Type"
),
...
...
@@ -48,7 +49,7 @@ class RequestTable(Table):
template
=
"django_tables2/with_pagination.html"
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
),
'id'
:
"request-list-table"
}
fields
=
(
"pk"
,
"status"
,
"type"
,
"user"
,
)
fields
=
(
"pk"
,
"status"
,
"type"
,
"
created"
,
"
user"
,
)
order_by
=
(
"-pk"
,
)
empty_text
=
_
(
"No more requests."
)
per_page
=
10
...
...
This diff is collapsed.
Click to expand it.
circle/request/templates/request/detail.html
View file @
01f0294e
...
...
@@ -38,6 +38,9 @@
<pre>
{{ object.message }}
</pre>
</p>
<hr
/>
<div
class=
"pull-right"
>
<strong>
{% trans "Submitted" %}:
</strong>
{{ object.created }}
</div>
{% if object.type == "lease" %}
<dl>
<dt>
{% trans "VM name" %}
</dt>
...
...
This diff is collapsed.
Click to expand it.
circle/request/views.py
View file @
01f0294e
...
...
@@ -208,6 +208,12 @@ class VmRequestMixin(LoginRequiredMixin, object):
user
=
self
.
request
.
user
if
not
vm
.
has_level
(
user
,
self
.
user_level
):
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
)
def
get_context_data
(
self
,
**
kwargs
):
...
...
This diff is collapsed.
Click to expand it.
circle/vm/models/activity.py
View file @
01f0294e
...
...
@@ -135,14 +135,6 @@ class InstanceActivity(ActivityModel):
def
get_absolute_url
(
self
):
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
):
op
=
self
.
instance
.
get_operation_from_activity_code
(
self
.
activity_code
)
return
(
self
.
task_uuid
and
op
and
op
.
has_percentage
and
...
...
@@ -215,6 +207,13 @@ class NodeActivity(ActivityModel):
app_label
=
'vm'
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
):
if
self
.
parent
:
return
'{}({})->{}'
.
format
(
self
.
parent
.
activity_code
,
...
...
This diff is collapsed.
Click to expand it.
circle/vm/models/instance.py
View file @
01f0294e
...
...
@@ -448,12 +448,17 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
if
new_node
is
False
:
# None would be a valid value
new_node
=
self
.
node
# 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
:
act
=
InstanceActivity
.
create
(
code_suffix
=
'vm_state_changed'
,
readable_name
=
create_readable
(
ugettext_noop
(
"vm state changed to
%(state)
s on
%(node)
s"
),
state
=
new_state
,
node
=
new_node
),
readable_name
=
create_readable
(
msg
,
state
=
new_state
,
node
=
new_node
),
instance
=
self
)
except
ActivityInProgressError
:
pass
# discard state change if another activity is in progress.
...
...
@@ -676,7 +681,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
with
self
.
activity
(
'notification_about_expiration'
,
readable_name
=
ugettext_noop
(
"notify owner about expiration"
),
on_commit
=
on_commit
):
on_commit
=
on_commit
,
concurrency_check
=
False
):
from
dashboard.views
import
VmRenewView
,
absolute_url
level
=
self
.
get_level_object
(
"owner"
)
for
u
,
ulevel
in
self
.
get_users_with_level
(
level__pk
=
level
.
pk
):
...
...
This diff is collapsed.
Click to expand it.
circle/vm/models/node.py
View file @
01f0294e
...
...
@@ -160,6 +160,8 @@ class Node(OperatedMixin, TimeStampedModel):
"""
try
:
self
.
get_remote_queue_name
(
"vm"
,
"fast"
)
self
.
get_remote_queue_name
(
"vm"
,
"slow"
)
self
.
get_remote_queue_name
(
"net"
,
"fast"
)
except
:
return
False
else
:
...
...
This diff is collapsed.
Click to expand it.
circle/vm/operations.py
View file @
01f0294e
...
...
@@ -861,7 +861,9 @@ class ShutOffOperation(InstanceOperation):
def
_operation
(
self
,
activity
):
# 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
.
_delete_vm
(
parent_activity
=
activity
)
...
...
This diff is collapsed.
Click to expand it.
circle/vm/tasks/vm_tasks.py
View file @
01f0294e
...
...
@@ -57,7 +57,7 @@ def get_queues():
inspect
=
celery
.
control
.
inspect
()
inspect
.
timeout
=
0.5
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
)
return
result
...
...
This diff is collapsed.
Click to expand it.
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
This diff is collapsed.
Click to expand it.
requirements/base.txt
View file @
01f0294e
amqp==1.4.6
anyjson==0.3.3
arrow==0.
6
.0
arrow==0.
7
.0
billiard==3.3.0.20
bpython==0.14.1
celery==3.1.18
Django==1.8.2
Django==1.8.
1
2
django-appconf==1.0.1
django-autocomplete-light==2.1.1
django-braces==1.8.0
django-crispy-forms==1.
4
.0
django-crispy-forms==1.
6
.0
django-model-utils==2.2
djangosaml2==0.13.0
django-sizefield==0.7
...
...
This diff is collapsed.
Click to expand it.
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