Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gutyán Gábor
/
circlestack
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
4b1d3f50
authored
Mar 12, 2014
by
Guba Sándor
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'issue-24' into feature-save-as
Conflicts: circle/dashboard/views.py circle/storage/models.py
parents
bf9231ac
16d245c1
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
214 additions
and
60 deletions
+214
-60
circle/dashboard/forms.py
+63
-16
circle/dashboard/templates/dashboard/_disk-list-element.html
+14
-0
circle/dashboard/templates/dashboard/template-edit.html
+31
-3
circle/dashboard/templates/dashboard/vm-detail/resources.html
+7
-4
circle/dashboard/tests/test_views.py
+12
-4
circle/dashboard/urls.py
+4
-1
circle/dashboard/views.py
+55
-22
circle/storage/models.py
+16
-2
circle/storage/tasks/local_tasks.py
+8
-8
circle/vm/models/instance.py
+4
-0
No files found.
circle/dashboard/forms.py
View file @
4b1d3f50
from
datetime
import
timedelta
import
uuid
from
django.contrib.auth.models
import
User
from
django.contrib.auth.forms
import
(
...
...
@@ -20,7 +19,9 @@ from sizefield.widgets import FileSizeWidget
from
firewall.models
import
Vlan
,
Host
from
storage.models
import
Disk
,
DataStore
from
vm.models
import
InstanceTemplate
,
Lease
,
InterfaceTemplate
,
Node
from
vm.models
import
(
InstanceTemplate
,
Lease
,
InterfaceTemplate
,
Node
,
Instance
)
VLANS
=
Vlan
.
objects
.
all
()
DISKS
=
Disk
.
objects
.
exclude
(
type
=
"qcow2-snap"
)
...
...
@@ -728,27 +729,61 @@ class LeaseForm(forms.ModelForm):
class
DiskAddForm
(
forms
.
Form
):
name
=
forms
.
CharField
()
size
=
forms
.
CharField
(
widget
=
FileSizeWidget
)
size
=
forms
.
CharField
(
widget
=
FileSizeWidget
,
required
=
False
)
url
=
forms
.
CharField
(
required
=
False
)
is_template
=
forms
.
CharField
()
object_pk
=
forms
.
CharField
()
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
is_template
=
kwargs
.
pop
(
"is_template"
)
self
.
object_pk
=
kwargs
.
pop
(
"object_pk"
)
self
.
user
=
kwargs
.
pop
(
"user"
)
super
(
DiskAddForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
initial
[
'is_template'
]
=
1
if
self
.
is_template
is
True
else
0
self
.
initial
[
'object_pk'
]
=
self
.
object_pk
def
clean_size
(
self
):
size_in_bytes
=
self
.
cleaned_data
.
get
(
"size"
)
if
not
size_in_bytes
.
isdigit
():
if
not
size_in_bytes
.
isdigit
()
and
len
(
size_in_bytes
)
>
0
:
raise
forms
.
ValidationError
(
_
(
"Invalid format, you can use "
" GB or MB!"
))
return
size_in_bytes
def
save
(
self
,
vm
,
commit
=
True
):
def
clean
(
self
):
cleaned_data
=
self
.
cleaned_data
size
=
cleaned_data
.
get
(
"size"
)
url
=
cleaned_data
.
get
(
"url"
)
if
not
size
and
not
url
:
msg
=
_
(
"You have to either specify size or URL"
)
self
.
_errors
[
_
(
"Global"
)]
=
self
.
error_class
([
msg
])
return
cleaned_data
def
save
(
self
,
commit
=
True
):
data
=
self
.
cleaned_data
d
=
Disk
(
name
=
data
[
'name'
],
filename
=
str
(
uuid
.
uuid4
()),
datastore
=
DataStore
.
objects
.
all
()[
0
],
type
=
"qcow2-norm"
,
size
=
data
[
'size'
],
dev_num
=
"a"
,
)
d
.
save
()
vm
.
disks
.
add
(
d
)
if
self
.
is_template
:
inst
=
InstanceTemplate
.
objects
.
get
(
pk
=
self
.
object_pk
)
else
:
inst
=
Instance
.
objects
.
get
(
pk
=
self
.
object_pk
)
if
data
[
'size'
]:
kwargs
=
{
'name'
:
data
[
'name'
],
'type'
:
"qcow2-norm"
,
'datastore'
:
DataStore
.
objects
.
all
()[
0
],
'size'
:
data
[
'size'
],
}
d
=
Disk
.
create_empty
(
instance
=
inst
,
user
=
self
.
user
,
**
kwargs
)
else
:
kwargs
=
{
'name'
:
data
[
'name'
],
'url'
:
data
[
'url'
],
}
Disk
.
create_from_url_async
(
instance
=
inst
,
user
=
self
.
user
,
**
kwargs
)
d
=
None
return
d
@property
...
...
@@ -756,11 +791,23 @@ class DiskAddForm(forms.Form):
helper
=
FormHelper
()
helper
.
form_show_labels
=
False
helper
.
layout
=
Layout
(
Field
(
"is_template"
,
type
=
"hidden"
),
Field
(
"object_pk"
,
type
=
"hidden"
),
Field
(
"name"
,
placeholder
=
_
(
"Name"
)),
Field
(
"size"
,
placeholder
=
_
(
"Disk size (for example: 20GB, "
"1500MB)"
)),
Field
(
"url"
,
placeholder
=
_
(
"URL to an ISO image"
)),
AnyTag
(
"div"
,
HTML
(
_
(
"Either specify the size for an empty disk or a URL "
"to an ISO image!"
)
),
css_class
=
"alert alert-info"
,
style
=
"padding: 5px; text-align: justify;"
,
),
)
helper
.
add_input
(
Submit
(
"submit"
,
"Create new disk"
,
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"Add"
)
,
css_class
=
"btn btn-success"
))
return
helper
...
...
circle/dashboard/templates/dashboard/_disk-list-element.html
0 → 100644
View file @
4b1d3f50
{% load i18n %}
{% load sizefieldtags %}
<i
class=
"{% if d.is_downloading %}icon-refresh icon-spin{% else %}icon-file{% endif %}"
></i>
{{ d.name }} (#{{ d.id }}) -
{% if not d.is_downloading %}
{% if d.ready %}
{{ d.size|filesize }}
{% else %}
<div
class=
"label label-danger"
>
failed
</div>
{% endif %}
{% else %}
<span
class=
"disk-list-disk-percentage"
data-disk-pk=
"{{ d.pk }}"
>
{{ d.get_download_percentage }}
</span>
%{% endif %}
<div
class=
"btn btn-xs btn-danger pull-right"
><i
class=
"icon-remove"
></i>
Remove
</div>
circle/dashboard/templates/dashboard/template-edit.html
View file @
4b1d3f50
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block content %}
...
...
@@ -22,7 +23,7 @@
<div
class=
"col-md-4"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h
3
class=
"no-margin"
><i
class=
"icon-group"
></i>
{% trans "Manage access" %}
</h3
>
<h
4
class=
"no-margin"
><i
class=
"icon-group"
></i>
{% trans "Manage access" %}
</h4
>
</div>
<div
class=
"panel-body"
>
<form
action=
"{% url "
dashboard
.
views
.
template-acl
"
pk=
object.pk
%}"
method=
"post"
>
{% csrf_token %}
...
...
@@ -64,8 +65,35 @@
</form>
</div>
</div>
</div>
</div>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h4
class=
"no-margin"
><i
class=
"icon-file"
></i>
{% trans "Disk list" %}
</h4>
</div>
<div
class=
"panel-body"
>
<ul
style=
"list-style: none; padding-left: 0;"
>
{% for d in disks %}
<li>
{% include "dashboard/_disk-list-element.html" %}
</li>
{% endfor %}
</ul>
</div>
</div>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h4
class=
"no-margin"
><i
class=
"icon-folder-open"
></i>
{% trans "Create new disk" %}
</h4>
</div>
<div
class=
"panel-body"
>
<form
action=
"{% url "
dashboard
.
views
.
disk-add
"
%}"
method=
"POST"
>
{% crispy disk_add_form %}
</form>
</div>
</div>
</div>
<!-- .col-md-4 -->
</div>
<!-- .row -->
<style>
...
...
circle/dashboard/templates/dashboard/vm-detail/resources.html
View file @
4b1d3f50
...
...
@@ -52,12 +52,15 @@
</a>
</div>
</h3>
<div
class=
"row"
id=
"vm-details-disk-add-for-form"
>
</div>
<div
class=
"row"
id=
"vm-details-disk-add-for-form"
></div>
{% if not instance.disks.all %}
{% trans "No disks are added!" %}
{% endif %}
{% for d in instance.disks.all %}
<h4
class=
"list-group-item-heading dashboard-vm-details-network-h3"
>
<i
class=
"icon-file"
></i>
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }
}
{% include "dashboard/_disk-list-element.html" %
}
</h4>
{% endfor %}
</div>
...
...
@@ -67,7 +70,7 @@
<div
class=
"col-md-12"
>
<div>
<hr
/>
<form
method=
"POST"
action=
"
"
style=
"max-width: 30
0px;"
>
<form
method=
"POST"
action=
"
{% url "
dashboard
.
views
.
disk-add
"
%}"
style=
"max-width: 35
0px;"
>
{% crispy forms.disk_add_form %}
</form>
<hr
/>
...
...
circle/dashboard/tests/test_views.py
View file @
4b1d3f50
...
...
@@ -213,8 +213,12 @@ class VmDetailTest(LoginMixin, TestCase):
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
disks
=
inst
.
disks
.
count
()
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'disk-name'
:
"a"
,
'disk-size'
:
1
})
response
=
c
.
post
(
"/dashboard/disk/add/"
,
{
'disk-name'
:
"a"
,
'disk-size'
:
1
,
'disk-is_template'
:
0
,
'disk-object_pk'
:
1
,
})
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
disks
,
inst
.
disks
.
count
())
...
...
@@ -224,8 +228,12 @@ class VmDetailTest(LoginMixin, TestCase):
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
disks
=
inst
.
disks
.
count
()
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'disk-name'
:
"a"
,
'disk-size'
:
1
})
response
=
c
.
post
(
"/dashboard/disk/add/"
,
{
'disk-name'
:
"a"
,
'disk-size'
:
1
,
'disk-is_template'
:
0
,
'disk-object_pk'
:
1
,
})
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
disks
+
1
,
inst
.
disks
.
count
())
...
...
circle/dashboard/urls.py
View file @
4b1d3f50
...
...
@@ -9,7 +9,7 @@ from .views import (
FavouriteView
,
NodeStatus
,
GroupList
,
TemplateDelete
,
LeaseDelete
,
VmGraphView
,
TemplateAclUpdateView
,
GroupDetailView
,
GroupDelete
,
GroupAclUpdateView
,
GroupUserDelete
,
NotificationView
,
NodeGraphView
,
VmMigrateView
,
VmDetailVncTokenView
,
VmMigrateView
,
DiskAddView
,
VmDetailVncTokenView
,
)
urlpatterns
=
patterns
(
...
...
@@ -89,4 +89,7 @@ urlpatterns = patterns(
url
(
r'^notifications/$'
,
NotificationView
.
as_view
(),
name
=
"dashboard.views.notifications"
),
url
(
r'^disk/add/$'
,
DiskAddView
.
as_view
(),
name
=
"dashboard.views.disk-add"
),
)
circle/dashboard/views.py
View file @
4b1d3f50
...
...
@@ -22,7 +22,7 @@ from django.views.generic import (TemplateView, DetailView, View, DeleteView,
UpdateView
,
CreateView
)
from
django.contrib
import
messages
from
django.utils.translation
import
ugettext
as
_
from
django.template.defaultfilters
import
title
from
django.template.defaultfilters
import
title
as
title_filter
from
django.template.loader
import
render_to_string
from
django.forms.models
import
inlineformset_factory
...
...
@@ -196,7 +196,10 @@ class VmDetailView(CheckedDetailView):
)
.
all
()
context
[
'acl'
]
=
get_vm_acl_data
(
instance
)
context
[
'forms'
]
=
{
'disk_add_form'
:
DiskAddForm
(
prefix
=
"disk"
),
'disk_add_form'
:
DiskAddForm
(
user
=
self
.
request
.
user
,
is_template
=
False
,
object_pk
=
self
.
get_object
()
.
pk
,
prefix
=
"disk"
),
}
context
[
'os_type_icon'
]
=
instance
.
os_type
.
replace
(
"unknown"
,
"question"
)
...
...
@@ -215,7 +218,6 @@ class VmDetailView(CheckedDetailView):
'port'
:
self
.
__add_port
,
'new_network_vlan'
:
self
.
__new_network
,
'save_as'
:
self
.
__save_as
,
'disk-name'
:
self
.
__add_disk
,
'shut_down'
:
self
.
__shut_down
,
'sleep'
:
self
.
__sleep
,
'wake_up'
:
self
.
__wake_up
,
...
...
@@ -390,24 +392,6 @@ class VmDetailView(CheckedDetailView):
return
redirect
(
reverse_lazy
(
"dashboard.views.template-detail"
,
kwargs
=
{
'pk'
:
template
.
pk
}))
def
__add_disk
(
self
,
request
):
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
form
=
DiskAddForm
(
request
.
POST
,
prefix
=
"disk"
)
if
form
.
is_valid
():
messages
.
success
(
request
,
_
(
"New disk successfully created!"
))
form
.
save
(
self
.
object
)
else
:
error
=
"<br /> "
.
join
([
"<strong>
%
s</strong>:
%
s"
%
(
title
(
i
[
0
]),
i
[
1
][
0
])
for
i
in
form
.
errors
.
items
()])
messages
.
error
(
request
,
error
)
return
redirect
(
"
%
s#resources"
%
reverse_lazy
(
"dashboard.views.detail"
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
}))
def
__shut_down
(
self
,
request
):
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
...
...
@@ -763,8 +747,16 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return
super
(
TemplateDetail
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_context_data
(
self
,
**
kwargs
):
obj
=
self
.
get_object
()
context
=
super
(
TemplateDetail
,
self
)
.
get_context_data
(
**
kwargs
)
context
[
'acl'
]
=
get_vm_acl_data
(
self
.
get_object
())
context
[
'acl'
]
=
get_vm_acl_data
(
obj
)
context
[
'disks'
]
=
obj
.
disks
.
all
()
context
[
'disk_add_form'
]
=
DiskAddForm
(
user
=
self
.
request
.
user
,
is_template
=
True
,
object_pk
=
obj
.
pk
,
prefix
=
"disk"
,
)
return
context
def
get_success_url
(
self
):
...
...
@@ -1759,3 +1751,44 @@ def circle_login(request):
}
return
login
(
request
,
authentication_form
=
authentication_form
,
extra_context
=
extra_context
)
class
DiskAddView
(
TemplateView
):
def
post
(
self
,
*
args
,
**
kwargs
):
is_template
=
self
.
request
.
POST
.
get
(
"disk-is_template"
)
object_pk
=
self
.
request
.
POST
.
get
(
"disk-object_pk"
)
is_template
=
int
(
is_template
)
==
1
if
is_template
:
obj
=
InstanceTemplate
.
objects
.
get
(
pk
=
object_pk
)
else
:
obj
=
Instance
.
objects
.
get
(
pk
=
object_pk
)
if
not
obj
.
has_level
(
self
.
request
.
user
,
'owner'
):
raise
PermissionDenied
()
form
=
DiskAddForm
(
self
.
request
.
POST
,
user
=
self
.
request
.
user
,
is_template
=
is_template
,
object_pk
=
object_pk
,
prefix
=
"disk"
)
if
form
.
is_valid
():
if
form
.
cleaned_data
.
get
(
"size"
):
messages
.
success
(
self
.
request
,
_
(
"Disk successfully added!"
))
else
:
messages
.
success
(
self
.
request
,
_
(
"Disk download started!"
))
form
.
save
()
else
:
error
=
"<br /> "
.
join
([
"<strong>
%
s</strong>:
%
s"
%
(
title_filter
(
i
[
0
]),
i
[
1
][
0
])
for
i
in
form
.
errors
.
items
()])
messages
.
error
(
self
.
request
,
error
)
if
is_template
:
r
=
obj
.
get_absolute_url
()
else
:
r
=
obj
.
get_absolute_url
()
r
=
"
%
s#resources"
%
r
return
redirect
(
r
)
circle/storage/models.py
View file @
4b1d3f50
...
...
@@ -16,6 +16,7 @@ from datetime import timedelta
from
acl.models
import
AclBase
from
.tasks
import
local_tasks
,
remote_tasks
from
celery.exceptions
import
TimeoutError
from
manager.mancelery
import
celery
from
common.models
import
ActivityModel
,
activitycontextimpl
,
WorkerNotFound
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -134,6 +135,19 @@ class Disk(AclBase, TimeStampedModel):
'raw-rw'
:
'vd'
,
}[
self
.
type
]
def
is_downloading
(
self
):
da
=
DiskActivity
.
objects
.
filter
(
disk
=
self
)
.
latest
(
"created"
)
return
(
da
.
activity_code
==
"storage.Disk.download"
and
da
.
succeeded
is
None
)
def
get_download_percentage
(
self
):
if
not
self
.
is_downloading
():
return
None
task
=
DiskActivity
.
objects
.
latest
(
"created"
)
.
task_uuid
result
=
celery
.
AsyncResult
(
id
=
task
)
return
result
.
info
.
get
(
"percent"
)
def
is_deletable
(
self
):
"""Returns whether the file can be deleted.
...
...
@@ -305,8 +319,8 @@ class Disk(AclBase, TimeStampedModel):
"""
kwargs
.
update
({
'cls'
:
cls
,
'url'
:
url
,
'instance'
:
instance
,
'user'
:
user
})
return
local_tasks
.
create_from_url
.
apply_async
(
kwargs
=
kwargs
,
queue
=
'localhost.man'
)
return
local_tasks
.
create_from_url
.
apply_async
(
kwargs
=
kwargs
,
queue
=
'localhost.man'
)
@classmethod
def
create_from_url
(
cls
,
url
,
instance
=
None
,
user
=
None
,
...
...
circle/storage/tasks/local_tasks.py
View file @
4b1d3f50
...
...
@@ -42,18 +42,18 @@ def restore(disk, user):
disk
.
restore
(
task_uuid
=
restore
.
request
.
id
,
user
=
user
)
class
create_from_url
(
AbortableTask
):
class
CreateFromURLTask
(
AbortableTask
):
def
__init__
(
self
):
self
.
bind
(
celery
)
def
run
(
self
,
**
kwargs
):
Disk
=
kwargs
[
'cls'
]
url
=
kwargs
[
'url'
]
params
=
kwargs
[
'params'
]
user
=
kwargs
[
'user'
]
Disk
.
create_from_url
(
url
=
url
,
params
=
params
,
Disk
=
kwargs
.
pop
(
'cls'
)
Disk
.
create_from_url
(
url
=
kwargs
.
pop
(
'url'
),
task_uuid
=
create_from_url
.
request
.
id
,
abortable_task
=
self
,
user
=
user
)
**
kwargs
)
create_from_url
=
CreateFromURLTask
()
@celery.task
...
...
circle/vm/models/instance.py
View file @
4b1d3f50
...
...
@@ -152,6 +152,10 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
if
is_new
:
self
.
set_level
(
self
.
owner
,
'owner'
)
@permalink
def
get_absolute_url
(
self
):
return
(
'dashboard.views.template-detail'
,
None
,
{
'pk'
:
self
.
pk
})
class
Instance
(
AclBase
,
VirtualMachineDescModel
,
TimeStampedModel
):
...
...
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