Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Fukász Rómeó Ervin
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
a5be38e8
authored
Oct 13, 2014
by
Bach Dániel
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master' into issue-248
Conflicts: circle/vm/tasks/local_agent_tasks.py
parents
ab68f725
0bfdb2a3
Show whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
710 additions
and
216 deletions
+710
-216
circle/circle/settings/base.py
+10
-1
circle/dashboard/forms.py
+71
-2
circle/dashboard/static/dashboard/dashboard.css
+5
-1
circle/dashboard/static/dashboard/dashboard.js
+12
-1
circle/dashboard/static/dashboard/vm-common.js
+2
-10
circle/dashboard/static/dashboard/vm-details.js
+2
-0
circle/dashboard/templates/dashboard/_disk-list-element.html
+19
-18
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
+5
-3
circle/dashboard/templates/dashboard/_vm-migrate.html
+12
-8
circle/dashboard/templates/dashboard/index-nodes.html
+37
-31
circle/dashboard/templates/dashboard/instanceactivity_detail.html
+20
-0
circle/dashboard/templates/dashboard/node-list/column-vm.html
+1
-1
circle/dashboard/templates/dashboard/template-edit.html
+7
-1
circle/dashboard/tests/test_mockedviews.py
+32
-15
circle/dashboard/views/template.py
+52
-0
circle/dashboard/views/vm.py
+43
-70
circle/firewall/migrations/0052_auto__chg_field_record_address.py
+202
-0
circle/firewall/models.py
+1
-1
circle/firewall/tasks/local_tasks.py
+20
-13
circle/manager/mancelery.py
+10
-0
circle/manager/moncelery.py
+10
-1
circle/manager/slowcelery.py
+10
-1
circle/storage/models.py
+3
-0
circle/vm/models/activity.py
+3
-4
circle/vm/models/node.py
+4
-3
circle/vm/operations.py
+18
-16
circle/vm/tasks/agent_tasks.py
+11
-1
circle/vm/tasks/local_agent_tasks.py
+68
-9
circle/vm/tests/test_operations.py
+2
-1
miscellaneous/mancelery.conf
+6
-1
miscellaneous/moncelery.conf
+4
-1
miscellaneous/slowcelery.conf
+8
-2
No files found.
circle/circle/settings/base.py
View file @
a5be38e8
...
...
@@ -431,9 +431,18 @@ LOGIN_REDIRECT_URL = "/"
AGENT_DIR
=
get_env_variable
(
'DJANGO_AGENT_DIR'
,
join
(
unicode
(
expanduser
(
"~"
)),
'agent'
))
# AGENT_DIR is the root directory for the agent.
# The directory structure SHOULD be:
# /home/username/agent
# |-- agent-linux
# | |-- .git
# | +-- ...
# |-- agent-win
# | +-- agent-win-%(version).exe
#
try
:
git_env
=
{
'GIT_DIR'
:
join
(
AGENT_DIR
,
'.git'
)}
git_env
=
{
'GIT_DIR'
:
join
(
join
(
AGENT_DIR
,
"agent-linux"
)
,
'.git'
)}
AGENT_VERSION
=
check_output
(
(
'git'
,
'log'
,
'-1'
,
r'--pretty=format:
%
h'
,
'HEAD'
),
env
=
git_env
)
except
:
...
...
circle/dashboard/forms.py
View file @
a5be38e8
...
...
@@ -18,6 +18,7 @@
from
__future__
import
absolute_import
from
datetime
import
timedelta
from
urlparse
import
urlparse
from
django.contrib.auth.forms
import
(
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
...
...
@@ -39,6 +40,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
from
django.forms.widgets
import
TextInput
,
HiddenInput
from
django.template
import
Context
from
django.template.loader
import
render_to_string
from
django.utils.html
import
escape
from
django.utils.translation
import
ugettext_lazy
as
_
from
sizefield.widgets
import
FileSizeWidget
from
django.core.urlresolvers
import
reverse_lazy
...
...
@@ -79,6 +81,12 @@ class VmSaveForm(forms.Form):
helper
.
form_tag
=
False
return
helper
def
__init__
(
self
,
*
args
,
**
kwargs
):
default
=
kwargs
.
pop
(
'default'
,
None
)
super
(
VmSaveForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
default
:
self
.
fields
[
'name'
]
.
initial
=
default
class
VmCustomizeForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
widget
=
forms
.
TextInput
(
attrs
=
{
...
...
@@ -744,6 +752,20 @@ class VmRenewForm(forms.Form):
return
helper
class
VmMigrateForm
(
forms
.
Form
):
live_migration
=
forms
.
BooleanField
(
required
=
False
,
initial
=
True
,
label
=
_
(
"live migration"
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
default
=
kwargs
.
pop
(
'default'
)
super
(
VmMigrateForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
.
insert
(
0
,
'to_node'
,
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
default
,
required
=
False
,
widget
=
forms
.
RadioSelect
(),
label
=
_
(
"Node"
)))
class
VmStateChangeForm
(
forms
.
Form
):
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
...
...
@@ -788,6 +810,12 @@ class VmCreateDiskForm(forms.Form):
help_text
=
_
(
'Size of disk to create in bytes or with units '
'like MB or GB.'
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
default
=
kwargs
.
pop
(
'default'
,
None
)
super
(
VmCreateDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
default
:
self
.
fields
[
'name'
]
.
initial
=
default
def
clean_size
(
self
):
size_in_bytes
=
self
.
cleaned_data
.
get
(
"size"
)
if
not
size_in_bytes
.
isdigit
()
and
len
(
size_in_bytes
)
>
0
:
...
...
@@ -839,13 +867,42 @@ class VmDiskResizeForm(forms.Form):
helper
.
form_tag
=
False
if
self
.
disk
:
helper
.
layout
=
Layout
(
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
self
.
disk
),
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
escape
(
self
.
disk
)
),
Field
(
'disk'
),
Field
(
'size'
))
return
helper
class
VmDiskRemoveForm
(
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
self
.
disk
=
kwargs
.
pop
(
'default'
)
super
(
VmDiskRemoveForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
.
insert
(
0
,
'disk'
,
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
self
.
disk
,
required
=
True
,
empty_label
=
None
,
label
=
_
(
'Disk'
)))
if
self
.
disk
:
self
.
fields
[
'disk'
]
.
widget
=
HiddenInput
()
@property
def
helper
(
self
):
helper
=
FormHelper
(
self
)
helper
.
form_tag
=
False
if
self
.
disk
:
helper
.
layout
=
Layout
(
AnyTag
(
"div"
,
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
escape
(
self
.
disk
)),
css_class
=
"form-group"
,
),
Field
(
"disk"
),
)
return
helper
class
VmDownloadDiskForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
)
,
required
=
False
)
url
=
forms
.
CharField
(
label
=
_
(
'URL'
),
validators
=
[
URLValidator
(),
])
@property
...
...
@@ -854,6 +911,18 @@ class VmDownloadDiskForm(forms.Form):
helper
.
form_tag
=
False
return
helper
def
clean
(
self
):
cleaned_data
=
super
(
VmDownloadDiskForm
,
self
)
.
clean
()
if
not
cleaned_data
[
'name'
]:
if
cleaned_data
[
'url'
]:
cleaned_data
[
'name'
]
=
urlparse
(
cleaned_data
[
'url'
])
.
path
.
split
(
'/'
)[
-
1
]
if
not
cleaned_data
[
'name'
]:
raise
forms
.
ValidationError
(
_
(
"Could not find filename in URL, "
"please specify a name explicitly."
))
return
cleaned_data
class
VmAddInterfaceForm
(
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
...
...
circle/dashboard/static/dashboard/dashboard.css
View file @
a5be38e8
...
...
@@ -528,7 +528,7 @@ footer a, footer a:hover, footer a:visited {
}
#dashboard-template-list
a
small
{
max-width
:
50
%
;
max-width
:
45
%
;
float
:
left
;
padding-top
:
2px
;
text-overflow
:
ellipsis
;
...
...
@@ -1012,3 +1012,7 @@ textarea[name="new_members"] {
.disk-resize-btn
{
margin-right
:
5px
;
}
#vm-migrate-node-list
li
{
cursor
:
pointer
;
}
circle/dashboard/static/dashboard/dashboard.js
View file @
a5be38e8
...
...
@@ -411,6 +411,17 @@ $(function () {
$
(
this
).
removeClass
(
"btn-default"
).
addClass
(
"btn-primary"
);
return
false
;
});
// vm migrate select for node
$
(
document
).
on
(
"click"
,
"#vm-migrate-node-list li"
,
function
(
e
)
{
var
li
=
$
(
this
).
closest
(
'li'
);
if
(
li
.
find
(
'input'
).
attr
(
'disabled'
))
return
true
;
$
(
'#vm-migrate-node-list li'
).
removeClass
(
'panel-primary'
);
li
.
addClass
(
'panel-primary'
).
find
(
'input'
).
prop
(
"checked"
,
true
);
return
true
;
});
});
function
generateVmHTML
(
pk
,
name
,
host
,
icon
,
_status
,
fav
,
is_last
)
{
...
...
@@ -445,7 +456,7 @@ function generateNodeHTML(name, icon, _status, url, is_last) {
function
generateNodeTagHTML
(
name
,
icon
,
_status
,
label
,
url
)
{
return
'<a href="'
+
url
+
'" class="label '
+
label
+
'" >'
+
'<i class="'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'<i class="
fa
'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'</a> '
;
}
...
...
circle/dashboard/static/dashboard/vm-common.js
View file @
a5be38e8
...
...
@@ -16,15 +16,6 @@ $(function() {
$
(
'#confirmation-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#confirmation-modal'
).
remove
();
});
$
(
'#vm-migrate-node-list li'
).
click
(
function
(
e
)
{
var
li
=
$
(
this
).
closest
(
'li'
);
if
(
li
.
find
(
'input'
).
attr
(
'disabled'
))
return
true
;
$
(
'#vm-migrate-node-list li'
).
removeClass
(
'panel-primary'
);
li
.
addClass
(
'panel-primary'
).
find
(
'input'
).
attr
(
'checked'
,
true
);
return
false
;
});
$
(
'#vm-migrate-node-list li input:checked'
).
closest
(
'li'
).
addClass
(
'panel-primary'
);
}
});
...
...
@@ -51,7 +42,8 @@ $(function() {
if
(
data
.
success
)
{
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
if
(
data
.
with_reload
)
{
location
.
reload
();
// when the activity check stops the page will reload
reload_vm_detail
=
true
;
}
/* if there are messages display them */
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
a5be38e8
var
show_all
=
false
;
var
in_progress
=
false
;
var
activity_hash
=
5
;
var
reload_vm_detail
=
false
;
$
(
function
()
{
/* do we need to check for new activities */
...
...
@@ -404,6 +405,7 @@ function checkNewActivity(runs) {
);
}
else
{
in_progress
=
false
;
if
(
reload_vm_detail
)
location
.
reload
();
}
$
(
'a[href="#activity"] i'
).
removeClass
(
'fa-spin'
);
},
...
...
circle/dashboard/templates/dashboard/_disk-list-element.html
View file @
a5be38e8
{% load i18n %}
{% load sizefieldtags %}
<i
class=
"fa {% if d.is_downloading %}fa-refresh fa-spin{% else %}fa-file{% if d.failed %}"
style=
"color: #d9534f;{% endif %}{% endif %}"
></i>
{{ d.name }} (#{{ d.id }}) -
{% if not d.is_downloading %}
{% if not d.failed %}
{% if d.size %}{{ d.size|filesize }}{% endif %}
{% else %}
<div
class=
"label label-danger"
{%
if
user
.
is_superuser
%}
title=
"{{ d.get_latest_activity_result }}"
{%
endif
%}
>
{% trans "failed" %}
</div>
{% endif %}
{% else %}
<span
class=
"disk-list-disk-percentage"
data-disk-pk=
"{{ d.pk }}"
>
{{ d.get_download_percentage }}
</span>
%{% endif %}
{% if is_owner != False %}
<a
href=
"{% url "
dashboard
.
views
.
disk-remove
"
pk=
d.pk
%}?
next=
{{
request
.
path
}}"
data-disk-pk=
"{{ d.pk }}"
class=
"btn btn-xs btn-danger pull-right disk-remove"
{%
if
not
long_remove
%}
title=
"{% trans "
Remove
"
%}"{%
endif
%}
>
<i
class=
"fa fa-times"
></i>
{% if long_remove %} {% trans "Remove" %}{% endif %}
<i
class=
"fa fa-file"
></i>
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if op.remove_disk %}
<span
class=
"operation-wrapper"
>
<a
href=
"{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
<i
class=
"fa fa-{{ op.remove_disk.icon }}"
></i>
{% trans "Remove" %}
</a>
{% if op.resize_disk %}
</span>
{% endif %}
{% if op.resize_disk %}
<span
class=
"operation-wrapper"
>
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-warning pull-right operation disk-resize-btn"
>
<i
class=
"fa fa-arrows-alt"
></i>
{% trans "Resize" %}
class=
"btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
<i
class=
"fa fa-{{ op.resize_disk.icon }}"
></i>
{% trans "Resize" %}
</a>
</span>
{% endif %}
{% endif %}
<div
style=
"clear: both;"
></div>
{% if request.user.is_superuser %}
<small>
{% trans "File name" %}: {{ d.filename }}
</small>
{% endif %}
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
View file @
a5be38e8
{% extends "dashboard/mass-operate.html" %}
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block formfields %}
...
...
@@ -11,20 +12,20 @@
<label
for=
"migrate-to-none"
>
<strong>
{% trans "Reschedule" %}
</strong>
</label>
<input
id=
"migrate-to-none"
type=
"radio"
name=
"node"
value=
""
style=
"float: right;"
checked=
"checked"
>
<input
id=
"migrate-to-none"
type=
"radio"
name=
"
to_
node"
value=
""
style=
"float: right;"
checked=
"checked"
>
<span
class=
"vm-migrate-node-property"
>
{% trans "This option will reschedule each virtual machine to the optimal node." %}
</span>
<div
style=
"clear: both;"
></div>
</div>
</li>
{% for n in
nodes
%}
{% for n in
form.fields.to_node.queryset.all
%}
<li
class=
"panel panel-default mass-migrate-node"
>
<div
class=
"panel-body"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<strong>
{{ n }}
</strong>
</label>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"node"
value=
"{{ n.pk }}"
style=
"float: right;"
/>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"
to_
node"
value=
"{{ n.pk }}"
style=
"float: right;"
/>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<div
style=
"clear: both;"
></div>
...
...
@@ -32,5 +33,6 @@
</li>
{% endfor %}
</ul>
{{ form.live_migration|as_crispy_field }}
<hr
/>
{% endblock %}
circle/dashboard/templates/dashboard/_vm-migrate.html
View file @
a5be38e8
{% extends "dashboard/operate.html" %}
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block question %}
<p>
...
...
@@ -13,24 +14,27 @@ Choose a compute node to migrate {{obj}} to.
{% block formfields %}
<ul
id=
"vm-migrate-node-list"
class=
"list-unstyled"
>
{% with current=object.node.pk %}
{% for n in
nodes
%}
{% with current=object.node.pk
recommended=form.fields.to_node.initial.pk
%}
{% for n in
form.fields.to_node.queryset.all
%}
<li
class=
"panel panel-default"
><div
class=
"panel-body"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<strong>
{{ n }}
</strong>
<div
class=
"label label-primary"
>
<i
class=
"fa {{n.get_status_icon}}"
></i>
{{n.get_status_display}}
</div>
<div
class=
"label label-primary"
>
<i
class=
"fa {{n.get_status_icon}}"
></i>
{{n.get_status_display}}
</div>
{% if current == n.pk %}
<div
class=
"label label-info"
>
{% trans "current" %}
</div>
{% endif %}
{% if recommended == n.pk %}
<div
class=
"label label-success"
>
{% trans "recommended" %}
</div>
{% endif %}
</label>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"node"
value=
"{{ n.pk }}"
style=
"float: right;"
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"
to_
node"
value=
"{{ n.pk }}"
style=
"float: right;"
{%
if
current =
=
n
.
pk
%}
disabled=
"disabled"
{%
endif
%}
{%
if
recommended =
=
n
.
pk
%}
checked=
"checked"
{%
endif
%}
/>
{%
if
recommended =
=
n
.
pk
%}
checked=
"checked"
{%
endif
%}
/>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<div
style=
"clear: both;"
></div>
</
div></
li>
</li>
{% endfor %}
{% endwith %}
</ul>
{{ form.live_migration|as_crispy_field }}
{% endblock %}
circle/dashboard/templates/dashboard/index-nodes.html
View file @
a5be38e8
{% load i18n %}
<div
class=
"panel panel-default"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<div
class=
"pull-right toolbar"
>
<div
class=
"btn-group"
>
...
...
@@ -7,9 +7,10 @@
data-container=
"body"
><i
class=
"fa fa-dashboard"
></i></a>
<a
href=
"#index-list-view"
data-index-box=
"node"
class=
"btn btn-default btn-xs disabled"
data-container=
"body"
><i
class=
"fa fa-list"
></i></a>
</div>
<span
class=
"btn btn-default btn-xs infobtn"
title=
"{% trans "
List
of
compute
nodes
,
also
called
worker
nodes
or
hypervisors
,
which
run
the
virtual
machines
."
%}"
><i
class=
"fa fa-info-circle"
></i></span>
<span
class=
"btn btn-default btn-xs infobtn"
title=
"{% trans "
List
of
compute
nodes
,
also
called
worker
nodes
or
hypervisors
,
which
run
the
virtual
machines
."
%}"
>
<i
class=
"fa fa-info-circle"
></i>
</span>
</div>
<h3
class=
"no-margin"
>
<i
class=
"fa fa-sitemap"
></i>
{% trans "Nodes" %}
...
...
@@ -28,12 +29,40 @@
</a>
{% endfor %}
</div>
</div>
<!-- #node-list-view -->
<div
class=
"panel-body"
id=
"node-graph-view"
style=
"display: none; min-height: 204px;"
>
<p
class=
"pull-right"
>
<input
class=
"knob"
data-fgColor=
"chartreuse"
data-thickness=
".4"
data-width=
"60"
data-height=
"60"
data-readOnly=
"true"
value=
"{% widthratio node_num.running sum_node_num 100 %}"
>
</p>
<p>
<span
class=
"big"
>
<big>
{{ node_num.running }}
</big>
running
</span>
+
<big>
{{ node_num.missing }}
</big>
missing +
<br><big>
{{ node_num.disabled }}
</big>
disabled +
<big>
{{ node_num.offline }}
</big>
offline
</p>
<ul
class=
"list-inline"
id=
"dashboard-node-taglist"
>
{% for i in nodes %}
<a
href=
"{{ i.get_absolute_url }}"
class=
"label {{i.get_status_label}}"
>
<i
class=
"fa {{ i.get_status_icon }}"
title=
"{{ i.get_status_display }}"
></i>
{{ i.name }}
</a>
{% endfor %}
</ul>
<div
class=
"clearfix"
></div>
</div>
<div
href=
"#"
class=
"list-group-item list-group-footer"
>
<div
class=
"row"
>
<div
class=
"col-sm-6 col-xs-6 input-group input-group-sm"
>
<input
id=
"dashboard-node-search-input"
type=
"text"
class=
"form-control"
placeholder=
"{% trans "
Search
..."
%}"
/>
<input
id=
"dashboard-node-search-input"
type=
"text"
class=
"form-control"
placeholder=
"{% trans "
Search
..."
%}"
/>
<div
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"form-control btn btn-primary"
title=
"search"
><i
class=
"fa fa-search"
></i></button>
<button
type=
"submit"
class=
"btn btn-primary"
title=
"{% trans "
Search
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-search"
></i>
</button>
</div>
</div>
<div
class=
"col-sm-6 text-right"
>
...
...
@@ -45,33 +74,10 @@
{% trans "list" %}
{% endif %}
</a>
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
><i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a
>
</div>
</div
>
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
>
<i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a
>
</div>
</div>
<div
class=
"panel-body"
id=
"node-graph-view"
style=
"display: none"
>
<p
class=
"pull-right"
>
<input
class=
"knob"
data-fgColor=
"chartreuse"
data-thickness=
".4"
data-width=
"60"
data-height=
"60"
data-readOnly=
"true"
value=
"{% widthratio node_num.running sum_node_num 100 %}"
></p>
<p><span
class=
"big"
><big>
{{ node_num.running }}
</big>
running
</span>
+
<big>
{{ node_num.missing }}
</big>
missing +
<br><big>
{{ node_num.disabled }}
</big>
disabled +
<big>
{{ node_num.offline }}
</big>
offline
</p>
<ul
class=
"list-inline"
id=
"dashboard-node-taglist"
>
{% for i in nodes %}
<a
href=
"{{ i.get_absolute_url }}"
class=
"label {{i.get_status_label}}"
>
<i
class=
"fa {{ i.get_status_icon }}"
title=
"{{ i.get_status_display }}"
></i>
{{ i.name }}
</a>
{% endfor %}
</ul>
<div
class=
"clearfix"
></div>
<div
class=
"row"
>
<div
class=
"col-sm-6 text-right pull-right"
>
{% if more_nodes >= 0 %}
<a
class=
"btn btn-primary btn-xs"
href=
"{% url "
dashboard
.
views
.
node-list
"
%}"
>
<i
class=
"fa fa-chevron-circle-right"
></i>
{% blocktrans with count=more_nodes %}
<strong>
{{count}}
</strong>
more{% endblocktrans %}
</a>
{% endif %}
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
><i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a>
</div>
</div>
</div>
</div>
circle/dashboard/templates/dashboard/instanceactivity_detail.html
View file @
a5be38e8
...
...
@@ -58,6 +58,26 @@
<dt>
{% trans "resultant state" %}
</dt>
<dd>
{{object.resultant_state|default:'n/a'}}
</dd>
<dt>
{% trans "subactivities" %}
</dt>
{% for s in object.children.all %}
<dd>
<span
{%
if
s
.
result
%}
title=
"{{ s.result|get_text:user }}"
{%
endif
%}
>
<a
href=
"{{ s.get_absolute_url }}"
>
{{ s.readable_name|get_text:user|capfirst }}
</a></span>
–
{% if s.finished %}
{{ s.finished|time:"H:i:s" }}
{% else %}
<i
class=
"fa fa-refresh fa-spin"
class=
"sub-activity-loading-icon"
></i>
{% endif %}
{% if s.has_failed %}
<div
class=
"label label-danger"
>
{% trans "failed" %}
</div>
{% endif %}
</dd>
{% empty %}
<dd>
{% trans "none" %}
</dd>
{% endfor %}
</div>
</div>
</div>
</div>
...
...
circle/dashboard/templates/dashboard/node-list/column-vm.html
View file @
a5be38e8
{% load i18n %}
<div
id=
"node-list-column-vm"
>
<a
class=
"real-link"
href=
"{% url "
dashboard
.
views
.
vm-list
"
%}?
s=
node:{{
record
.
name
}}"
>
<a
class=
"real-link"
href=
"{% url "
dashboard
.
views
.
vm-list
"
%}?
s=
node
_exact
:{{
record
.
name
}}"
>
{{ value }}
</a>
</div>
circle/dashboard/templates/dashboard/template-edit.html
View file @
a5be38e8
...
...
@@ -86,7 +86,13 @@
{% endif %}
{% for d in disks %}
<li>
{% include "dashboard/_disk-list-element.html" %}
<i
class=
"fa fa-file"
></i>
{{ d.name }} (#{{ d.id }}) -
<a
href=
"{% url "
dashboard
.
views
.
disk-remove
"
pk=
d.pk
%}?
next=
{{
request
.
path
}}"
data-disk-pk=
"{{ d.pk }}"
class=
"btn btn-xs btn-danger pull-right disk-remove"
{%
if
not
long_remove
%}
title=
"{% trans "
Remove
"
%}"{%
endif
%}
>
<i
class=
"fa fa-times"
></i>
{% if long_remove %} {% trans "Remove" %}{% endif %}
</a>
</li>
{% endfor %}
</ul>
...
...
circle/dashboard/tests/test_mockedviews.py
View file @
a5be38e8
...
...
@@ -34,6 +34,13 @@ from ..views import AclUpdateView
from
..
import
views
class
QuerySet
(
list
):
model
=
MagicMock
()
def
get
(
self
,
*
args
,
**
kwargs
):
return
self
.
pop
()
class
ViewUserTestCase
(
unittest
.
TestCase
):
def
test_404
(
self
):
...
...
@@ -145,58 +152,66 @@ class VmOperationViewTestCase(unittest.TestCase):
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
def
test_migrate
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'to_node'
:
1
,
'live_migration'
:
True
},
superuser
=
True
)
view
=
vm_ops
[
'migrate'
]
node
=
MagicMock
(
pk
=
1
,
name
=
'node1'
)
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
,
\
patch
(
'dashboard.views.vm.get_object_or_404'
)
as
go4
:
patch
.
object
(
view
,
'get_form_kwargs'
)
as
form_kwargs
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
_meta
.
object_name
=
"Instance"
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
.
async
=
MagicMock
()
inst
.
has_level
.
return_value
=
True
form_kwargs
.
return_value
=
{
'default'
:
100
,
'choices'
:
QuerySet
([
node
])}
go
.
return_value
=
inst
go4
.
return_value
=
MagicMock
()
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
not
msg
.
error
.
called
assert
go4
.
called
inst
.
migrate
.
async
.
assert_called_once_with
(
to_node
=
node
,
live_migration
=
True
,
user
=
request
.
user
)
def
test_migrate_failed
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
True
)
view
=
vm_ops
[
'migrate'
]
node
=
MagicMock
(
pk
=
1
,
name
=
'node1'
)
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
,
\
patch
(
'dashboard.views.vm.get_object_or_404'
)
as
go4
:
patch
.
object
(
view
,
'get_form_kwargs'
)
as
form_kwargs
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
_meta
.
object_name
=
"Instance"
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
.
async
=
MagicMock
()
inst
.
migrate
.
async
.
side_effect
=
Exception
inst
.
has_level
.
return_value
=
True
form_kwargs
.
return_value
=
{
'default'
:
100
,
'choices'
:
QuerySet
([
node
])}
go
.
return_value
=
inst
go4
.
return_value
=
MagicMock
()
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
inst
.
migrate
.
async
.
called
assert
msg
.
error
.
called
assert
go4
.
called
def
test_migrate_wo_permission
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
False
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
False
)
view
=
vm_ops
[
'migrate'
]
node
=
MagicMock
(
pk
=
1
,
name
=
'node1'
)
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.vm.get_object_or_404'
)
as
go4
:
patch
.
object
(
view
,
'get_form_kwargs'
)
as
form_kwargs
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
_meta
.
object_name
=
"Instance"
inst
.
migrate
=
Instance
.
_ops
[
'migrate'
](
inst
)
inst
.
migrate
.
async
=
MagicMock
()
inst
.
has_level
.
return_value
=
True
form_kwargs
.
return_value
=
{
'default'
:
100
,
'choices'
:
QuerySet
([
node
])}
go
.
return_value
=
inst
go4
.
return_value
=
MagicMock
()
with
self
.
assertRaises
(
PermissionDenied
):
assert
view
.
as_view
()(
request
,
pk
=
1234
)[
'location'
]
assert
go4
.
called
assert
not
inst
.
migrate
.
async
.
called
def
test_migrate_template
(
self
):
"""check if GET dialog's template can be rendered"""
...
...
@@ -219,6 +234,7 @@ class VmOperationViewTestCase(unittest.TestCase):
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
name
=
"asd"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
save_as_template
=
Instance
.
_ops
[
'save_as_template'
](
inst
)
inst
.
save_as_template
.
async
=
MagicMock
()
...
...
@@ -235,6 +251,7 @@ class VmOperationViewTestCase(unittest.TestCase):
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
patch
(
'dashboard.views.util.messages'
)
as
msg
:
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
name
=
"asd"
inst
.
_meta
.
object_name
=
"Instance"
inst
.
save_as_template
=
Instance
.
_ops
[
'save_as_template'
](
inst
)
inst
.
save_as_template
.
async
=
MagicMock
()
...
...
@@ -301,7 +318,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
view
.
as_view
()(
request
,
pk
=
1234
)
.
render
()
def
test_migrate
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
True
)
view
=
vm_mass_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
...
...
@@ -318,7 +335,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
assert
not
msg2
.
error
.
called
def
test_migrate_failed
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
True
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
True
)
view
=
vm_mass_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
,
\
...
...
@@ -334,7 +351,7 @@ class VmMassOperationViewTestCase(unittest.TestCase):
assert
msg
.
error
.
called
def
test_migrate_wo_permission
(
self
):
request
=
FakeRequestFactory
(
POST
=
{
'node'
:
1
},
superuser
=
False
)
request
=
FakeRequestFactory
(
POST
=
{
'
to_
node'
:
1
},
superuser
=
False
)
view
=
vm_mass_ops
[
'migrate'
]
with
patch
.
object
(
view
,
'get_object'
)
as
go
:
...
...
circle/dashboard/views/template.py
View file @
a5be38e8
...
...
@@ -37,6 +37,7 @@ from braces.views import (
from
django_tables2
import
SingleTableView
from
vm.models
import
InstanceTemplate
,
InterfaceTemplate
,
Instance
,
Lease
from
storage.models
import
Disk
from
..forms
import
(
TemplateForm
,
TemplateListSearchForm
,
AclUserOrGroupAddForm
,
LeaseForm
,
...
...
@@ -319,6 +320,57 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return
kwargs
class
DiskRemoveView
(
DeleteView
):
model
=
Disk
def
get_queryset
(
self
):
qs
=
super
(
DiskRemoveView
,
self
)
.
get_queryset
()
return
qs
.
exclude
(
template_set
=
None
)
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
DiskRemoveView
,
self
)
.
get_context_data
(
**
kwargs
)
disk
=
self
.
get_object
()
template
=
disk
.
template_set
.
get
()
if
not
template
.
has_level
(
self
.
request
.
user
,
'owner'
):
raise
PermissionDenied
()
context
[
'title'
]
=
_
(
"Disk remove confirmation"
)
context
[
'text'
]
=
_
(
"Are you sure you want to remove "
"<strong>
%(disk)
s</strong> from "
"<strong>
%(app)
s</strong>?"
%
{
'disk'
:
disk
,
'app'
:
template
}
)
return
context
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
disk
=
self
.
get_object
()
template
=
disk
.
template_set
.
get
()
if
not
template
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
template
.
remove_disk
(
disk
=
disk
,
user
=
request
.
user
)
disk
.
destroy
()
next_url
=
request
.
POST
.
get
(
"next"
)
success_url
=
next_url
if
next_url
else
template
.
get_absolute_url
()
success_message
=
_
(
"Disk successfully removed."
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'message'
:
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
"
%
s#resources"
%
success_url
)
class
LeaseCreate
(
LoginRequiredMixin
,
PermissionRequiredMixin
,
SuccessMessageMixin
,
CreateView
):
model
=
Lease
...
...
circle/dashboard/views/vm.py
View file @
a5be38e8
...
...
@@ -59,7 +59,8 @@ from ..forms import (
AclUserOrGroupAddForm
,
VmResourcesForm
,
TraitsForm
,
RawDataForm
,
VmAddInterfaceForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
VmSaveForm
,
VmRenewForm
,
VmStateChangeForm
,
VmListSearchForm
,
VmCustomizeForm
,
TransferOwnershipForm
,
VmDiskResizeForm
,
RedeployForm
,
TransferOwnershipForm
,
VmDiskResizeForm
,
RedeployForm
,
VmDiskRemoveForm
,
VmMigrateForm
,
)
from
..models
import
Favourite
,
Profile
...
...
@@ -370,11 +371,9 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
return
val
class
VmDiskResizeView
(
FormOperationMixin
,
VmOperationView
):
op
=
'resize_disk'
form_class
=
VmDiskResizeForm
class
VmDiskModifyView
(
FormOperationMixin
,
VmOperationView
):
show_in_toolbar
=
False
with_reload
=
True
icon
=
'arrows-alt'
effect
=
"success"
...
...
@@ -389,7 +388,7 @@ class VmDiskResizeView(FormOperationMixin, VmOperationView):
else
:
default
=
None
val
=
super
(
VmDisk
Resize
View
,
self
)
.
get_form_kwargs
()
val
=
super
(
VmDisk
Modify
View
,
self
)
.
get_form_kwargs
()
val
.
update
({
'choices'
:
choices
,
'default'
:
default
})
return
val
...
...
@@ -402,6 +401,14 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView):
icon
=
'hdd-o'
effect
=
"success"
is_disk_operation
=
True
with_reload
=
True
def
get_form_kwargs
(
self
):
op
=
self
.
get_op
()
val
=
super
(
VmCreateDiskView
,
self
)
.
get_form_kwargs
()
num
=
op
.
instance
.
disks
.
count
()
+
1
val
[
'default'
]
=
"
%
s
%
d"
%
(
op
.
instance
.
name
,
num
)
return
val
class
VmDownloadDiskView
(
FormOperationMixin
,
VmOperationView
):
...
...
@@ -412,38 +419,31 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView):
icon
=
'download'
effect
=
"success"
is_disk_operation
=
True
with_reload
=
True
class
VmMigrateView
(
VmOperationView
):
class
VmMigrateView
(
FormOperationMixin
,
VmOperationView
):
op
=
'migrate'
icon
=
'truck'
effect
=
'info'
template_name
=
'dashboard/_vm-migrate.html'
form_class
=
VmMigrateForm
def
get_context_data
(
self
,
**
kwargs
):
ctx
=
super
(
VmMigrateView
,
self
)
.
get_context_data
(
**
kwargs
)
ctx
[
'nodes'
]
=
[
n
for
n
in
Node
.
objects
.
filter
(
enabled
=
True
)
if
n
.
online
]
def
get_form_kwargs
(
self
):
online
=
(
n
.
pk
for
n
in
Node
.
objects
.
filter
(
enabled
=
True
)
if
n
.
online
)
choices
=
Node
.
objects
.
filter
(
pk__in
=
online
)
default
=
None
inst
=
self
.
get_object
()
ctx
[
"recommended"
]
=
None
try
:
if
isinstance
(
inst
,
Instance
):
ctx
[
"recommended"
]
=
inst
.
select_node
()
.
pk
default
=
inst
.
select_node
()
except
SchedulerError
:
logger
.
exception
(
"scheduler error:"
)
return
ctx
def
post
(
self
,
request
,
extra
=
None
,
*
args
,
**
kwargs
):
if
extra
is
None
:
extra
=
{}
node
=
self
.
request
.
POST
.
get
(
"node"
)
if
node
:
node
=
get_object_or_404
(
Node
,
pk
=
node
)
extra
[
"to_node"
]
=
node
return
super
(
VmMigrateView
,
self
)
.
post
(
request
,
extra
,
*
args
,
**
kwargs
)
val
=
super
(
VmMigrateView
,
self
)
.
get_form_kwargs
()
val
.
update
({
'choices'
:
choices
,
'default'
:
default
})
return
val
class
VmSaveView
(
FormOperationMixin
,
VmOperationView
):
...
...
@@ -453,6 +453,12 @@ class VmSaveView(FormOperationMixin, VmOperationView):
effect
=
'info'
form_class
=
VmSaveForm
def
get_form_kwargs
(
self
):
op
=
self
.
get_op
()
val
=
super
(
VmSaveView
,
self
)
.
get_form_kwargs
()
val
[
'default'
]
=
op
.
_rename
(
op
.
instance
.
name
)
return
val
class
VmResourcesChangeView
(
VmOperationView
):
op
=
'resources_change'
...
...
@@ -649,7 +655,12 @@ vm_ops = OrderedDict([
op
=
'destroy'
,
icon
=
'times'
,
effect
=
'danger'
)),
(
'create_disk'
,
VmCreateDiskView
),
(
'download_disk'
,
VmDownloadDiskView
),
(
'resize_disk'
,
VmDiskResizeView
),
(
'resize_disk'
,
VmDiskModifyView
.
factory
(
op
=
'resize_disk'
,
form_class
=
VmDiskResizeForm
,
icon
=
'arrows-alt'
,
effect
=
"warning"
)),
(
'remove_disk'
,
VmDiskModifyView
.
factory
(
op
=
'remove_disk'
,
form_class
=
VmDiskRemoveForm
,
icon
=
'times'
,
effect
=
"danger"
)),
(
'add_interface'
,
VmAddInterfaceView
),
(
'renew'
,
VmRenewView
),
(
'resources_change'
,
VmResourcesChangeView
),
...
...
@@ -751,6 +762,12 @@ class MassOperationView(OperationView):
self
.
check_auth
()
if
extra
is
None
:
extra
=
{}
if
hasattr
(
self
,
'form_class'
):
form
=
self
.
form_class
(
self
.
request
.
POST
,
**
self
.
get_form_kwargs
())
if
form
.
is_valid
():
extra
.
update
(
form
.
cleaned_data
)
self
.
_call_operations
(
extra
)
if
request
.
is_ajax
():
store
=
messages
.
get_messages
(
request
)
...
...
@@ -789,6 +806,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
allowed_filters
=
{
'name'
:
"name__icontains"
,
'node'
:
"node__name__icontains"
,
'node_exact'
:
"node__name"
,
'status'
:
"status__iexact"
,
'tags[]'
:
"tags__name__in"
,
'tags'
:
"tags__name__in"
,
# for search string
...
...
@@ -1112,51 +1130,6 @@ class InstanceActivityDetail(CheckedDetailView):
return
ctx
class
DiskRemoveView
(
DeleteView
):
model
=
Disk
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
DiskRemoveView
,
self
)
.
get_context_data
(
**
kwargs
)
disk
=
self
.
get_object
()
app
=
disk
.
get_appliance
()
context
[
'title'
]
=
_
(
"Disk remove confirmation"
)
context
[
'text'
]
=
_
(
"Are you sure you want to remove "
"<strong>
%(disk)
s</strong> from "
"<strong>
%(app)
s</strong>?"
%
{
'disk'
:
disk
,
'app'
:
app
}
)
return
context
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
disk
=
self
.
get_object
()
app
=
disk
.
get_appliance
()
if
not
app
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
app
.
remove_disk
(
disk
=
disk
,
user
=
request
.
user
)
disk
.
destroy
()
next_url
=
request
.
POST
.
get
(
"next"
)
success_url
=
next_url
if
next_url
else
app
.
get_absolute_url
()
success_message
=
_
(
"Disk successfully removed."
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'message'
:
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
"
%
s#resources"
%
success_url
)
@require_GET
def
get_disk_download_status
(
request
,
pk
):
disk
=
Disk
.
objects
.
get
(
pk
=
pk
)
...
...
circle/firewall/migrations/0052_auto__chg_field_record_address.py
0 → 100644
View file @
a5be38e8
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Changing field 'Record.address'
db
.
alter_column
(
u'firewall_record'
,
'address'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
400
))
def
backwards
(
self
,
orm
):
# Changing field 'Record.address'
db
.
alter_column
(
u'firewall_record'
,
'address'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
200
))
models
=
{
u'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
u'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"(u'content_type__app_label', u'content_type__model', u'codename')"
,
'unique_together'
:
"((u'content_type', u'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['contenttypes.ContentType']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
u'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Permission']"
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
u'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
u'firewall.blacklistitem'
:
{
'Meta'
:
{
'object_name'
:
'BlacklistItem'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Host']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv4'
:
(
'django.db.models.fields.GenericIPAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'39'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'reason'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'snort_message'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'tempban'"
,
'max_length'
:
'10'
})
},
u'firewall.domain'
:
{
'Meta'
:
{
'object_name'
:
'Domain'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'ttl'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'600'
})
},
u'firewall.ethernetdevice'
:
{
'Meta'
:
{
'object_name'
:
'EthernetDevice'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'switch_port'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'ethernet_devices'"
,
'to'
:
u"orm['firewall.SwitchPort']"
})
},
u'firewall.firewall'
:
{
'Meta'
:
{
'object_name'
:
'Firewall'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
})
},
u'firewall.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'firewall.host'
:
{
'Meta'
:
{
'ordering'
:
"('normalized_hostname', 'vlan')"
,
'unique_together'
:
"(('hostname', 'vlan'),)"
,
'object_name'
:
'Host'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'external_ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Group']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'hostname'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'ipv6'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'location'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'mac'
:
(
'firewall.fields.MACAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'17'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'normalized_hostname'
:
(
'common.models.HumanSortField'
,
[],
{
'default'
:
"''"
,
'maximum_number_length'
:
'4'
,
'max_length'
:
'80'
,
'monitor'
:
"'hostname'"
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'reverse'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'shared_ip'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Vlan']"
})
},
u'firewall.record'
:
{
'Meta'
:
{
'ordering'
:
"('domain', 'name')"
,
'object_name'
:
'Record'
},
'address'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'400'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'domain'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Domain']"
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Host']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'ttl'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'600'
}),
'type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'6'
})
},
u'firewall.rule'
:
{
'Meta'
:
{
'ordering'
:
"('direction', 'proto', 'sport', 'dport', 'nat_external_port', 'host')"
,
'object_name'
:
'Rule'
},
'action'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'drop'"
,
'max_length'
:
'10'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'direction'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'3'
}),
'dport'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'extra'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'firewall'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Firewall']"
}),
'foreign_network'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'ForeignRules'"
,
'to'
:
u"orm['firewall.VlanGroup']"
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Host']"
}),
'hostgroup'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'nat'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'nat_external_ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'nat_external_port'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'proto'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'sport'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.Vlan']"
}),
'vlangroup'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'rules'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.VlanGroup']"
}),
'weight'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'30000'
})
},
u'firewall.switchport'
:
{
'Meta'
:
{
'object_name'
:
'SwitchPort'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'tagged_vlans'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"'tagged_ports'"
,
'null'
:
'True'
,
'to'
:
u"orm['firewall.VlanGroup']"
}),
'untagged_vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'untagged_ports'"
,
'to'
:
u"orm['firewall.Vlan']"
})
},
u'firewall.vlan'
:
{
'Meta'
:
{
'object_name'
:
'Vlan'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'dhcp_pool'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'domain'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Domain']"
}),
'host_ipv6_prefixlen'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'112'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv6_template'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'2001:738:2001:4031:
%(b)
d:
%(c)
d:
%(d)
d:0'"
}),
'managed'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'network4'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
}),
'network6'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'network_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'portforward'"
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'reverse_domain'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'
%(d)
d.
%(c)
d.
%(b)
d.
%(a)
d.in-addr.arpa'"
}),
'snat_ip'
:
(
'django.db.models.fields.GenericIPAddressField'
,
[],
{
'max_length'
:
'39'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'snat_to'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Vlan']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vid'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'unique'
:
'True'
})
},
u'firewall.vlangroup'
:
{
'Meta'
:
{
'object_name'
:
'VlanGroup'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vlans'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Vlan']"
,
'null'
:
'True'
,
'blank'
:
'True'
})
}
}
complete_apps
=
[
'firewall'
]
\ No newline at end of file
circle/firewall/models.py
View file @
a5be38e8
...
...
@@ -874,7 +874,7 @@ class Record(models.Model):
verbose_name
=
_
(
'host'
))
type
=
models
.
CharField
(
max_length
=
6
,
choices
=
CHOICES_type
,
verbose_name
=
_
(
'type'
))
address
=
models
.
CharField
(
max_length
=
2
00
,
address
=
models
.
CharField
(
max_length
=
4
00
,
verbose_name
=
_
(
'address'
))
ttl
=
models
.
IntegerField
(
default
=
600
,
verbose_name
=
_
(
'ttl'
))
owner
=
models
.
ForeignKey
(
User
,
verbose_name
=
_
(
'owner'
))
...
...
circle/firewall/tasks/local_tasks.py
View file @
a5be38e8
...
...
@@ -29,26 +29,24 @@ settings = django.conf.settings.FIREWALL_SETTINGS
logger
=
getLogger
(
__name__
)
def
_apply_once
(
name
,
queues
,
task
,
data
):
def
_apply_once
(
name
,
tasks
,
queues
,
task
,
data
):
"""Reload given networking component if needed.
"""
lockname
=
"
%
s_lock"
%
name
if
not
cache
.
get
(
lockname
):
if
name
not
in
tasks
:
return
cache
.
delete
(
lockname
)
data
=
data
()
for
queue
in
queues
:
try
:
task
.
apply_async
(
args
=
data
,
queue
=
queue
,
expires
=
60
)
.
get
(
timeout
=
5
)
task
.
apply_async
(
args
=
data
,
queue
=
queue
,
expires
=
60
)
.
get
(
timeout
=
2
)
logger
.
info
(
"
%
s configuration is reloaded. (queue:
%
s)"
,
name
,
queue
)
except
TimeoutError
as
e
:
logger
.
critical
(
'
%
s (queue:
%
s
)'
,
e
,
queu
e
)
logger
.
critical
(
'
%
s (queue:
%
s
, task:
%
s)'
,
e
,
queue
,
nam
e
)
except
:
logger
.
critical
(
'Unhandled exception: queue:
%
s data:
%
s'
,
queue
,
data
,
exc_info
=
True
)
logger
.
critical
(
'Unhandled exception: queue:
%
s data:
%
s
task:
%
s
'
,
queue
,
data
,
name
,
exc_info
=
True
)
def
get_firewall_queues
():
...
...
@@ -68,19 +66,28 @@ def reloadtask_worker():
from
remote_tasks
import
(
reload_dns
,
reload_dhcp
,
reload_firewall
,
reload_firewall_vlan
,
reload_blacklist
)
tasks
=
[]
for
i
in
(
'dns'
,
'dhcp'
,
'firewall'
,
'firewall_vlan'
,
'blacklist'
):
lockname
=
"
%
s_lock"
%
i
if
cache
.
get
(
lockname
):
tasks
.
append
(
i
)
cache
.
delete
(
lockname
)
logger
.
info
(
"reloadtask_worker: Reload
%
s"
,
", "
.
join
(
tasks
))
firewall_queues
=
get_firewall_queues
()
dns_queues
=
[(
"
%
s.dns"
%
i
)
for
i
in
settings
.
get
(
'dns_queues'
,
[
gethostname
()])]
_apply_once
(
'dns'
,
dns_queues
,
reload_dns
,
_apply_once
(
'dns'
,
tasks
,
dns_queues
,
reload_dns
,
lambda
:
(
dns
(),
))
_apply_once
(
'dhcp'
,
firewall_queues
,
reload_dhcp
,
_apply_once
(
'dhcp'
,
tasks
,
firewall_queues
,
reload_dhcp
,
lambda
:
(
dhcp
(),
))
_apply_once
(
'firewall'
,
firewall_queues
,
reload_firewall
,
_apply_once
(
'firewall'
,
tasks
,
firewall_queues
,
reload_firewall
,
lambda
:
(
BuildFirewall
()
.
build_ipt
()))
_apply_once
(
'firewall_vlan'
,
firewall_queues
,
reload_firewall_vlan
,
_apply_once
(
'firewall_vlan'
,
tasks
,
firewall_queues
,
reload_firewall_vlan
,
lambda
:
(
vlan
(),
))
_apply_once
(
'blacklist'
,
firewall_queues
,
reload_blacklist
,
_apply_once
(
'blacklist'
,
tasks
,
firewall_queues
,
reload_blacklist
,
lambda
:
(
list
(
ipset
()),
))
...
...
circle/manager/mancelery.py
View file @
a5be38e8
...
...
@@ -16,12 +16,15 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
celery
import
Celery
from
celery.signals
import
worker_ready
from
datetime
import
timedelta
from
kombu
import
Queue
,
Exchange
from
os
import
getenv
HOSTNAME
=
"localhost"
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
QUEUE_NAME
=
HOSTNAME
+
'.man'
celery
=
Celery
(
'manager'
,
broker
=
getenv
(
"AMQP_URI"
),
...
...
@@ -57,3 +60,10 @@ celery.conf.update(
}
)
@worker_ready.connect
()
def
cleanup_tasks
(
conf
=
None
,
**
kwargs
):
'''Discard all task and clean up activity.'''
from
vm.models.activity
import
cleanup
cleanup
(
queue_name
=
QUEUE_NAME
)
circle/manager/moncelery.py
View file @
a5be38e8
...
...
@@ -16,12 +16,14 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
celery
import
Celery
from
celery.signals
import
worker_ready
from
datetime
import
timedelta
from
kombu
import
Queue
,
Exchange
from
os
import
getenv
HOSTNAME
=
"localhost"
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
QUEUE_NAME
=
HOSTNAME
+
'.monitor'
celery
=
Celery
(
'monitor'
,
broker
=
getenv
(
"AMQP_URI"
),
...
...
@@ -34,7 +36,7 @@ celery.conf.update(
CELERY_CACHE_BACKEND
=
CACHE_URI
,
CELERY_TASK_RESULT_EXPIRES
=
300
,
CELERY_QUEUES
=
(
Queue
(
HOSTNAME
+
'.monitor'
,
Exchange
(
'monitor'
,
type
=
'direct'
),
Queue
(
QUEUE_NAME
,
Exchange
(
'monitor'
,
type
=
'direct'
),
routing_key
=
"monitor"
),
),
CELERYBEAT_SCHEDULE
=
{
...
...
@@ -70,3 +72,10 @@ celery.conf.update(
}
)
@worker_ready.connect
()
def
cleanup_tasks
(
conf
=
None
,
**
kwargs
):
'''Discard all task and clean up activity.'''
from
vm.models.activity
import
cleanup
cleanup
(
queue_name
=
QUEUE_NAME
)
circle/manager/slowcelery.py
View file @
a5be38e8
...
...
@@ -16,12 +16,14 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
celery
import
Celery
from
celery.signals
import
worker_ready
from
datetime
import
timedelta
from
kombu
import
Queue
,
Exchange
from
os
import
getenv
HOSTNAME
=
"localhost"
CACHE_URI
=
getenv
(
"CACHE_URI"
,
"pylibmc://127.0.0.1:11211/"
)
QUEUE_NAME
=
HOSTNAME
+
'.man.slow'
celery
=
Celery
(
'manager.slow'
,
broker
=
getenv
(
"AMQP_URI"
),
...
...
@@ -36,7 +38,7 @@ celery.conf.update(
CELERY_CACHE_BACKEND
=
CACHE_URI
,
CELERY_TASK_RESULT_EXPIRES
=
300
,
CELERY_QUEUES
=
(
Queue
(
HOSTNAME
+
'.man.slow'
,
Exchange
(
'manager.slow'
,
type
=
'direct'
),
Queue
(
QUEUE_NAME
,
Exchange
(
'manager.slow'
,
type
=
'direct'
),
routing_key
=
"manager.slow"
),
),
CELERYBEAT_SCHEDULE
=
{
...
...
@@ -48,3 +50,10 @@ celery.conf.update(
}
)
@worker_ready.connect
()
def
cleanup_tasks
(
conf
=
None
,
**
kwargs
):
'''Discard all task and clean up activity.'''
from
vm.models.activity
import
cleanup
cleanup
(
queue_name
=
QUEUE_NAME
)
circle/storage/models.py
View file @
a5be38e8
...
...
@@ -490,6 +490,9 @@ class Disk(TimeStampedModel):
disk
.
destroy
()
raise
humanize_exception
(
ugettext_noop
(
"Operation aborted by user."
),
e
)
except
:
disk
.
destroy
()
raise
disk
.
is_ready
=
True
disk
.
save
()
return
disk
circle/vm/models/activity.py
View file @
a5be38e8
...
...
@@ -20,7 +20,6 @@ from contextlib import contextmanager
from
logging
import
getLogger
from
warnings
import
warn
from
celery.signals
import
worker_ready
from
celery.contrib.abortable
import
AbortableAsyncResult
from
django.core.urlresolvers
import
reverse
...
...
@@ -263,15 +262,15 @@ def node_activity(code_suffix, node, task_uuid=None, user=None,
return
activitycontextimpl
(
act
)
@worker_ready.connect
()
def
cleanup
(
conf
=
None
,
**
kwargs
):
# TODO check if other manager workers are running
from
celery.task.control
import
discard_all
discard_all
()
msg_txt
=
ugettext_noop
(
"Manager is restarted, activity is cleaned up. "
"You can try again now."
)
message
=
create_readable
(
msg_txt
,
msg_txt
)
queue_name
=
kwargs
.
get
(
'queue_name'
,
None
)
for
i
in
InstanceActivity
.
objects
.
filter
(
finished__isnull
=
True
):
op
=
i
.
get_operation
()
if
op
and
op
.
async_queue
==
queue_name
:
i
.
finish
(
False
,
result
=
message
)
logger
.
error
(
'Forced finishing stale activity
%
s'
,
i
)
for
i
in
NodeActivity
.
objects
.
filter
(
finished__isnull
=
True
):
...
...
circle/vm/models/node.py
View file @
a5be38e8
...
...
@@ -313,10 +313,11 @@ class Node(OperatedMixin, TimeStampedModel):
def
get_status_label
(
self
):
return
{
'OFFLINE'
:
'label-warning'
,
'DISABLED'
:
'label-
warning
'
,
'DISABLED'
:
'label-
danger
'
,
'MISSING'
:
'label-danger'
,
'ONLINE'
:
'label-success'
}
.
get
(
self
.
get_state
(),
'label-danger'
)
'ACTIVE'
:
'label-success'
,
'PASSIVE'
:
'label-warning'
,
}
.
get
(
self
.
get_state
(),
'label-danger'
)
@node_available
def
update_vm_states
(
self
):
...
...
circle/vm/operations.py
View file @
a5be38e8
...
...
@@ -324,7 +324,7 @@ class DeployOperation(InstanceOperation):
"deployed to node:
%(node)
s"
),
node
=
self
.
instance
.
node
)
def
_operation
(
self
,
activity
,
timeout
=
15
):
def
_operation
(
self
,
activity
):
# Allocate VNC port and host node
self
.
instance
.
allocate_vnc_port
()
self
.
instance
.
allocate_node
()
...
...
@@ -405,7 +405,7 @@ class DestroyOperation(InstanceOperation):
required_perms
=
()
resultant_state
=
'DESTROYED'
def
_operation
(
self
,
activity
):
def
_operation
(
self
,
activity
,
system
):
# Destroy networks
with
activity
.
sub_activity
(
'destroying_net'
,
...
...
@@ -415,7 +415,7 @@ class DestroyOperation(InstanceOperation):
self
.
instance
.
destroy_net
()
if
self
.
instance
.
node
:
self
.
instance
.
_delete_vm
(
parent_activity
=
activity
)
self
.
instance
.
_delete_vm
(
parent_activity
=
activity
,
system
=
system
)
# Destroy disks
with
activity
.
sub_activity
(
...
...
@@ -425,7 +425,8 @@ class DestroyOperation(InstanceOperation):
# Delete mem. dump if exists
try
:
self
.
instance
.
_delete_mem_dump
(
parent_activity
=
activity
)
self
.
instance
.
_delete_mem_dump
(
parent_activity
=
activity
,
system
=
system
)
except
:
pass
...
...
@@ -470,12 +471,11 @@ class MigrateOperation(RemoteInstanceOperation):
async_queue
=
"localhost.man.slow"
task
=
vm_tasks
.
migrate
remote_queue
=
(
"vm"
,
"slow"
)
timeout
=
6
00
remote_timeout
=
10
00
def
_get_remote_args
(
self
,
to_node
,
**
kwargs
):
def
_get_remote_args
(
self
,
to_node
,
live_migration
,
**
kwargs
):
return
(
super
(
MigrateOperation
,
self
)
.
_get_remote_args
(
**
kwargs
)
+
[
to_node
.
host
.
hostname
,
True
])
# TODO handle non-live migration
+
[
to_node
.
host
.
hostname
,
live_migration
])
def
rollback
(
self
,
activity
):
with
activity
.
sub_activity
(
...
...
@@ -483,7 +483,7 @@ class MigrateOperation(RemoteInstanceOperation):
"redeploy network (rollback)"
)):
self
.
instance
.
deploy_net
()
def
_operation
(
self
,
activity
,
to_node
=
None
):
def
_operation
(
self
,
activity
,
to_node
=
None
,
live_migration
=
True
):
if
not
to_node
:
with
activity
.
sub_activity
(
'scheduling'
,
readable_name
=
ugettext_noop
(
...
...
@@ -495,7 +495,8 @@ class MigrateOperation(RemoteInstanceOperation):
with
activity
.
sub_activity
(
'migrate_vm'
,
readable_name
=
create_readable
(
ugettext_noop
(
"migrate to
%(node)
s"
),
node
=
to_node
)):
super
(
MigrateOperation
,
self
)
.
_operation
(
to_node
=
to_node
)
super
(
MigrateOperation
,
self
)
.
_operation
(
to_node
=
to_node
,
live_migration
=
live_migration
)
except
Exception
as
e
:
if
hasattr
(
e
,
'libvirtError'
):
self
.
rollback
(
activity
)
...
...
@@ -575,6 +576,7 @@ class RemoveDiskOperation(InstanceOperation):
'destroy_disk'
,
readable_name
=
ugettext_noop
(
'destroy disk'
)
):
disk
.
destroy
()
return
self
.
instance
.
disks
.
remove
(
disk
)
def
get_activity_name
(
self
,
kwargs
):
...
...
@@ -631,7 +633,7 @@ class SaveAsTemplateOperation(InstanceOperation):
for
disk
in
self
.
disks
:
disk
.
destroy
()
def
_operation
(
self
,
activity
,
user
,
system
,
timeout
=
300
,
name
=
None
,
def
_operation
(
self
,
activity
,
user
,
system
,
name
=
None
,
with_shutdown
=
True
,
task
=
None
,
**
kwargs
):
if
with_shutdown
:
try
:
...
...
@@ -709,7 +711,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin,
resultant_state
=
'STOPPED'
task
=
vm_tasks
.
shutdown
remote_queue
=
(
"vm"
,
"slow"
)
timeout
=
120
remote_
timeout
=
120
def
_operation
(
self
,
task
):
super
(
ShutdownOperation
,
self
)
.
_operation
(
task
=
task
)
...
...
@@ -778,12 +780,12 @@ class SleepOperation(InstanceOperation):
else
:
activity
.
resultant_state
=
'ERROR'
def
_operation
(
self
,
activity
):
def
_operation
(
self
,
activity
,
system
):
with
activity
.
sub_activity
(
'shutdown_net'
,
readable_name
=
ugettext_noop
(
"shutdown network"
)):
self
.
instance
.
shutdown_net
()
self
.
instance
.
_suspend_vm
(
parent_activity
=
activity
)
self
.
instance
.
_suspend_vm
(
parent_activity
=
activity
,
system
=
system
)
self
.
instance
.
yield_node
()
@register_operation
...
...
@@ -792,7 +794,7 @@ class SleepOperation(InstanceOperation):
name
=
_
(
"suspend virtual machine"
)
task
=
vm_tasks
.
sleep
remote_queue
=
(
"vm"
,
"slow"
)
timeout
=
6
00
remote_timeout
=
10
00
def
_get_remote_args
(
self
,
**
kwargs
):
return
(
super
(
SleepOperation
.
SuspendVmOperation
,
self
)
...
...
@@ -845,7 +847,7 @@ class WakeUpOperation(InstanceOperation):
name
=
_
(
"resume virtual machine"
)
task
=
vm_tasks
.
wake_up
remote_queue
=
(
"vm"
,
"slow"
)
timeout
=
6
00
remote_timeout
=
10
00
def
_get_remote_args
(
self
,
**
kwargs
):
return
(
super
(
WakeUpOperation
.
WakeUpVmOperation
,
self
)
...
...
circle/vm/tasks/agent_tasks.py
View file @
a5be38e8
...
...
@@ -53,8 +53,18 @@ def start_access_server(vm):
pass
@celery.task
(
name
=
'agent.update_legacy'
)
def
update_legacy
(
vm
,
data
,
executable
=
None
):
pass
@celery.task
(
name
=
'agent.append'
)
def
append
(
vm
,
data
,
filename
,
chunk_number
):
pass
@celery.task
(
name
=
'agent.update'
)
def
update
(
vm
,
data
):
def
update
(
vm
,
filename
,
executable
,
checksum
):
pass
...
...
circle/vm/tasks/local_agent_tasks.py
View file @
a5be38e8
...
...
@@ -19,11 +19,14 @@ from common.models import create_readable
from
manager.mancelery
import
celery
from
vm.tasks.agent_tasks
import
(
restart_networking
,
change_password
,
set_time
,
set_hostname
,
start_access_server
,
cleanup
,
update
,
change_ip
)
cleanup
,
update
,
append
,
change_ip
,
update_legacy
)
from
firewall.models
import
Host
import
time
import
os
from
base64
import
encodestring
from
hashlib
import
md5
from
StringIO
import
StringIO
from
tarfile
import
TarFile
,
TarInfo
from
django.conf
import
settings
...
...
@@ -61,17 +64,34 @@ def send_networking_commands(instance, act):
restart_networking
.
apply_async
(
queue
=
queue
,
args
=
(
instance
.
vm_name
,
))
def
create_
agent
_tar
():
def
create_
linux
_tar
():
def
exclude
(
tarinfo
):
if
tarinfo
.
name
.
startswith
(
'./.git'
):
ignored
=
(
'./.'
,
'./misc'
,
'./windows'
)
if
any
(
tarinfo
.
name
.
startswith
(
x
)
for
x
in
ignored
):
return
None
else
:
return
tarinfo
f
=
StringIO
()
with
TarFile
.
open
(
fileobj
=
f
,
mode
=
'w:gz'
)
as
tar
:
agent_path
=
os
.
path
.
join
(
settings
.
AGENT_DIR
,
"agent-linux"
)
tar
.
add
(
agent_path
,
arcname
=
'.'
,
filter
=
exclude
)
version_fileobj
=
StringIO
(
settings
.
AGENT_VERSION
)
version_info
=
TarInfo
(
name
=
'version.txt'
)
version_info
.
size
=
len
(
version_fileobj
.
buf
)
tar
.
addfile
(
version_info
,
version_fileobj
)
return
encodestring
(
f
.
getvalue
())
.
replace
(
'
\n
'
,
''
)
def
create_windows_tar
():
f
=
StringIO
()
agent_path
=
os
.
path
.
join
(
settings
.
AGENT_DIR
,
"agent-win"
)
with
TarFile
.
open
(
fileobj
=
f
,
mode
=
'w|gz'
)
as
tar
:
tar
.
add
(
settings
.
AGENT_DIR
,
arcname
=
'.'
,
filter
=
exclude
)
tar
.
add
(
agent_path
,
arcname
=
'.'
)
version_fileobj
=
StringIO
(
settings
.
AGENT_VERSION
)
version_info
=
TarInfo
(
name
=
'version.txt'
)
...
...
@@ -82,7 +102,7 @@ def create_agent_tar():
@celery.task
def
agent_started
(
vm
,
version
=
None
):
def
agent_started
(
vm
,
version
=
None
,
system
=
None
):
from
vm.models
import
Instance
,
InstanceActivity
instance
=
Instance
.
objects
.
get
(
id
=
int
(
vm
.
split
(
'-'
)[
-
1
]))
queue
=
instance
.
get_remote_queue_name
(
"agent"
)
...
...
@@ -104,7 +124,7 @@ def agent_started(vm, version=None):
if
version
and
version
!=
settings
.
AGENT_VERSION
:
try
:
update_agent
(
instance
,
act
)
update_agent
(
instance
,
act
,
system
,
settings
.
AGENT_VERSION
)
except
TimeoutError
:
pass
else
:
...
...
@@ -146,11 +166,16 @@ def measure_boot_time(instance):
@celery.task
def
agent_stopped
(
vm
):
from
vm.models
import
Instance
,
InstanceActivity
from
vm.models.activity
import
ActivityInProgressError
instance
=
Instance
.
objects
.
get
(
id
=
int
(
vm
.
split
(
'-'
)[
-
1
]))
qs
=
InstanceActivity
.
objects
.
filter
(
instance
=
instance
,
activity_code
=
'vm.Instance.agent'
)
act
=
qs
.
latest
(
'id'
)
with
act
.
sub_activity
(
'stopping'
,
readable_name
=
ugettext_noop
(
'stopping'
)):
try
:
with
act
.
sub_activity
(
'stopping'
,
readable_name
=
ugettext_noop
(
'stopping'
)):
pass
except
ActivityInProgressError
:
pass
...
...
@@ -161,7 +186,7 @@ def get_network_configs(instance):
return
(
interfaces
,
settings
.
FIREWALL_SETTINGS
[
'rdns_ip'
])
def
update_agent
(
instance
,
act
=
None
):
def
update_agent
(
instance
,
act
=
None
,
system
=
None
,
version
=
None
):
if
act
:
act
=
act
.
sub_activity
(
'update'
,
...
...
@@ -176,6 +201,40 @@ def update_agent(instance, act=None):
version
=
settings
.
AGENT_VERSION
))
with
act
:
queue
=
instance
.
get_remote_queue_name
(
"agent"
)
if
system
==
"Windows"
:
executable
=
os
.
listdir
(
os
.
path
.
join
(
settings
.
AGENT_DIR
,
"agent-win"
))[
0
]
# executable = "agent-winservice-%(version)s.exe" % {
# 'version': version}
data
=
create_windows_tar
()
elif
system
==
"Linux"
:
executable
=
""
data
=
create_linux_tar
()
else
:
executable
=
""
# Legacy update method
return
update_legacy
.
apply_async
(
queue
=
queue
,
args
=
(
instance
.
vm_name
,
create_linux_tar
())
)
.
get
(
timeout
=
60
)
checksum
=
md5
(
data
)
.
hexdigest
()
chunk_size
=
1024
*
1024
chunk_number
=
0
index
=
0
filename
=
version
+
".tar"
while
True
:
chunk
=
data
[
index
:
index
+
chunk_size
]
if
chunk
:
append
.
apply_async
(
queue
=
queue
,
args
=
(
instance
.
vm_name
,
chunk
,
filename
,
chunk_number
))
.
get
(
timeout
=
60
)
index
=
index
+
chunk_size
chunk_number
=
chunk_number
+
1
else
:
update
.
apply_async
(
queue
=
queue
,
args
=
(
instance
.
vm_name
,
create_agent_tar
()))
.
get
(
timeout
=
10
)
args
=
(
instance
.
vm_name
,
filename
,
executable
,
checksum
)
)
.
get
(
timeout
=
60
)
break
circle/vm/tests/test_operations.py
View file @
a5be38e8
...
...
@@ -59,7 +59,8 @@ class MigrateOperationTestCase(TestCase):
MigrateException
,
op
.
_operation
,
act
,
to_node
=
None
)
assert
inst
.
select_node
.
called
op
.
_get_remote_args
.
assert_called_once_with
(
to_node
=
'test'
)
op
.
_get_remote_args
.
assert_called_once_with
(
to_node
=
'test'
,
live_migration
=
True
)
class
RebootOperationTestCase
(
TestCase
):
...
...
miscellaneous/mancelery.conf
View file @
a5be38e8
...
...
@@ -6,9 +6,14 @@ respawn limit 30 30
setgid
cloud
setuid
cloud
kill
timeout
360
kill
signal
SIGTERM
script
cd
/
home
/
cloud
/
circle
/
circle
. /
home
/
cloud
/.
virtualenvs
/
circle
/
bin
/
activate
. /
home
/
cloud
/.
virtualenvs
/
circle
/
bin
/
postactivate
exec
./
manage
.
py
celery
--
app
=
manager
.
mancelery
worker
--
autoreload
--
loglevel
=
info
--
hostname
=
mancelery
-
B
-
c
10
./
manage
.
py
celery
-
f
--
app
=
manager
.
mancelery
purge
exec
./
manage
.
py
celery
--
app
=
manager
.
mancelery
worker
--
autoreload
--
loglevel
=
info
--
hostname
=
mancelery
-
B
-
c
3
end
script
miscellaneous/moncelery.conf
View file @
a5be38e8
...
...
@@ -3,6 +3,7 @@ description "CIRCLE moncelery for monitoring jobs"
respawn
respawn
limit
30
30
setgid
cloud
setuid
cloud
...
...
@@ -10,5 +11,7 @@ script
cd
/
home
/
cloud
/
circle
/
circle
. /
home
/
cloud
/.
virtualenvs
/
circle
/
bin
/
activate
. /
home
/
cloud
/.
virtualenvs
/
circle
/
bin
/
postactivate
exec
./
manage
.
py
celery
--
app
=
manager
.
moncelery
worker
--
autoreload
--
loglevel
=
info
--
hostname
=
moncelery
-
B
-
c
3
./
manage
.
py
celery
-
f
--
app
=
manager
.
moncelery
purge
exec
./
manage
.
py
celery
--
app
=
manager
.
moncelery
worker
--
autoreload
--
loglevel
=
info
--
hostname
=
moncelery
-
B
-
c
2
end
script
miscellaneous/slowcelery.conf
View file @
a5be38e8
description
"CIRCLE
mancelery for slow
jobs"
description
"CIRCLE
slowcelery for resource intensive or long
jobs"
respawn
respawn
limit
30
30
...
...
@@ -6,9 +6,15 @@ respawn limit 30 30
setgid
cloud
setuid
cloud
kill
timeout
360
kill
signal
INT
script
cd
/
home
/
cloud
/
circle
/
circle
. /
home
/
cloud
/.
virtualenvs
/
circle
/
bin
/
activate
. /
home
/
cloud
/.
virtualenvs
/
circle
/
bin
/
postactivate
exec
./
manage
.
py
celery
--
app
=
manager
.
slowcelery
worker
--
autoreload
--
loglevel
=
info
--
hostname
=
slowcelery
-
B
-
c
5
./
manage
.
py
celery
-
f
--
app
=
manager
.
slowcelery
purge
exec
./
manage
.
py
celery
--
app
=
manager
.
slowcelery
worker
--
autoreload
--
loglevel
=
info
--
hostname
=
slowcelery
-
B
-
c
1
end
script
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