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
5c743bdc
authored
8 years ago
by
Czémán Arnold
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dashboard: add delete and restore feature for Storage, small rework on Endpoint deletition
parent
f7b7edeb
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
251 additions
and
13 deletions
+251
-13
circle/dashboard/forms.py
+22
-2
circle/dashboard/static/dashboard/dashboard.js
+3
-1
circle/dashboard/templates/dashboard/confirm/ajax-restore.html
+24
-0
circle/dashboard/templates/dashboard/confirm/base-restore.html
+37
-0
circle/dashboard/templates/dashboard/storage-list.html
+1
-0
circle/dashboard/templates/dashboard/storage/detail.html
+22
-1
circle/dashboard/urls.py
+5
-0
circle/dashboard/views/storage.py
+94
-7
circle/dashboard/views/vm.py
+4
-2
circle/storage/migrations/0007_datastore_destroyed.py
+19
-0
circle/storage/models.py
+20
-0
No files found.
circle/dashboard/forms.py
View file @
5c743bdc
...
...
@@ -460,6 +460,9 @@ class NodeForm(forms.ModelForm):
class
TemplateForm
(
forms
.
ModelForm
):
networks
=
forms
.
ModelMultipleChoiceField
(
queryset
=
None
,
required
=
False
,
label
=
_
(
"Networks"
))
datastore
=
forms
.
ModelChoiceField
(
queryset
=
DataStore
.
objects
.
filter
(
destroyed__isnull
=
True
),
empty_label
=
None
)
num_cores
=
forms
.
IntegerField
(
widget
=
forms
.
NumberInput
(
attrs
=
{
'class'
:
"form-control input-tags cpu-count-input"
,
...
...
@@ -1451,8 +1454,9 @@ class RawDataForm(forms.ModelForm):
class
VmDataStoreForm
(
forms
.
ModelForm
):
datastore
=
forms
.
ModelChoiceField
(
queryset
=
DataStore
.
objects
.
all
(),
empty_label
=
None
)
datastore
=
forms
.
ModelChoiceField
(
queryset
=
DataStore
.
objects
.
filter
(
destroyed__isnull
=
True
),
empty_label
=
None
)
class
Meta
:
model
=
Instance
...
...
@@ -1671,13 +1675,29 @@ class CephDataStoreForm(DataStoreForm):
class
StorageListSearchForm
(
forms
.
Form
):
CHOICES
=
(
(
"active"
,
_
(
"active"
)),
(
"destroyed"
,
_
(
"destroyed"
)),
((
"all"
),
_
(
"all"
)),
)
s
=
forms
.
CharField
(
widget
=
forms
.
TextInput
(
attrs
=
{
'class'
:
"form-control input-tags"
,
'placeholder'
:
_
(
"Search..."
)
}))
stype
=
forms
.
ChoiceField
(
CHOICES
,
widget
=
forms
.
Select
(
attrs
=
{
'class'
:
"btn btn-default input-tags"
,
}))
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
StorageListSearchForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
# set initial value, otherwise it would be overwritten by request.GET
if
not
self
.
data
.
get
(
"stype"
):
data
=
self
.
data
.
copy
()
data
[
'stype'
]
=
"active"
self
.
data
=
data
class
EndpointForm
(
ModelForm
):
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/static/dashboard/dashboard.js
View file @
5c743bdc
...
...
@@ -29,7 +29,9 @@ $(function () {
return
false
;
});
$
(
'.group-create, .node-create, .tx-tpl-ownership, .group-delete, .node-delete, .disk-remove, .template-delete, .delete-from-group, .lease-delete, .endpoint-delete'
).
click
(
function
(
e
)
{
$
(
'.group-create, .node-create, .tx-tpl-ownership, .group-delete, .node-delete, '
+
'.disk-remove, .template-delete, .delete-from-group, .lease-delete, .endpoint-delete, '
+
'.storage-delete, .storage-restore'
).
click
(
function
(
e
)
{
$
.
ajax
({
type
:
'GET'
,
url
:
$
(
this
).
prop
(
'href'
),
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/confirm/ajax-restore.html
0 → 100644
View file @
5c743bdc
{% load i18n %}
<div
class=
"modal fade"
id=
"confirmation-modal"
tabindex=
"-1"
role=
"dialog"
>
<div
class=
"modal-dialog"
>
<div
class=
"modal-content"
>
<div
class=
"modal-body"
>
{% blocktrans with object=object %}
Are you sure you want to restore
<strong>
{{ object }}
</strong>
?
{% endblocktrans %}
<br
/>
<div
class=
"pull-right"
style=
"margin-top: 15px;"
>
<form
action=
"{{ request.path }}"
method=
"POST"
>
{% csrf_token %}
<button
type=
"button"
class=
"btn btn-default"
data-dismiss=
"modal"
>
{% trans "Cancel" %}
</button>
<input
type=
"hidden"
name=
"next"
value=
"{{ request.GET.next }}"
/>
<button
class=
"btn btn-warning modal-accept"
{%
if
disable_submit
%}
disabled
{%
endif
%}
>
{% trans "Restore" %}
</button>
</form>
</div>
<div
class=
"clearfix"
></div>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/confirm/base-restore.html
0 → 100644
View file @
5c743bdc
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div
class=
"body-content"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h3
class=
"no-margin"
>
{% if title %}
{{ title }}
{% else %}
{% trans "Restore confirmation" %}
{% endif %}
</h3>
</div>
<div
class=
"panel-body"
>
{% if text %}
{{ text|safe }}
{% else %}
{% blocktrans with object=object %}
Are you sure you want to restore
<strong>
{{ object }}
</strong>
?
{% endblocktrans %}
{% endif %}
<div
class=
"pull-right"
>
<form
action=
"{{ request.path }}"
method=
"POST"
>
{% csrf_token %}
<a
class=
"btn btn-default"
>
{% trans "Cancel" %}
</a>
<input
type=
"hidden"
name=
"next"
value=
"{{ request.GET.next }}"
/>
<button
class=
"btn btn-warning"
{%
if
disable_submit
%}
disabled
{%
endif
%}
>
{% trans "Yes" %}
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/storage-list.html
View file @
5c743bdc
...
...
@@ -28,6 +28,7 @@
<div
class=
"input-group"
>
{{ search_form.s }}
<div
class=
"input-group-btn"
>
{{ search_form.stype }}
<button
type=
"submit"
class=
"btn btn-primary input-tags"
>
<i
class=
"fa fa-search"
></i>
</button>
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/templates/dashboard/storage/detail.html
View file @
5c743bdc
...
...
@@ -11,7 +11,7 @@
<div
class=
"col-md-6"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h3
class=
"no-margin"
><i
class=
"fa fa-database"
></i>
{% trans "Datastore" %}
</h3>
<h3
class=
"no-margin"
><i
class=
"fa fa-database"
></i>
{% trans "Data
store" %}
</h3>
</div>
<div
class=
"panel-body"
>
<form
id=
"storage-create-form"
action=
""
method=
"POST"
>
...
...
@@ -23,6 +23,27 @@
</div>
<!-- .panel-body -->
</div>
</div>
<div
class=
"col-md-6"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
{% if object.destroyed %}
<a
href=
"{% url "
dashboard
.
views
.
storage-restore
"
pk=
object.pk
%}"
class=
"btn btn-xs btn-warning pull-right storage-restore"
>
{% trans "Restore" %}
</a>
<h4
class=
"no-margin"
><i
class=
"fa fa-medkit"
></i>
{% trans "Restore data store" %}
</h4>
{% else %}
<a
href=
"{% url "
dashboard
.
views
.
storage-delete
"
pk=
object.pk
%}"
class=
"btn btn-xs btn-danger pull-right storage-delete"
>
{% trans "Delete" %}
</a>
<h4
class=
"no-margin"
><i
class=
"fa fa-times"
></i>
{% trans "Delete data store" %}
</h4>
{% endif %}
</div>
</div>
</div>
<div
class=
"col-md-6"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/urls.py
View file @
5c743bdc
...
...
@@ -54,6 +54,7 @@ from .views import (
NodeActivityView
,
UserList
,
StorageDetail
,
StorageList
,
StorageChoose
,
StorageCreate
,
DiskDetail
,
StorageDelete
,
StorageRestore
,
EndpointCreate
,
EndpointList
,
EndpointEdit
,
EndpointDelete
,
MessageList
,
MessageDetail
,
MessageCreate
,
MessageDelete
,
)
...
...
@@ -244,6 +245,10 @@ urlpatterns = patterns(
name
=
"dashboard.views.storage-list"
),
url
(
r'^storage/choose/$'
,
StorageChoose
.
as_view
(),
name
=
"dashboard.views.storage-choose"
),
url
(
r"^storage/delete/(?P<pk>\d+)/$"
,
StorageDelete
.
as_view
(),
name
=
"dashboard.views.storage-delete"
),
url
(
r"^storage/restore/(?P<pk>\d+)/$"
,
StorageRestore
.
as_view
(),
name
=
"dashboard.views.storage-restore"
),
url
(
r'^storage/endpoint/create/$'
,
EndpointCreate
.
as_view
(),
name
=
"dashboard.views.storage-endpoint-create"
),
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/views/storage.py
View file @
5c743bdc
...
...
@@ -172,7 +172,9 @@ class StorageList(SuperuserRequiredMixin, FilterMixin, SingleTableView):
def
get_queryset
(
self
):
logger
.
debug
(
'StorageList.get_queryset() called. User:
%
s'
,
unicode
(
self
.
request
.
user
))
qs
=
DataStore
.
get_all
()
cleaned_data
=
self
.
search_form
.
cleaned_data
stype
=
cleaned_data
.
get
(
'stype'
,
"all"
)
qs
=
self
.
get_queryset_by_stype
(
stype
)
self
.
create_fake_get
()
try
:
...
...
@@ -183,6 +185,14 @@ class StorageList(SuperuserRequiredMixin, FilterMixin, SingleTableView):
return
qs
def
get_queryset_by_stype
(
self
,
stype
):
if
stype
==
"all"
:
return
DataStore
.
get_all
()
elif
stype
==
"destroyed"
:
return
DataStore
.
objects
.
filter
(
destroyed__isnull
=
False
)
else
:
return
DataStore
.
objects
.
filter
(
destroyed__isnull
=
True
)
class
StorageDetail
(
SuperuserRequiredMixin
,
UpdateView
):
model
=
DataStore
...
...
@@ -273,6 +283,84 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView):
return
reverse
(
"dashboard.views.storage-detail"
,
kwargs
=
{
"pk"
:
ds
.
id
})
class
StorageDelete
(
SuperuserRequiredMixin
,
DeleteView
):
model
=
DataStore
success_message
=
_
(
"Endpoint successfully deleted."
)
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
check_destroyable
(
self
):
object
=
self
.
get_object
()
if
not
object
.
is_destroyable
:
raise
PermissionDenied
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
try
:
self
.
check_destroyable
()
except
PermissionDenied
:
message
=
ugettext
(
"Another object references"
" to the selected object."
)
if
request
.
is_ajax
():
return
JsonResponse
({
"error"
:
message
})
else
:
messages
.
warning
(
request
,
message
)
return
redirect
(
self
.
get_success_url
())
return
super
(
StorageDelete
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.storage-list"
)
def
delete_obj
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
get_object
()
.
destroy
()
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
check_destroyable
()
self
.
delete_obj
(
request
,
*
args
,
**
kwargs
)
if
request
.
is_ajax
():
return
JsonResponse
(
json
.
dumps
({
'message'
:
self
.
success_message
}),
)
else
:
messages
.
success
(
request
,
self
.
success_message
)
return
HttpResponseRedirect
(
self
.
get_success_url
())
class
StorageRestore
(
SuperuserRequiredMixin
,
UpdateView
):
model
=
DataStore
fields
=
(
"destroyed"
,)
success_message
=
_
(
"Data store successfully restored."
)
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-restore.html'
]
else
:
return
[
'dashboard/confirm/base-restore.html'
]
def
form_valid
(
self
,
form
):
object
=
self
.
get_object
()
object
.
destroyed
=
None
object
.
save
()
if
self
.
request
.
is_ajax
():
return
JsonResponse
(
json
.
dumps
({
'message'
:
self
.
success_message
}),
)
else
:
messages
.
success
(
self
.
request
,
self
.
success_message
)
return
HttpResponseRedirect
(
self
.
get_success_url
())
def
get_success_url
(
self
):
ds
=
self
.
get_object
()
return
reverse_lazy
(
"dashboard.views.storage-detail"
,
kwargs
=
{
"pk"
:
ds
.
id
})
class
DiskDetail
(
SuperuserRequiredMixin
,
UpdateView
):
model
=
Disk
form_class
=
DiskForm
...
...
@@ -407,14 +495,14 @@ class EndpointDelete(SuperuserRequiredMixin, DeleteView):
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
check_
referenc
e
(
self
):
def
check_
deletabl
e
(
self
):
object
=
self
.
get_object
()
if
object
.
datastore_set
.
count
()
!=
0
:
if
not
object
.
is_deletable
:
raise
PermissionDenied
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
try
:
self
.
check_
referenc
e
()
self
.
check_
deletabl
e
()
except
PermissionDenied
:
message
=
ugettext
(
"Another object references"
" to the selected object."
)
...
...
@@ -432,13 +520,12 @@ class EndpointDelete(SuperuserRequiredMixin, DeleteView):
self
.
get_object
()
.
delete
()
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
check_
referenc
e
()
self
.
check_
deletabl
e
()
self
.
delete_obj
(
request
,
*
args
,
**
kwargs
)
if
request
.
is_ajax
():
return
Http
Response
(
return
Json
Response
(
json
.
dumps
({
'message'
:
self
.
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
self
.
success_message
)
...
...
This diff is collapsed.
Click to expand it.
circle/dashboard/views/vm.py
View file @
5c743bdc
...
...
@@ -437,7 +437,8 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView):
val
=
super
(
VmCreateDiskView
,
self
)
.
get_form_kwargs
()
num
=
op
.
instance
.
disks
.
count
()
+
1
val
[
'default'
]
=
"
%
s
%
d"
%
(
op
.
instance
.
name
,
num
)
val
[
'datastore_choices'
]
=
DataStore
.
get_all
()
val
[
'datastore_choices'
]
=
DataStore
.
objects
.
filter
(
destroyed__isnull
=
True
)
return
val
...
...
@@ -453,7 +454,8 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView):
def
get_form_kwargs
(
self
):
val
=
super
(
VmDownloadDiskView
,
self
)
.
get_form_kwargs
()
val
[
'datastore_choices'
]
=
DataStore
.
get_all
()
val
[
'datastore_choices'
]
=
DataStore
.
objects
.
filter
(
destroyed__isnull
=
True
)
return
val
...
...
This diff is collapsed.
Click to expand it.
circle/storage/migrations/0007_datastore_destroyed.py
0 → 100644
View file @
5c743bdc
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'storage'
,
'0006_auto_20160505_0824'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'datastore'
,
name
=
'destroyed'
,
field
=
models
.
DateTimeField
(
default
=
None
,
null
=
True
,
blank
=
True
),
),
]
This diff is collapsed.
Click to expand it.
circle/storage/models.py
View file @
5c743bdc
...
...
@@ -63,6 +63,10 @@ class Endpoint(Model):
def
__unicode__
(
self
):
return
u"
%
s |
%
s:
%
d"
%
(
self
.
name
,
self
.
address
,
self
.
port
)
@property
def
is_deletable
(
self
):
return
self
.
datastore_set
.
filter
(
destroyed__isnull
=
True
)
.
count
()
==
0
class
DataStore
(
Model
):
...
...
@@ -86,6 +90,7 @@ class DataStore(Model):
verbose_name
=
_
(
'Ceph username'
))
secret_uuid
=
CharField
(
max_length
=
255
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'uuid of secret key'
))
destroyed
=
DateTimeField
(
blank
=
True
,
default
=
None
,
null
=
True
)
class
Meta
:
ordering
=
[
'name'
]
...
...
@@ -120,6 +125,14 @@ class DataStore(Model):
return
[(
ep
.
address
,
ep
.
port
)
for
ep
in
self
.
endpoints
.
all
()]
def
destroy
(
self
):
if
self
.
destroyed
:
return
False
self
.
destroyed
=
timezone
.
now
()
self
.
save
()
return
True
@property
def
used_percent
(
self
):
stats
=
self
.
get_statistics
()
...
...
@@ -127,6 +140,13 @@ class DataStore(Model):
return
int
(
100
-
free_percent
)
@property
def
is_destroyable
(
self
):
disk_count
=
self
.
disk_set
.
filter
(
destroyed__isnull
=
True
)
.
count
()
template_count
=
self
.
instancetemplate_set
.
count
()
vm_count
=
self
.
instance_set
.
filter
(
destroyed_at__isnull
=
True
)
.
count
()
return
0
==
disk_count
+
vm_count
+
template_count
@method_cache
(
30
)
def
get_statistics
(
self
,
timeout
=
15
):
q
=
self
.
get_remote_queue_name
(
"storage"
,
priority
=
"fast"
)
...
...
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