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
A prog2-höz tartozó friss repo anyagok itt elérhetőek:
https://git.iit.bme.hu/
Commit
4cbd33d4
authored
Aug 29, 2016
by
Czémán Arnold
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dashboard, vm, storage: add disk snapshoting feature
parent
a8272957
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
321 additions
and
9 deletions
+321
-9
circle/dashboard/forms.py
+83
-0
circle/dashboard/static/dashboard/dashboard.less
+18
-0
circle/dashboard/templates/dashboard/_disk-list-element.html
+49
-4
circle/dashboard/views/vm.py
+20
-0
circle/storage/migrations/0003_auto_20160826_1619.py
+18
-0
circle/storage/models.py
+30
-4
circle/storage/tasks/storage_tasks.py
+21
-1
circle/vm/operations.py
+82
-0
No files found.
circle/dashboard/forms.py
View file @
4cbd33d4
...
...
@@ -19,6 +19,7 @@ from __future__ import absolute_import
from
datetime
import
timedelta
from
urlparse
import
urlparse
import
re
from
django.forms
import
ModelForm
from
django.contrib.auth.forms
import
(
...
...
@@ -897,6 +898,88 @@ class VmDiskRemoveForm(OperationForm):
return
helper
def
snapshot_name_validator
(
name
):
number
=
re
.
compile
(
r'^\d+$'
)
if
number
.
match
(
name
):
raise
forms
.
ValidationError
(
_
(
'The name shall not be a number.'
),
code
=
'invalid'
)
class
VmCommonSnapshotDiskForm
(
OperationForm
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
self
.
disk
=
kwargs
.
pop
(
'default'
)
self
.
snap_id
=
kwargs
.
pop
(
'snap_id'
)
self
.
snap_name
=
kwargs
.
pop
(
'snap_name'
)
super
(
VmCommonSnapshotDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
[
'disk'
]
=
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
self
.
disk
,
required
=
True
,
empty_label
=
None
,
label
=
_
(
'Disk'
))
if
self
.
disk
:
self
.
fields
[
'disk'
]
.
widget
=
HiddenInput
()
self
.
fields
[
'snap_id'
]
=
forms
.
IntegerField
(
initial
=
self
.
snap_id
,
widget
=
HiddenInput
())
self
.
fields
[
'snap_name'
]
=
forms
.
CharField
(
initial
=
self
.
snap_name
,
widget
=
HiddenInput
(),
validators
=
[
snapshot_name_validator
])
@property
def
helper
(
self
):
helper
=
super
(
VmCommonSnapshotDiskForm
,
self
)
.
helper
if
self
.
disk
:
helper
.
layout
=
Layout
(
AnyTag
(
'div'
,
HTML
(
_
(
'<label>Disk:</label>
%
s<br />'
'<label>Snapshot:</label>
%
s (#
%
s)'
)
%
(
escape
(
self
.
disk
),
escape
(
self
.
snap_name
),
escape
(
self
.
snap_id
))),
css_class
=
'form-group'
,
),
Field
(
'disk'
),
Field
(
'snap_id'
),
Field
(
'snap_name'
),
)
return
helper
class
VmSnapshotDiskForm
(
OperationForm
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
self
.
disk
=
kwargs
.
pop
(
'default'
)
super
(
VmSnapshotDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
[
'disk'
]
=
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
self
.
disk
,
required
=
True
,
empty_label
=
None
,
label
=
_
(
'Disk'
))
if
self
.
disk
:
self
.
fields
[
'disk'
]
.
widget
=
HiddenInput
()
self
.
fields
[
'snap_name'
]
=
forms
.
CharField
(
validators
=
[
snapshot_name_validator
])
@property
def
helper
(
self
):
helper
=
super
(
VmSnapshotDiskForm
,
self
)
.
helper
if
self
.
disk
:
helper
.
layout
=
Layout
(
AnyTag
(
'div'
,
HTML
(
_
(
'<label>Disk:</label>
%
s'
)
%
escape
(
self
.
disk
)),
css_class
=
'form-group'
,
),
Field
(
'disk'
),
Field
(
'snap_name'
),
)
return
helper
class
VmDownloadDiskForm
(
OperationForm
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
),
required
=
False
)
url
=
forms
.
CharField
(
label
=
_
(
'URL'
),
validators
=
[
URLValidator
(),
])
...
...
circle/dashboard/static/dashboard/dashboard.less
View file @
4cbd33d4
...
...
@@ -1533,3 +1533,21 @@ textarea[name="new_members"] {
#manage-access-select-all {
cursor: pointer;
}
.snapshot-list {
margin-top: 5px;
padding-left: 5px;
background: gray;
}
.show-snapshot-btn {
margin-top: 10px;
}
.disk-create_snapshot-btn {
margin-right: 5px;
}
.snapshot-table {
background: white;
}
circle/dashboard/templates/dashboard/_disk-list-element.html
View file @
4cbd33d4
...
...
@@ -6,6 +6,14 @@
<span
class=
"operation-wrapper pull-right"
>
<div>
{% if op.create_snapshot %}
<a
href=
"{{ op.create_snapshot.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-{{ op.create_snapshot.effect }} operation disk-create_snapshot-btn
{% if op.create_snapshot.disabled %}disabled{% endif %}"
>
<i
class=
"fa fa-{{ op.create_snapshot.icon }} fa-fw-12"
></i>
{% trans "Snapshot" %}
</a>
{% endif %}
{% if d.is_resizable %}
{% if op.resize_disk %}
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
...
...
@@ -30,10 +38,47 @@
<i
class=
"fa fa-{{ op.remove_disk.icon }} fa-fw-12"
></i>
{% trans "Remove" %}
</a>
{% endif %}
</div>
<div
class=
"pull-right"
>
{% if perms.view_snapshot and d.list_snapshots %}
<input
type=
"button"
class=
"btn btn-default btn-xs show-snapshot-btn"
data-toggle=
"collapse"
data-target=
"#snapshots-{{ d.pk }}"
value=
"{% trans "
Show
snapshots
"
%}"
/>
{% endif %}
</div>
</span>
<div
style=
"clear: both;"
></div>
<br
/>
{% if request.user.is_superuser %}
<small>
{% trans "File name" %}: {{ d.filename }}
</small><br/>
<small>
{% trans "Bus" %}: {{ d.device_bus }}
</small>
<small>
{% trans "File name" %}: {{ d.filename }}
</small><br
/>
<small>
{% trans "Bus" %}: {{ d.device_bus }}
</small><br
/>
{% endif %}
<div
style=
"clear: both;"
></div>
{% if perms.storage.view_snapshot %}
<div
id=
"snapshots-{{ d.pk }}"
class=
"collapse out snapshot-list"
>
<table
class=
"table table-striped info-panel small snapshot-table"
>
{% for snap in d.list_snapshots %}
<tr>
<td>
{{ snap.id }}
</td>
<td>
{{ snap.name }}
</td>
<td><span
title=
"{{ snap.date }}"
>
{{ snap.date_human }}
</span></td>
<td>
<div
class=
"pull-right"
>
<a
href=
"{{ op.revert_snapshot.get_url }}?disk={{d.pk}}&snap_name={{ snap.name }}&snap_id={{ snap.id }}"
class=
"btn btn-xs btn-{{ op.revert_snapshot.effect }} operation disk-revert_snapshot-btn
{% if op.revert_snapshot.disabled %}disabled{% endif %}"
title=
"{% trans "
Revert
"
%}"
>
<i
class=
"fa fa-{{ op.revert_snapshot.icon }} fa-fw-12"
></i>
</a>
<a
href=
"{{ op.remove_snapshot.get_url }}?disk={{d.pk}}&snap_name={{ snap.name }}&snap_id={{ snap.id }}"
class=
"btn btn-xs btn-{{ op.remove_snapshot.effect }} operation disk-remove_snapshot-btn
{% if op.remove_snapshot.disabled %}disabled{% endif %}"
title=
"{% trans "
Remove
"
%}"
>
<i
class=
"fa fa-{{ op.remove_snapshot.icon }} fa-fw-12"
></i>
</a>
</div>
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
circle/dashboard/views/vm.py
View file @
4cbd33d4
...
...
@@ -65,6 +65,7 @@ from ..forms import (
VmAddInterfaceForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
VmSaveForm
,
VmRenewForm
,
VmStateChangeForm
,
VmListSearchForm
,
VmCustomizeForm
,
VmDiskResizeForm
,
RedeployForm
,
VmDiskRemoveForm
,
VmSnapshotDiskForm
,
VmCommonSnapshotDiskForm
,
VmMigrateForm
,
VmDeployForm
,
VmPortRemoveForm
,
VmPortAddForm
,
VmRemoveInterfaceForm
,
...
...
@@ -743,6 +744,18 @@ class VmDeployView(FormOperationMixin, VmOperationView):
return
kwargs
class
VmCommonSnapshotDiskView
(
VmDiskModifyView
):
form_class
=
VmCommonSnapshotDiskForm
def
get_form_kwargs
(
self
):
snap_id
=
self
.
request
.
GET
.
get
(
'snap_id'
)
snap_name
=
self
.
request
.
GET
.
get
(
'snap_name'
)
val
=
super
(
VmCommonSnapshotDiskView
,
self
)
.
get_form_kwargs
()
val
.
update
({
'snap_id'
:
snap_id
,
'snap_name'
:
snap_name
})
return
val
vm_ops
=
OrderedDict
([
(
'deploy'
,
VmDeployView
),
(
'wake_up'
,
VmOperationView
.
factory
(
...
...
@@ -792,6 +805,13 @@ vm_ops = OrderedDict([
op
=
'install_keys'
,
icon
=
'key'
,
effect
=
'info'
,
show_in_toolbar
=
False
,
)),
(
'create_snapshot'
,
VmDiskModifyView
.
factory
(
op
=
'create_snapshot'
,
icon
=
'camera'
,
effect
=
'success'
,
form_class
=
VmSnapshotDiskForm
)),
(
'remove_snapshot'
,
VmCommonSnapshotDiskView
.
factory
(
op
=
'remove_snapshot'
,
icon
=
'times'
,
effect
=
'danger'
)),
(
'revert_snapshot'
,
VmCommonSnapshotDiskView
.
factory
(
op
=
'revert_snapshot'
,
icon
=
'backward'
,
effect
=
'warning'
)),
])
...
...
circle/storage/migrations/0003_auto_20160826_1619.py
0 → 100644
View file @
4cbd33d4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'storage'
,
'0002_disk_bus'
),
]
operations
=
[
migrations
.
AlterModelOptions
(
name
=
'disk'
,
options
=
{
'ordering'
:
[
'name'
],
'verbose_name'
:
'disk'
,
'verbose_name_plural'
:
'disks'
,
'permissions'
:
((
'create_empty_disk'
,
'Can create an empty disk.'
),
(
'download_disk'
,
'Can download a disk.'
),
(
'resize_disk'
,
'Can resize a disk.'
),
(
'create_snapshot'
,
'Can create snapshot'
),
(
'remove_snapshot'
,
'Can remove snapshot'
),
(
'revert_snapshot'
,
'Can revert snapshot'
),
(
'view_snapshot'
,
'Can view snapshot'
))},
),
]
circle/storage/models.py
View file @
4cbd33d4
...
...
@@ -24,6 +24,8 @@ from os.path import join
import
uuid
import
re
import
arrow
from
celery.contrib.abortable
import
AbortableAsyncResult
from
django.db.models
import
(
Model
,
BooleanField
,
CharField
,
DateTimeField
,
ForeignKey
)
...
...
@@ -142,7 +144,11 @@ class Disk(TimeStampedModel):
permissions
=
(
(
'create_empty_disk'
,
_
(
'Can create an empty disk.'
)),
(
'download_disk'
,
_
(
'Can download a disk.'
)),
(
'resize_disk'
,
_
(
'Can resize a disk.'
))
(
'resize_disk'
,
_
(
'Can resize a disk.'
)),
(
'create_snapshot'
,
_
(
'Can create snapshot'
)),
(
'remove_snapshot'
,
_
(
'Can remove snapshot'
)),
(
'revert_snapshot'
,
_
(
'Can revert snapshot'
)),
(
'view_snapshot'
,
_
(
'Can view snapshot'
)),
)
class
DiskError
(
HumanReadableException
):
...
...
@@ -391,9 +397,9 @@ class Disk(TimeStampedModel):
queue_name
=
self
.
get_remote_queue_name
(
'storage'
,
priority
=
"fast"
)
disk_desc
=
self
.
get_disk_desc
()
if
self
.
base
is
not
None
:
storage_tasks
.
snapshot
.
apply_async
(
args
=
[
disk_desc
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
storage_tasks
.
snapshot
_from_base
.
apply_async
(
args
=
[
disk_desc
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
else
:
storage_tasks
.
create
.
apply_async
(
args
=
[
disk_desc
],
queue
=
queue_name
...
...
@@ -403,6 +409,26 @@ class Disk(TimeStampedModel):
self
.
save
()
return
True
def
repack_snapshot_info
(
self
,
snap
):
date
=
arrow
.
get
(
snap
[
'date-sec'
])
return
{
'id'
:
snap
[
'id'
],
'name'
:
snap
[
'name'
],
'date'
:
date
.
format
(
'YYYY.DD.MM. hh:mm:ss'
),
'date_human'
:
date
.
humanize
(),
}
@method_cache
(
30
)
def
list_snapshots
(
self
,
timeout
=
15
):
if
not
self
.
is_ready
:
return
[]
queue_name
=
self
.
get_remote_queue_name
(
'storage'
,
priority
=
'fast'
)
disk_desc
=
self
.
get_disk_desc
()
snaps
=
storage_tasks
.
list_snapshots
.
apply_async
(
args
=
[
disk_desc
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
)
return
[
self
.
repack_snapshot_info
(
snap
)
for
snap
in
snaps
]
@classmethod
def
create
(
cls
,
user
=
None
,
**
params
):
disk
=
cls
.
__create
(
user
,
params
)
...
...
circle/storage/tasks/storage_tasks.py
View file @
4cbd33d4
...
...
@@ -48,8 +48,28 @@ def delete_dump(path):
pass
@celery.task
(
name
=
'storagedriver.snapshot_from_base'
)
def
snapshot_from_base
(
disk_desc
):
pass
@celery.task
(
name
=
'storagedriver.snapshot'
)
def
snapshot
(
disk_desc
):
def
snapshot
(
disk_desc
,
name
):
pass
@celery.task
(
name
=
'storagedriver.list_snapshots'
)
def
list_snapshots
(
disk_desc
):
pass
@celery.task
(
name
=
'storagedriver.remove_snapshot'
)
def
remove_snapshot
(
disk_desc
,
id
):
pass
@celery.task
(
name
=
'storagedriver.revert_snapshot'
)
def
revert_snapshot
(
disk_desc
,
id
):
pass
...
...
circle/vm/operations.py
View file @
4cbd33d4
...
...
@@ -283,6 +283,88 @@ class CreateDiskOperation(InstanceOperation):
size
=
filesizeformat
(
kwargs
[
'size'
]),
name
=
kwargs
[
'name'
])
class
RemoteSnapshotDiskOperation
(
InstanceOperation
):
remote_queue
=
(
'storage'
,
'slow'
)
remote_timeout
=
30
def
_operation
(
self
,
disk
,
**
kwargs
):
if
disk
:
if
not
disk
.
is_ready
:
raise
disk
.
DiskIsNotReady
(
disk
)
disk_desc
=
disk
.
get_disk_desc
()
args
=
[
disk_desc
]
+
self
.
_get_remote_args
(
**
kwargs
)
return
self
.
task
.
apply_async
(
args
=
args
,
queue
=
disk
.
get_remote_queue_name
(
*
self
.
remote_queue
)
)
.
get
(
timeout
=
self
.
remote_timeout
)
@register_operation
class
CreateSnapshotDiskOperation
(
RemoteSnapshotDiskOperation
):
id
=
'create_snapshot'
name
=
_
(
'create snapshot'
)
description
=
_
(
'Create snapshot from disk.'
)
required_perms
=
(
'storage.create_snapshot'
,
)
accept_states
=
(
'STOPPED'
)
task
=
storage_tasks
.
snapshot
def
_get_remote_args
(
self
,
**
kwargs
):
snap_name
=
kwargs
.
get
(
'snap_name'
)
if
not
snap_name
:
snap_name
=
'new snapshot'
return
[
snap_name
]
def
get_activity_name
(
self
,
kwargs
):
return
create_readable
(
ugettext_noop
(
'Created snapshot
%(snap_name)
s'
' from disk
%(disk_name)
s'
),
disk_name
=
kwargs
[
'disk'
]
.
name
,
snap_name
=
kwargs
[
'snap_name'
])
@register_operation
class
RemoveSnapshotDiskOperation
(
RemoteSnapshotDiskOperation
):
id
=
'remove_snapshot'
name
=
_
(
'remove snapshot'
)
description
=
_
(
'Remove snapshot from disk.'
)
required_perms
=
(
'storage.remove_snapshot'
,
)
task
=
storage_tasks
.
remove_snapshot
def
_get_remote_args
(
self
,
**
kwargs
):
return
[
kwargs
.
get
(
'snap_id'
)]
def
get_activity_name
(
self
,
kwargs
):
return
create_readable
(
ugettext_noop
(
'Removed snapshot
%(snap_name)
s'
' from disk
%(disk_name)
s'
),
disk_name
=
kwargs
[
'disk'
]
.
name
,
snap_name
=
kwargs
[
'snap_name'
])
@register_operation
class
RevertSnapshotDiskOperation
(
RemoteSnapshotDiskOperation
):
id
=
'revert_snapshot'
name
=
_
(
'revert snapshot'
)
description
=
_
(
'Revert snapshot on disk.'
)
required_perms
=
(
'storage.revert_snapshot'
,
)
accept_states
=
(
'STOPPED'
)
task
=
storage_tasks
.
revert_snapshot
def
_get_remote_args
(
self
,
**
kwargs
):
return
[
kwargs
.
get
(
'snap_id'
)]
def
get_activity_name
(
self
,
kwargs
):
return
create_readable
(
ugettext_noop
(
'Revert snapshot
%(snap_name)
s'
' on disk
%(disk_name)
s'
),
disk_name
=
kwargs
[
'disk'
]
.
name
,
snap_name
=
kwargs
[
'snap_name'
])
@register_operation
class
ResizeDiskOperation
(
RemoteInstanceOperation
):
...
...
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