Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
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
9b3516eb
authored
Jan 08, 2026
by
Your Name
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
extended storage handling
parent
b578176f
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
353 additions
and
75 deletions
+353
-75
circle/dashboard/forms.py
+20
-13
circle/dashboard/static/dashboard/activity.js
+14
-4
circle/dashboard/templates/base.html
+95
-0
circle/dashboard/templates/dashboard/lease-edit.html
+2
-2
circle/dashboard/templates/dashboard/storage/detail.html
+47
-1
circle/dashboard/views/storage.py
+165
-48
circle/fabfile.py
+1
-1
circle/storage/models.py
+7
-5
circle/storage/tasks/periodic_tasks.py
+1
-0
circle/vm/models/instance.py
+1
-1
No files found.
circle/dashboard/forms.py
View file @
9b3516eb
...
...
@@ -728,48 +728,58 @@ class LeaseForm(forms.ModelForm):
Field
(
"delete_interval_seconds"
,
type
=
"hidden"
,
value
=
"0"
),
HTML
(
string_concat
(
"<label>"
,
_
(
"Suspend in"
),
"</label>"
)),
Div
(
NumberField
(
"suspend_minutes"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"min"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"suspend_hours"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"h
ours
"
)),
HTML
(
_
(
"h"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"suspend_days"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"d
ays
"
)),
HTML
(
_
(
"d"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"suspend_weeks"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"w
eeks
"
)),
HTML
(
_
(
"w
k
"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"suspend_months"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"mo
nths
"
)),
HTML
(
_
(
"mo"
)),
css_class
=
"input-group-addon"
,
),
css_class
=
"input-group interval-input"
,
),
HTML
(
string_concat
(
"<label>"
,
_
(
"Delete in"
),
"</label>"
)),
Div
(
NumberField
(
"delete_minutes"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"min"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"delete_hours"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"h
ours
"
)),
HTML
(
_
(
"h"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"delete_days"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"d
ays
"
)),
HTML
(
_
(
"d"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"delete_weeks"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"w
eeks
"
)),
HTML
(
_
(
"w
k
"
)),
css_class
=
"input-group-addon"
,
),
NumberField
(
"delete_months"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"mo
nths
"
)),
HTML
(
_
(
"mo"
)),
css_class
=
"input-group-addon"
,
),
css_class
=
"input-group interval-input"
,
...
...
@@ -1706,10 +1716,10 @@ class UserListSearchForm(forms.Form):
class
DataStoreForm
(
ModelForm
):
@property
def
helper
(
self
):
helper
=
FormHelper
()
helper
.
form_tag
=
False
# IMPORTANT: form tag is in template
helper
.
layout
=
Layout
(
Fieldset
(
''
,
...
...
@@ -1717,15 +1727,12 @@ class DataStoreForm(ModelForm):
'path'
,
'hostname'
,
),
FormActions
(
Submit
(
'submit'
,
_
(
'Save'
)),
)
)
return
helper
class
Meta
:
model
=
DataStore
fields
=
(
"name"
,
"path"
,
"hostname"
,
)
fields
=
(
"name"
,
"path"
,
"hostname"
)
class
DiskForm
(
ModelForm
):
...
...
circle/dashboard/static/dashboard/activity.js
View file @
9b3516eb
...
...
@@ -104,9 +104,19 @@ $(function() {
});
/* if the operation fails show the modal again */
/* submit only the operation form via AJAX (JSON response expected) */
$
(
"body"
).
on
(
"submit"
,
"#confirmation-modal form"
,
function
(
e
)
{
e
.
preventDefault
()
var
$form
=
$
(
this
);
// Only intercept the modal "operation" form submission.
// If the form does NOT contain the op submit button, let the browser handle it normally
// (redirects, GET forms, multi-step forms, etc.).
if
(
$form
.
find
(
"#op-form-send"
).
length
===
0
)
{
return
true
;
}
e
.
preventDefault
();
var
url
=
$form
.
attr
(
"action"
);
$
.
ajax
({
...
...
@@ -115,10 +125,9 @@ $(function() {
type
:
'POST'
,
data
:
$form
.
serialize
(),
success
:
function
(
data
)
{
// mindig zárjuk le az aktuális modalt
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
if
(
data
.
success
)
{
if
(
data
&&
data
.
success
)
{
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
if
(
data
.
with_reload
)
{
...
...
@@ -129,7 +138,6 @@ $(function() {
addMessage
(
data
.
messages
.
join
(
"<br />"
),
data
.
success
?
"success"
:
"danger"
);
}
}
else
{
// a bezárás után nyissuk meg az új (hibás) modalt
$
(
'#confirmation-modal'
).
one
(
'hidden.bs.modal'
,
function
()
{
showConfirmationModal
(
data
);
});
...
...
@@ -140,6 +148,8 @@ $(function() {
if
(
xhr
.
status
===
500
)
{
addMessage
(
"500 Internal Server Error"
,
"danger"
);
}
else
if
(
xhr
.
status
===
405
)
{
addMessage
(
"405 Method Not Allowed"
,
"danger"
);
}
else
{
addMessage
(
xhr
.
status
+
" Unknown Error"
,
"danger"
);
}
...
...
circle/dashboard/templates/base.html
View file @
9b3516eb
...
...
@@ -99,5 +99,100 @@
{% block extra_etc %}
{% endblock %}
<style>
/* Full-page loading overlay */
#loading-overlay
{
position
:
fixed
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
background
:
rgba
(
255
,
255
,
255
,
0.85
);
z-index
:
9999
;
display
:
none
;
}
#loading-overlay
.spinner
{
position
:
absolute
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
text-align
:
center
;
color
:
#555
;
}
</style>
<div
id=
"loading-overlay"
>
<div
class=
"spinner"
>
<i
class=
"fa fa-spinner fa-spin fa-3x"
></i>
<p>
{% trans "Loading..." %}
</p>
</div>
</div>
//
<script>
// (function () {
// // Robust loader without beforeunload/select-change pitfalls.
// var overlay = document.getElementById("loading-overlay");
// if (!overlay) return;
//
// var timer = null;
//
// function showLoaderDelayed() {
// // Avoid stacking timers.
// if (timer) return;
//
// timer = setTimeout(function () {
// overlay.style.display = "block";
// }, 300); // show only if navigation takes noticeable time
// }
//
// function cancelLoader() {
// if (timer) {
// clearTimeout(timer);
// timer = null;
// }
// // In case it was shown and the browser restored from BFCache.
// overlay.style.display = "none";
// }
//
// // Cancel on load/pageshow (covers bfcache restore too).
// window.addEventListener("load", cancelLoader);
// window.addEventListener("pageshow", cancelLoader);
//
// // Show on form submit (POST or GET).
// var forms = document.getElementsByTagName("form");
// for (var i = 0; i
<
forms
.
length
;
i
++
)
{
// forms[i].addEventListener("submit", function () {
//// showLoaderDelayed();
// });
// }
// // Show on autosubmit select change (programmatic form.submit() does not trigger submit events).
// var autos = document.querySelectorAll('select[data-autosubmit="1"]');
// for (var k = 0; k
<
autos
.
length
;
k
++
)
{
// autos[k].addEventListener("change", function () {
// showLoaderDelayed();
// });
// }
//
// // Show on same-tab link clicks.
// var links = document.getElementsByTagName("a");
// for (var j = 0; j
<
links
.
length
;
j
++
)
{
// (function (a) {
// a.addEventListener("click", function (e) {
// // Ignore modified clicks (new tab/window, etc.)
// if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey) return;
// if (a.target && a.target !== "_self") return;
// if (!a.href) return;
//
// // Ignore hash-only navigation on the same page.
// var href = a.getAttribute("href");
// if (href && href.charAt(0) === "#") return;
//
//// showLoaderDelayed();
// });
// })(links[j]);
// }
// })();
//
</script>
</body>
</html>
circle/dashboard/templates/dashboard/lease-edit.html
View file @
9b3516eb
...
...
@@ -6,7 +6,7 @@
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-
7
"
>
<div
class=
"col-md-
6
"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
class=
"pull-right btn btn-default btn-xs"
href=
"{% url "
dashboard
.
views
.
template-list
"
%}"
>
{% trans "Back" %}
</a>
...
...
@@ -21,7 +21,7 @@
</div>
</div>
<div
class=
"col-md-
5
"
>
<div
class=
"col-md-
6
"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h4
class=
"no-margin"
><i
class=
"icon-group"
></i>
{% trans "Manage access" %}
</h4>
...
...
circle/dashboard/templates/dashboard/storage/detail.html
View file @
9b3516eb
...
...
@@ -16,10 +16,51 @@
<h3
class=
"no-margin"
><i
class=
"fa fa-database"
></i>
{% trans "Datastore" %}
</h3>
</div>
<div
class=
"panel-body"
>
{% crispy form %}
<form
method=
"get"
action=
""
>
<div
class=
"form-group"
>
<label>
{% trans "Datastore" %}
</label>
<select
class=
"form-control"
name=
"ds"
data-autosubmit=
"1"
onchange=
"this.form.submit()"
>
<option
value=
"new"
{%
if
ds_selected =
=
"
new
"
%}
selected
{%
endif
%}
>
-- {% trans "Create new datastore" %} --
</option>
{% for d in datastores %}
<option
value=
"{{ d.pk }}"
{%
if
ds
and
ds
.
pk =
=
d
.
pk
%}
selected
{%
endif
%}
>
{{ d.name }} — {{ d.hostname }} : {{ d.path }}
</option>
{% endfor %}
</select>
</div>
{# tartsuk meg a filter/search paramokat, hogy ne vesszenek el #}
<input
type=
"hidden"
name=
"filter"
value=
"{{ request.GET.filter }}"
>
<input
type=
"hidden"
name=
"s"
value=
"{{ request.GET.s }}"
>
</form>
<hr/>
<form
method=
"post"
action=
""
>
{% csrf_token %}
<input
type=
"hidden"
name=
"ds"
value=
"{{ ds_selected }}"
>
{% crispy form %}
<div
class=
"form-actions"
>
{% if mode == "create" %}
<button
type=
"submit"
class=
"btn btn-success"
>
<i
class=
"fa fa-plus"
></i>
{% trans "Create" %}
</button>
{% else %}
<button
type=
"submit"
class=
"btn btn-primary"
>
<i
class=
"fa fa-save"
></i>
{% trans "Save" %}
</button>
{% endif %}
</div>
</form>
</div>
<!-- .panel-body -->
</div>
</div>
{% if stats %}
<div
class=
"col-md-7"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
...
...
@@ -62,10 +103,12 @@
</div>
<!-- .panel-body -->
</div>
</div>
{% endif %}
</div>
<div
class=
"row"
>
<div
class=
"col-md-12"
>
{% if stats %}
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h3
class=
"no-margin"
><i
class=
"fa fa-file"
></i>
{% trans "Disks" %}
</h3>
...
...
@@ -103,11 +146,13 @@
</div>
</div>
<!-- .panel-body -->
</div>
{% endif %}
</div>
</div>
<div
class=
"row"
>
<div
class=
"col-md-12"
>
{% if stats %}
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h3
class=
"no-margin"
>
...
...
@@ -152,6 +197,7 @@
</div>
</div>
<!-- .panel-body -->
</div>
{% endif %}
</div>
</div>
...
...
circle/dashboard/views/storage.py
View file @
9b3516eb
...
...
@@ -16,68 +16,150 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
__future__
import
unicode_literals
,
absolute_import
import
errno
from
django.contrib
import
messages
from
django.core.urlresolvers
import
reverse
from
django.db.models
import
Q
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.views.generic
import
UpdateView
from
django.views.generic
import
UpdateView
,
TemplateView
from
braces.views
import
SuperuserRequiredMixin
from
sizefield.utils
import
filesizeformat
from
braces.views
import
SuperuserRequiredMixin
from
common.models
import
WorkerNotFound
from
storage.models
import
DataStore
,
Disk
from
..tables
import
DiskListTable
from
..forms
import
DataStoreForm
,
DiskForm
from
..forms
import
DataStoreForm
,
DiskForm
from
django.shortcuts
import
get_object_or_404
,
redirect
from
django.db
import
IntegrityError
class
StorageDetail
(
SuperuserRequiredMixin
,
UpdateView
):
model
=
DataStore
form_class
=
DataStoreForm
class
StorageDetail
(
SuperuserRequiredMixin
,
TemplateView
):
template_name
=
"dashboard/storage/detail.html"
def
get_object
(
self
):
return
DataStore
.
objects
.
get
()
def
_current_querystring
(
self
):
# Preserve UI state across redirects.
parts
=
[]
ds
=
self
.
request
.
GET
.
get
(
"ds"
)
if
ds
:
parts
.
append
(
"ds=
%
s"
%
ds
)
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
StorageDetail
,
self
)
.
get_context_data
(
**
kwargs
)
flt
=
self
.
request
.
GET
.
get
(
"filter"
)
if
flt
:
parts
.
append
(
"filter=
%
s"
%
flt
)
s
=
self
.
request
.
GET
.
get
(
"s"
)
if
s
:
parts
.
append
(
"s=
%
s"
%
s
)
return
(
"?"
+
"&"
.
join
(
parts
))
if
parts
else
""
def
_redirect_with_ds
(
self
,
ds_pk
):
# Redirect back to page selecting a specific datastore.
parts
=
[
"ds=
%
s"
%
ds_pk
]
flt
=
self
.
request
.
GET
.
get
(
"filter"
)
if
flt
:
parts
.
append
(
"filter=
%
s"
%
flt
)
s
=
self
.
request
.
GET
.
get
(
"s"
)
if
s
:
parts
.
append
(
"s=
%
s"
%
s
)
return
redirect
(
"
%
s?
%
s"
%
(
self
.
request
.
path
,
"&"
.
join
(
parts
)))
def
get_datastore
(
self
):
ds_id
=
self
.
request
.
GET
.
get
(
"ds"
)
qs
=
DataStore
.
objects
.
order_by
(
"name"
)
# "new" (or empty) means create mode, no existing datastore.
if
ds_id
==
"new"
:
return
None
if
ds_id
:
return
get_object_or_404
(
DataStore
,
pk
=
ds_id
)
return
qs
.
first
()
# can be None if empty
ds
=
self
.
get_object
()
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
ds_id
=
request
.
POST
.
get
(
"ds"
)
# hidden field in template: "new" or an id
instance
=
None
if
ds_id
and
ds_id
!=
"new"
:
instance
=
get_object_or_404
(
DataStore
,
pk
=
int
(
ds_id
))
form
=
DataStoreForm
(
request
.
POST
,
instance
=
instance
)
if
not
form
.
is_valid
():
context
=
self
.
get_context_data
()
context
[
"form"
]
=
form
return
self
.
render_to_response
(
context
)
try
:
context
[
'stats'
]
=
self
.
_get_stats
()
context
[
'missing_disks'
]
=
ds
.
get_missing_disks
()
context
[
'orphan_disks'
]
=
ds
.
get_orphan_disks
()
except
WorkerNotFound
:
messages
.
error
(
self
.
request
,
_
(
"The DataStore is offline."
))
context
[
'disk_table'
]
=
DiskListTable
(
self
.
get_table_data
(),
request
=
self
.
request
,
template
=
"django_tables2/with_pagination.html"
)
context
[
'filter_names'
]
=
(
(
'vm'
,
_
(
"virtual machine"
)),
(
'template'
,
_
(
"template"
)),
(
'none'
,
_
(
"none"
)),
)
return
context
ds
=
form
.
save
()
except
IntegrityError
as
e
:
messages
.
error
(
request
,
_
(
"Could not save datastore:
%
s"
)
%
e
)
return
redirect
(
request
.
path
)
messages
.
success
(
request
,
_
(
"Datastore saved."
))
return
redirect
(
"
%
s?ds=
%
s"
%
(
request
.
path
,
ds
.
pk
))
# def post(self, request, *args, **kwargs):
# action = request.POST.get("action")
# if action == "create":
# return self._handle_create(request)
# if action == "update":
# return self._handle_update(request)
# messages.error(request, _("Unknown action."))
# return redirect(request.path + self._current_querystring())
def
_handle_create
(
self
,
request
):
form
=
DataStoreCreateForm
(
request
.
POST
)
if
not
form
.
is_valid
():
context
=
self
.
get_context_data
()
context
[
"create_form"
]
=
form
return
self
.
render_to_response
(
context
)
try
:
ds
=
form
.
save
()
except
IntegrityError
as
e
:
messages
.
error
(
request
,
_
(
"Could not create datastore:
%
s"
)
%
e
)
return
redirect
(
request
.
path
+
self
.
_current_querystring
())
messages
.
success
(
request
,
_
(
"Datastore created."
))
return
self
.
_redirect_with_ds
(
ds
.
pk
)
def
_handle_update
(
self
,
request
):
ds
=
self
.
get_datastore
()
if
ds
is
None
:
messages
.
error
(
request
,
_
(
"No datastore selected."
))
return
redirect
(
request
.
path
)
form
=
DataStoreForm
(
request
.
POST
,
instance
=
ds
)
if
not
form
.
is_valid
():
context
=
self
.
get_context_data
()
context
[
"edit_form"
]
=
form
return
self
.
render_to_response
(
context
)
try
:
ds
=
form
.
save
()
except
IntegrityError
as
e
:
messages
.
error
(
request
,
_
(
"Could not update datastore:
%
s"
)
%
e
)
return
self
.
_redirect_with_ds
(
ds
.
pk
)
messages
.
success
(
request
,
_
(
"Datastore updated."
))
return
self
.
_redirect_with_ds
(
ds
.
pk
)
def
get_table_data
(
self
,
ds
):
if
ds
is
None
:
return
Disk
.
objects
.
none
()
def
get_table_data
(
self
):
ds
=
self
.
get_object
()
qs
=
Disk
.
objects
.
filter
(
datastore
=
ds
,
destroyed
=
None
)
filter_name
=
self
.
request
.
GET
.
get
(
"filter"
)
search
=
self
.
request
.
GET
.
get
(
"s"
)
filter_queries
=
{
'vm'
:
{
'instance_set__isnull'
:
False
,
},
'template'
:
{
'template_set__isnull'
:
False
,
},
'none'
:
{
'template_set__isnull'
:
True
,
'instance_set__isnull'
:
True
,
}
'vm'
:
{
'instance_set__isnull'
:
False
},
'template'
:
{
'template_set__isnull'
:
False
},
'none'
:
{
'template_set__isnull'
:
True
,
'instance_set__isnull'
:
True
},
}
if
filter_name
:
...
...
@@ -90,21 +172,18 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView):
return
qs
def
_get_stats
(
self
):
# datastore stats
stats
=
self
.
object
.
get_statistics
()
def
get_stats
(
self
,
ds
):
stats
=
ds
.
get_statistics
()
free_space
=
int
(
stats
[
'free_space'
])
free_percent
=
float
(
stats
[
'free_percent'
])
total_space
=
free_space
/
(
free_percent
/
100.0
)
total_space
=
free_space
/
(
free_percent
/
100.0
)
used_space
=
total_space
-
free_space
# file stats
data
=
self
.
get_object
()
.
get_file_statistics
()
data
=
ds
.
get_file_statistics
()
dumps_size
=
sum
(
d
[
'size'
]
for
d
in
data
[
'dumps'
])
trash
=
sum
(
d
[
'size'
]
for
d
in
data
[
'trash'
])
iso_raw
=
sum
(
d
[
'size'
]
for
d
in
data
[
'disks'
]
if
d
[
'format'
]
in
(
"iso"
,
"raw"
))
iso_raw
=
sum
(
d
[
'size'
]
for
d
in
data
[
'disks'
]
if
d
[
'format'
]
in
(
"iso"
,
"raw"
))
vm_size
=
vm_actual_size
=
template_actual_size
=
0
for
d
in
data
[
'disks'
]:
...
...
@@ -127,8 +206,45 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView):
'template_actual_size'
:
template_actual_size
,
}
def
get_success_url
(
self
):
return
reverse
(
"dashboard.views.storage"
)
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
StorageDetail
,
self
)
.
get_context_data
(
**
kwargs
)
ds
=
self
.
get_datastore
()
context
[
"datastores"
]
=
DataStore
.
objects
.
order_by
(
"name"
)
context
[
"ds"
]
=
ds
# If no datastore exists yet, only show the create form.
if
ds
is
not
None
:
context
[
"form"
]
=
DataStoreForm
(
instance
=
ds
)
context
[
"mode"
]
=
"update"
context
[
"ds_selected"
]
=
str
(
ds
.
pk
)
try
:
context
[
"stats"
]
=
self
.
get_stats
(
ds
)
context
[
"missing_disks"
]
=
ds
.
get_missing_disks
()
context
[
"orphan_disks"
]
=
ds
.
get_orphan_disks
()
except
WorkerNotFound
:
messages
.
error
(
self
.
request
,
_
(
"The DataStore is offline."
))
except
(
OSError
,
IOError
)
as
e
:
messages
.
error
(
self
.
request
,
e
)
else
:
context
[
"form"
]
=
DataStoreForm
()
context
[
"mode"
]
=
"create"
context
[
"ds_selected"
]
=
"new"
context
[
"stats"
]
=
None
context
[
"missing_disks"
]
=
None
context
[
"orphan_disks"
]
=
None
context
[
"disk_table"
]
=
DiskListTable
(
self
.
get_table_data
(
ds
),
request
=
self
.
request
,
template
=
"django_tables2/with_pagination.html"
)
context
[
"filter_names"
]
=
(
(
'vm'
,
_
(
"virtual machine"
)),
(
'template'
,
_
(
"template"
)),
(
'none'
,
_
(
"none"
)),
)
return
context
class
DiskDetail
(
SuperuserRequiredMixin
,
UpdateView
):
...
...
@@ -138,3 +254,4 @@ class DiskDetail(SuperuserRequiredMixin, UpdateView):
def
form_valid
(
self
,
form
):
pass
circle/fabfile.py
View file @
9b3516eb
...
...
@@ -37,7 +37,7 @@ except Exception as e:
else
:
env
.
roledefs
[
'node'
]
=
[
unicode
(
n
.
host
.
ipv4
)
for
n
in
_Node
.
objects
.
filter
(
enabled
=
True
)]
env
.
roledefs
[
'storage'
]
=
[
_DataStore
.
objects
.
get
()
.
hostname
]
env
.
roledefs
[
'storage'
]
=
[
_DataStore
.
objects
.
all
()[
0
]
.
hostname
]
def
update_all
():
...
...
circle/storage/models.py
View file @
9b3516eb
...
...
@@ -47,14 +47,16 @@ class DataStore(Model):
"""Collection of virtual disks.
"""
name
=
CharField
(
max_length
=
100
,
unique
=
True
,
verbose_name
=
_
(
'name'
))
path
=
CharField
(
max_length
=
200
,
unique
=
True
,
verbose_name
=
_
(
'path'
))
hostname
=
CharField
(
max_length
=
40
,
unique
=
True
,
verbose_name
=
_
(
'hostname'
))
path
=
CharField
(
max_length
=
200
,
verbose_name
=
_
(
'path'
))
hostname
=
CharField
(
max_length
=
40
,
verbose_name
=
_
(
'hostname'
))
class
Meta
:
ordering
=
[
'name'
]
verbose_name
=
_
(
'datastore'
)
verbose_name_plural
=
_
(
'datastores'
)
unique_together
=
(
(
"hostname"
,
"path"
),
)
def
__unicode__
(
self
):
return
u'
%
s (
%
s)'
%
(
self
.
name
,
self
.
path
)
...
...
@@ -106,7 +108,7 @@ class DataStore(Model):
queue_name
=
self
.
get_remote_queue_name
(
'storage'
,
"slow"
)
files
=
set
(
storage_tasks
.
list_files
.
apply_async
(
args
=
[
self
.
path
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
))
disks
=
Disk
.
objects
.
filter
(
destroyed__isnull
=
True
,
is_ready
=
True
)
disks
=
Disk
.
objects
.
filter
(
d
atastore
=
self
,
d
estroyed__isnull
=
True
,
is_ready
=
True
)
return
disks
.
exclude
(
filename__in
=
files
)
@method_cache
(
120
)
...
...
@@ -426,7 +428,7 @@ class Disk(TimeStampedModel):
@classmethod
def
__create
(
cls
,
user
,
params
):
datastore
=
params
.
pop
(
'datastore'
,
DataStore
.
objects
.
get
()
)
datastore
=
params
.
pop
(
'datastore'
,
DataStore
.
objects
.
all
()[
0
]
)
filename
=
params
.
pop
(
'filename'
,
str
(
uuid
.
uuid4
()))
disk
=
cls
(
filename
=
filename
,
datastore
=
datastore
,
**
params
)
return
disk
...
...
circle/storage/tasks/periodic_tasks.py
View file @
9b3516eb
...
...
@@ -65,6 +65,7 @@ def list_orphan_disks(timeout=15):
queue_name
=
ds
.
get_remote_queue_name
(
'storage'
,
"slow"
)
files
=
set
(
storage_tasks
.
list_files
.
apply_async
(
args
=
[
ds
.
path
],
queue
=
queue_name
)
.
get
(
timeout
=
timeout
))
logging
.
error
(
"files in
%
s:
%
s"
%
(
ds
.
path
,
files
))
disks
=
set
([
disk
.
filename
for
disk
in
ds
.
disk_set
.
all
()])
for
i
in
files
-
disks
:
if
not
re
.
match
(
'cloud-[0-9]*
\
.dump'
,
i
):
...
...
circle/vm/models/instance.py
View file @
9b3516eb
...
...
@@ -523,7 +523,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
datastore
=
self
.
disks
.
all
()[
0
]
.
datastore
except
IndexError
:
from
storage.models
import
DataStore
datastore
=
DataStore
.
objects
.
get
()
datastore
=
DataStore
.
objects
.
all
()[
0
]
path
=
datastore
.
path
+
'/'
+
self
.
vm_name
+
'.dump'
return
{
'datastore'
:
datastore
,
'path'
:
path
}
...
...
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