mirror of
https://github.com/retspen/webvirtcloud
synced 2024-12-24 23:25:24 +00:00
Merge pull request #581 from catborise/master
external snapshot & add filter for logs & fixes
This commit is contained in:
commit
58af536dbf
9 changed files with 295 additions and 56 deletions
|
@ -173,6 +173,10 @@ class Instance(models.Model):
|
||||||
def snapshots(self):
|
def snapshots(self):
|
||||||
return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"])
|
return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"])
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def external_snapshots(self):
|
||||||
|
return sorted(self.proxy.get_external_snapshots(), reverse=True, key=lambda k: k["date"])
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def inst_xml(self):
|
def inst_xml(self):
|
||||||
return self.proxy._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
return self.proxy._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||||
|
|
|
@ -25,10 +25,12 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
{% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser or 'instances.view_instances' in perms %}
|
{% if 'instances.view_instances' in perms %}
|
||||||
{% include 'allinstances_index_grouped.html' %}
|
{% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser %}
|
||||||
{% else %}
|
{% include 'allinstances_index_grouped.html' %}
|
||||||
{% include 'allinstances_index_nongrouped.html' %}
|
{% else %}
|
||||||
|
{% include 'allinstances_index_nongrouped.html' %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -679,7 +679,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (~$.inArray(hash, ['#resize', "resizevm_cpu", "resizevm_mem", "resizevm_disk"])) {
|
if (~$.inArray(hash, ['#resize', "#resizevm_cpu", "#resizevm_mem", "#resizevm_disk"])) {
|
||||||
var btnsect = $('#navbtn>li>a');
|
var btnsect = $('#navbtn>li>a');
|
||||||
$(btnsect).each(function () {
|
$(btnsect).each(function () {
|
||||||
if ($(this).attr('href') === '#resize') {
|
if ($(this).attr('href') === '#resize') {
|
||||||
|
@ -702,7 +702,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (~$.inArray(hash, ['#takesnapshot', '#managesnapshot'])) {
|
if (~$.inArray(hash, ['#takesnapshot', "#takeextsnapshot", "#managesnapshot"])) {
|
||||||
var btnsect = $('#navbtn>li>a');
|
var btnsect = $('#navbtn>li>a');
|
||||||
$(btnsect).each(function () {
|
$(btnsect).each(function () {
|
||||||
if ($(this).attr('href') === '#snapshots') {
|
if ($(this).attr('href') === '#snapshots') {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{% icon 'clone' %}
|
{% icon 'clone' %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweron' instance.id %}" title="{% trans "Power On" %}">
|
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweron' instance.id %}" onclick="showPleaseWaitDialog()" title="{% trans "Power On" %}">
|
||||||
{% icon 'play' %}
|
{% icon 'play' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if instance.proxy.instance.info.0 == 1 %}
|
{% if instance.proxy.instance.info.0 == 1 %}
|
||||||
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power On" %}">{% icon 'play' %}</a>
|
<a class="btn btn-sm btn-secondary disabled" onclick="showPleaseWaitDialog()" title="{% trans "Power On" %}">{% icon 'play' %}</a>
|
||||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:suspend' instance.id %}"
|
<a class="btn btn-sm btn-secondary" href="{% url 'instances:suspend' instance.id %}"
|
||||||
title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
|
title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
|
||||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweroff' instance.id %}">{% icon 'power-off' %}</a>
|
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweroff' instance.id %}">{% icon 'power-off' %}</a>
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane tab-pane-bordered" id="powerforce" role="tabpanel" aria-labelledby="powerforce-tab">
|
<div class="tab-pane tab-pane-bordered" id="powerforce" role="tabpanel" aria-labelledby="powerforce-tab">
|
||||||
<p>{% trans "This action forcibly powers off the instance and may cause data corruption." %}</p>
|
<p>{% trans "This action forcibly powers off the instance and may cause data corruption." %}</p>
|
||||||
<form action="{% url 'instances:force_off' instance.id %}" method="post" role="form" aria-label="Force to shotdown instance form">
|
<form action="{% url 'instances:force_off' instance.id %}" method="post" role="form" aria-label="Force to shutdown instance form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="submit" name="powerforce" class="btn btn-lg btn-success float-end" value="{% trans "Force Off" %}">
|
<input type="submit" name="powerforce" class="btn btn-lg btn-success float-end" value="{% trans "Force Off" %}">
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
<p>{% trans "Template instance cannot be started." %}</p>
|
<p>{% trans "Template instance cannot be started." %}</p>
|
||||||
<input type="submit" name="poweron" class="btn btn-lg btn-success float-end disabled" value="{% trans "Power On" %}">
|
<input type="submit" name="poweron" class="btn btn-lg btn-success float-end disabled" value="{% trans "Power On" %}">
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="submit" name="poweron" class="btn btn-lg btn-success float-end" value="{% trans "Power On" %}">
|
<input type="submit" name="poweron" class="btn btn-lg btn-success float-end" onclick="showPleaseWaitDialog()" value="{% trans "Power On" %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|
140
instances/templates/instances/snapshots_tab.html
Normal file → Executable file
140
instances/templates/instances/snapshots_tab.html
Normal file → Executable file
|
@ -5,7 +5,12 @@
|
||||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance snapshot menu">
|
<ul class="nav nav-tabs" role="tablist" aria-label="Instance snapshot menu">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#takesnapshot" type="button" role="tab" aria-controls="takesnapshot" aria-selected="true">
|
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#takesnapshot" type="button" role="tab" aria-controls="takesnapshot" aria-selected="true">
|
||||||
{% trans "Take Snapshot" %}
|
{% trans "Take Snapshot - Internal" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#takeextsnapshot" type="button" role="tab" aria-controls="externalSnapshot" aria-selected="false">
|
||||||
|
{% trans "Take Snapshot - External" %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
|
@ -29,60 +34,117 @@
|
||||||
<input type="text" class="form-control form-control-lg" name="name" placeholder="{% trans "Snapshot Name" %}" maxlength="14">
|
<input type="text" class="form-control form-control-lg" name="name" placeholder="{% trans "Snapshot Name" %}" maxlength="14">
|
||||||
<span class="input-group-text">|</span>
|
<span class="input-group-text">|</span>
|
||||||
<input type="text" class="form-control form-control-lg" name="description" placeholder="{% trans "Snapshot Description" %}" maxlength="45">
|
<input type="text" class="form-control form-control-lg" name="description" placeholder="{% trans "Snapshot Description" %}" maxlength="45">
|
||||||
<input type="submit" class="btn btn-lg btn-success float-end" name="snapshot" value="{% trans "Take Snapshot" %}" onclick="showPleaseWaitDialog();">
|
{% if instance.external_snapshots|length > 0 %}
|
||||||
|
<input type="submit" class="btn btn-lg btn-success disabled float-end" name="snapshot" value="{% trans "Take Snapshot" %}">
|
||||||
|
{% else %}
|
||||||
|
<input type="submit" class="btn btn-lg btn-success float-end" name="snapshot" value="{% trans "Take Snapshot" %}" onclick="showPleaseWaitDialog();">
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div> <!-- Tab pane - takesnapshot -->
|
||||||
|
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="takeextsnapshot">
|
||||||
|
{% if instance.status != 5 %}
|
||||||
|
<p>{% trans "You can get external snapshots within this tab." %}</p>
|
||||||
|
<p class="text-primary">{% trans "External snapshots are experimental in this stage, use it if you know what you are doing. It may require manual intervention." %}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans "Create an external snapshot" %}</p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-danger">{% trans "Give your External Snapshot a <b>distinctive description</b> so it wouldn't get mixed with other snapshots." %}</p>
|
||||||
|
<form action="{% url 'instances:create_external_snapshot' instance.id %}" method="post" role="form" aria-label="Create snapshot form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" class="form-control form-control-lg" name="name" placeholder="{% trans "Snapshot Name" %}" maxlength="14">
|
||||||
|
<span class="input-group-text">|</span>
|
||||||
|
<input type="text" class="form-control form-control-lg" name="description" placeholder="{% trans "Snapshot Description" %}" maxlength="45">
|
||||||
|
{% if instance.external_snapshots|length > 0 or instance.snapshots|length > 0 %}
|
||||||
|
<input type="submit" class="btn btn-lg btn-success disabled float-end" name="snapshot" value="{% trans "Take Snapshot" %}">
|
||||||
|
{% else %}
|
||||||
|
<input type="submit" class="btn btn-lg btn-success float-end" name="snapshot" value="{% trans "Take Snapshot" %}" onclick="showPleaseWaitDialog();">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<p class="text-danger">{% trans "WebVirtCloud supports only one external snapshot at the moment." %}</p>
|
||||||
|
</form>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div> <!--tab pane takeextsnapshot-->
|
||||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="managesnapshot">
|
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="managesnapshot">
|
||||||
{% if instance.snapshots %}
|
{% if instance.snapshots or instance.external_snapshots %}
|
||||||
<p>{% trans "Choose a snapshot for restore/delete" %}</p>
|
<p>{% trans "Choose a snapshot for restore/delete" %}</p>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<th scope="col">{% trans "Date" %}</th>
|
<th scope="col">{% trans "Date" %}</th>
|
||||||
<th scope="col">{% trans "Name" %}</th>
|
<th scope="col">{% trans "Name" %}</th>
|
||||||
<th scope="col">{% trans "Description" %}</th>
|
<th scope="col">{% trans "Type - Description" %}</th>
|
||||||
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for snap in instance.snapshots %}
|
{% if instance.snapshots %}
|
||||||
<tr>
|
{% for snap in instance.snapshots %}
|
||||||
<td>{{ snap.date|date:"M d H:i:s" }}</td>
|
<tr>
|
||||||
<td><strong>{{ snap.name }}</strong></td>
|
<td>{{ snap.date|date:"M d H:i:s" }}</td>
|
||||||
<td>{{ snap.description }}</td>
|
<td><strong>{{ snap.name }}</strong></td>
|
||||||
<td style="width:30px;">
|
<td>({% trans "Internal" %}) - {{ snap.description }}</td>
|
||||||
<form action="{% url 'instances:revert_snapshot' instance.id %}" method="post" role="form" aria-label="Restore snapshot form">
|
<td style="width:30px;">
|
||||||
{% csrf_token %}
|
<form action="{% url 'instances:revert_snapshot' instance.id %}" method="post" role="form" aria-label="Restore snapshot form">
|
||||||
<input type="hidden" name="name" value="{{ snap.name }}">
|
{% csrf_token %}
|
||||||
{% if instance.status == 5 %}
|
<input type="hidden" name="name" value="{{ snap.name }}">
|
||||||
<button type="submit" class="btn btn-sm btn-secondary" name="revert_snapshot" title="{% trans 'Revert to this Snapshot' %}" onclick="return confirm('Are you sure?')">
|
{% if instance.status == 5 %}
|
||||||
<span class="fa fa-download"></span>
|
<button type="submit" class="btn btn-sm btn-primary" name="revert_snapshot" title="{% trans 'Revert to this Snapshot' %}" onclick="return confirm('Are you sure?')">
|
||||||
</button>
|
<span class="fa fa-download"></span>
|
||||||
{% else %}
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary disabled"
|
{% else %}
|
||||||
title="{% trans "To restore snapshots you need Power Off the instance." %}">
|
<button type="button" class="btn btn-sm btn-primary disabled"
|
||||||
<span class="fa fa-download"></span>
|
title="{% trans "To restore snapshots you need Power Off the instance." %}">
|
||||||
</button>
|
<span class="fa fa-download"></span>
|
||||||
{% endif %}
|
</button>
|
||||||
</form>
|
{% endif %}
|
||||||
</td>
|
</form>
|
||||||
<td style="width:30px;">
|
</td>
|
||||||
<form action="{% url 'instances:delete_snapshot' instance.id %}" method="post" role="form" aria-label="Delete snapshot form">{% csrf_token %}
|
<td style="width:30px;">
|
||||||
<input type="hidden" name="name" value="{{ snap.name }}">
|
<form action="{% url 'instances:delete_snapshot' instance.id %}" method="post" role="form" aria-label="Delete snapshot form">{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-sm btn-danger" title="{% trans 'Delete Snapshot' %}" onclick="return confirm('{% trans "Are you sure?" %}')">
|
<input type="hidden" name="name" value="{{ snap.name }}">
|
||||||
{% icon 'trash' %}
|
<button type="submit" class="btn btn-sm btn-danger" title="{% trans 'Delete Snapshot' %}" onclick="return confirm('{% trans "Are you sure?" %}')">
|
||||||
</button>
|
{% icon 'trash' %}
|
||||||
</form>
|
</button>
|
||||||
</td>
|
</form>
|
||||||
</tr>
|
</td>
|
||||||
{% endfor %}
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% elif instance.external_snapshots %}
|
||||||
|
{% for ext_snap in instance.external_snapshots %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ ext_snap.date|date:"M d H:i:s" }}</td>
|
||||||
|
<td><strong>{{ ext_snap.name }}</strong></td>
|
||||||
|
<td>({% trans "External" %}) - {{ ext_snap.description }}</td>
|
||||||
|
<td style="width:30px;">
|
||||||
|
<form action="{% url 'instances:revert_external_snapshot' instance.id %}" method="post" role="form" aria-label="Restore external snapshot form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="name" value="{{ ext_snap.name }}">
|
||||||
|
<input type="hidden" name="date" value="{{ ext_snap.date }}">
|
||||||
|
<input type="hidden" name="desc" value="{{ ext_snap.description }}">
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary" name="revert_external_snapshot" title="{% trans 'Revert to this Snapshot' %}" onclick="return confirm('You are going to lose your unsaved work by reverting to this snapshot state. Are you sure?')">
|
||||||
|
<span class="fa fa-download"></span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
<td style="width:30px;">
|
||||||
|
<form action="{% url 'instances:delete_external_snapshot' instance.id %}" method="post" role="form" aria-label="Delete external snapshot form">{% csrf_token %}
|
||||||
|
<input type="hidden" name="name" value="{{ ext_snap.name }}">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" name="delete_external_snapshot" title="{% trans 'Delete Snapshot' %}" onclick="return confirm('You are about to delete this snapshot and merge it with base image. Are you sure?')">
|
||||||
|
{% icon 'trash' %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans "You do not have any snapshots" %}</p>
|
<p>{% trans "You do not have any snapshots" %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div> <!--tab pane managesnapshot-->
|
||||||
</div>
|
</div> <!-- Tab content-->
|
||||||
</div>
|
</div> <!-- Tab panel-->
|
||||||
|
|
3
instances/urls.py
Normal file → Executable file
3
instances/urls.py
Normal file → Executable file
|
@ -39,6 +39,9 @@ urlpatterns = [
|
||||||
path("<int:pk>/snapshot/", views.snapshot, name="snapshot"),
|
path("<int:pk>/snapshot/", views.snapshot, name="snapshot"),
|
||||||
path("<int:pk>/delete_snapshot/", views.delete_snapshot, name="delete_snapshot"),
|
path("<int:pk>/delete_snapshot/", views.delete_snapshot, name="delete_snapshot"),
|
||||||
path("<int:pk>/revert_snapshot/", views.revert_snapshot, name="revert_snapshot"),
|
path("<int:pk>/revert_snapshot/", views.revert_snapshot, name="revert_snapshot"),
|
||||||
|
path("<int:pk>/create_external_snapshot/", views.create_external_snapshot, name="create_external_snapshot"),
|
||||||
|
path("<int:pk>/revert_external_snapshot/", views.revert_external_snapshot, name="revert_external_snapshot"),
|
||||||
|
path("<int:pk>/delete_external_snapshot/", views.delete_external_snapshot, name="delete_external_snapshot"),
|
||||||
path("<int:pk>/set_vcpu/", views.set_vcpu, name="set_vcpu"),
|
path("<int:pk>/set_vcpu/", views.set_vcpu, name="set_vcpu"),
|
||||||
path("<int:pk>/set_vcpu_hotplug/", views.set_vcpu_hotplug, name="set_vcpu_hotplug"),
|
path("<int:pk>/set_vcpu_hotplug/", views.set_vcpu_hotplug, name="set_vcpu_hotplug"),
|
||||||
path("<int:pk>/set_autostart/", views.set_autostart, name="set_autostart"),
|
path("<int:pk>/set_autostart/", views.set_autostart, name="set_autostart"),
|
||||||
|
|
81
instances/views.py
Normal file → Executable file
81
instances/views.py
Normal file → Executable file
|
@ -20,7 +20,9 @@ from django.http import Http404, HttpResponse, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from libvirt import (VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_UNDEFINE_NVRAM,
|
from libvirt import (VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,
|
||||||
|
VIR_DOMAIN_UNDEFINE_NVRAM,
|
||||||
|
VIR_DOMAIN_START_PAUSED,
|
||||||
libvirtError)
|
libvirtError)
|
||||||
from logs.views import addlogmsg
|
from logs.views import addlogmsg
|
||||||
from vrtManager import util
|
from vrtManager import util
|
||||||
|
@ -146,7 +148,7 @@ def instance(request, pk):
|
||||||
instance.drbd = drbd_status(request, pk)
|
instance.drbd = drbd_status(request, pk)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
return render(request, "instance.html", locals())
|
return render(request, "instance.html", locals(),)
|
||||||
|
|
||||||
|
|
||||||
def status(request, pk):
|
def status(request, pk):
|
||||||
|
@ -1015,6 +1017,81 @@ def revert_snapshot(request, pk):
|
||||||
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
|
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
|
||||||
|
|
||||||
|
|
||||||
|
def create_external_snapshot(request, pk):
|
||||||
|
instance = get_instance(request.user, pk)
|
||||||
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
|
if allow_admin_or_not_template and request.user.has_perm(
|
||||||
|
"instances.snapshot_instances"
|
||||||
|
):
|
||||||
|
name = request.POST.get("name", "")
|
||||||
|
desc = request.POST.get("description", "")
|
||||||
|
instance.proxy.create_external_snapshot("s1." + name, instance, desc=desc)
|
||||||
|
msg = _("Create external snapshot: %(snap)s") % {"snap": name}
|
||||||
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
|
||||||
|
|
||||||
|
|
||||||
|
def get_external_snapshots(request, pk):
|
||||||
|
instance = get_instance(request.user, pk)
|
||||||
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
|
if allow_admin_or_not_template and request.user.has_perm(
|
||||||
|
"instances.snapshot_instances"
|
||||||
|
):
|
||||||
|
external_snapshots = instance.proxy.get_external_snapshots()
|
||||||
|
return external_snapshots
|
||||||
|
|
||||||
|
|
||||||
|
def revert_external_snapshot(request, pk):
|
||||||
|
instance = get_instance(request.user, pk)
|
||||||
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
|
if allow_admin_or_not_template and request.user.has_perm(
|
||||||
|
"instances.snapshot_instances"
|
||||||
|
):
|
||||||
|
instance_state = True if instance.proxy.get_status() != 5 else False
|
||||||
|
name = request.POST.get("name", "")
|
||||||
|
date = request.POST.get("date", "")
|
||||||
|
desc = request.POST.get("desc", "")
|
||||||
|
instance.proxy.force_shutdown() if instance_state else None
|
||||||
|
instance.proxy.revert_external_snapshot(name, date, desc)
|
||||||
|
instance.proxy.start() if instance_state else None
|
||||||
|
msg = _("Revert external snapshot: %(snap)s") % {"snap": name}
|
||||||
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
|
||||||
|
|
||||||
|
|
||||||
|
def delete_external_snapshot(request, pk):
|
||||||
|
instance = get_instance(request.user, pk)
|
||||||
|
instance_state = True if instance.proxy.get_status() == 5 else False
|
||||||
|
allow_admin_or_not_template = (
|
||||||
|
request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
|
)
|
||||||
|
|
||||||
|
if allow_admin_or_not_template and request.user.has_perm(
|
||||||
|
"instances.snapshot_instances"
|
||||||
|
):
|
||||||
|
name = request.POST.get("name", "")
|
||||||
|
|
||||||
|
instance.proxy.start(VIR_DOMAIN_START_PAUSED) if instance_state else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance.proxy.delete_external_snapshot(name)
|
||||||
|
msg = _("Delete external snapshot: %(snap)s") % {"snap": name}
|
||||||
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
finally:
|
||||||
|
instance.proxy.force_shutdown() if instance_state else None
|
||||||
|
|
||||||
|
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def set_vcpu(request, pk):
|
def set_vcpu(request, pk):
|
||||||
instance = get_instance(request.user, pk)
|
instance = get_instance(request.user, pk)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import contextlib
|
||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from libvirt import (
|
from libvirt import (
|
||||||
|
@ -18,6 +19,14 @@ try:
|
||||||
VIR_MIGRATE_POSTCOPY,
|
VIR_MIGRATE_POSTCOPY,
|
||||||
VIR_MIGRATE_UNDEFINE_SOURCE,
|
VIR_MIGRATE_UNDEFINE_SOURCE,
|
||||||
VIR_MIGRATE_UNSAFE,
|
VIR_MIGRATE_UNSAFE,
|
||||||
|
VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY,
|
||||||
|
VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY,
|
||||||
|
VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL,
|
||||||
|
VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL,
|
||||||
|
VIR_DOMAIN_BLOCK_COMMIT_DELETE,
|
||||||
|
VIR_DOMAIN_BLOCK_COMMIT_ACTIVE,
|
||||||
|
VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT,
|
||||||
|
VIR_DOMAIN_START_PAUSED,
|
||||||
libvirtError,
|
libvirtError,
|
||||||
)
|
)
|
||||||
from libvirt_qemu import VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, qemuAgentCommand
|
from libvirt_qemu import VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, qemuAgentCommand
|
||||||
|
@ -34,7 +43,6 @@ from vrtManager import util
|
||||||
from vrtManager.connection import wvmConnect
|
from vrtManager.connection import wvmConnect
|
||||||
from vrtManager.storage import wvmStorage, wvmStorages
|
from vrtManager.storage import wvmStorage, wvmStorages
|
||||||
|
|
||||||
|
|
||||||
class wvmInstances(wvmConnect):
|
class wvmInstances(wvmConnect):
|
||||||
def get_instance_status(self, name):
|
def get_instance_status(self, name):
|
||||||
inst = self.get_instance(name)
|
inst = self.get_instance(name)
|
||||||
|
@ -202,8 +210,8 @@ class wvmInstance(wvmConnect):
|
||||||
|
|
||||||
return info_results
|
return info_results
|
||||||
|
|
||||||
def start(self):
|
def start(self, flags=0):
|
||||||
self.instance.create()
|
self.instance.createWithFlags(flags)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.instance.shutdown()
|
self.instance.shutdown()
|
||||||
|
@ -449,6 +457,7 @@ class wvmInstance(wvmConnect):
|
||||||
disk_format = used_size = disk_size = None
|
disk_format = used_size = disk_size = None
|
||||||
disk_cache = disk_io = disk_discard = disk_zeroes = "default"
|
disk_cache = disk_io = disk_discard = disk_zeroes = "default"
|
||||||
readonly = shareable = serial = None
|
readonly = shareable = serial = None
|
||||||
|
backing_file = None
|
||||||
|
|
||||||
device = disk.xpath("@device")[0]
|
device = disk.xpath("@device")[0]
|
||||||
if device == "disk":
|
if device == "disk":
|
||||||
|
@ -480,6 +489,9 @@ class wvmInstance(wvmConnect):
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
disk_zeroes = disk.xpath("driver/@detect_zeroes")[0]
|
disk_zeroes = disk.xpath("driver/@detect_zeroes")[0]
|
||||||
|
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
backing_file = disk.xpath("backingStore/source/@file")[0]
|
||||||
|
|
||||||
readonly = bool(disk.xpath("readonly"))
|
readonly = bool(disk.xpath("readonly"))
|
||||||
shareable = bool(disk.xpath("shareable"))
|
shareable = bool(disk.xpath("shareable"))
|
||||||
serial = (
|
serial = (
|
||||||
|
@ -509,6 +521,7 @@ class wvmInstance(wvmConnect):
|
||||||
"storage": storage,
|
"storage": storage,
|
||||||
"path": src_file,
|
"path": src_file,
|
||||||
"format": disk_format,
|
"format": disk_format,
|
||||||
|
"backing_file": backing_file,
|
||||||
"size": disk_size,
|
"size": disk_size,
|
||||||
"used": used_size,
|
"used": used_size,
|
||||||
"cache": disk_cache,
|
"cache": disk_cache,
|
||||||
|
@ -1283,9 +1296,87 @@ class wvmInstance(wvmConnect):
|
||||||
)
|
)
|
||||||
self._defineXML(xml_temp)
|
self._defineXML(xml_temp)
|
||||||
|
|
||||||
def get_snapshot(self):
|
def create_external_snapshot(self, name, date=None, desc=None):
|
||||||
|
creation_time = time.time()
|
||||||
|
state = "shutoff" if self.get_status() == 5 else "running"
|
||||||
|
#<seclabel type='none' model='dac' relabel='no'/>
|
||||||
|
xml = """<domainsnapshot>
|
||||||
|
<name>%s</name>
|
||||||
|
<description>%s</description>
|
||||||
|
<state>%s</state>
|
||||||
|
<creationTime>%d</creationTime>
|
||||||
|
""" % (
|
||||||
|
name,
|
||||||
|
desc,
|
||||||
|
state,
|
||||||
|
creation_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.change_snapshot_xml()
|
||||||
|
xml += self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||||
|
xml += """<active>0</active>
|
||||||
|
</domainsnapshot>"""
|
||||||
|
|
||||||
|
self._snapshotCreateXML(xml, VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY)
|
||||||
|
|
||||||
|
|
||||||
|
def get_external_snapshots(self):
|
||||||
|
return self.get_snapshot(VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL)
|
||||||
|
|
||||||
|
def delete_external_snapshot(self, name):
|
||||||
|
disk_info = self.get_disk_devices()
|
||||||
|
for disk in disk_info:
|
||||||
|
target_dev = disk["dev"]
|
||||||
|
backing_file = disk["backing_file"]
|
||||||
|
snap_source_file = disk["path"]
|
||||||
|
self.instance.blockCommit(target_dev, backing_file, snap_source_file,
|
||||||
|
flags=VIR_DOMAIN_BLOCK_COMMIT_DELETE|
|
||||||
|
VIR_DOMAIN_BLOCK_COMMIT_ACTIVE)
|
||||||
|
while True:
|
||||||
|
info = self.instance.blockJobInfo(target_dev, 0)
|
||||||
|
if info.get('cur') == info.get('end'):
|
||||||
|
self.instance.blockJobAbort(target_dev,flags=VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT)
|
||||||
|
time.sleep(2)
|
||||||
|
break
|
||||||
|
# Check again pool for snapshot delta volume; if it exist, remove it manually
|
||||||
|
with contextlib.suppress(libvirtError):
|
||||||
|
vol_snap = self.get_volume_by_path(snap_source_file)
|
||||||
|
pool = vol_snap.storagePoolLookupByVolume()
|
||||||
|
pool.refresh(0)
|
||||||
|
vol_snap.delete(0)
|
||||||
|
|
||||||
|
snap = self.instance.snapshotLookupByName(name, 0)
|
||||||
|
snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)
|
||||||
|
|
||||||
|
|
||||||
|
def revert_external_snapshot(self, name, date, desc):
|
||||||
|
pool = None
|
||||||
|
snap = self.instance.snapshotLookupByName(name, 0)
|
||||||
|
snap_xml = snap.getXMLDesc(0)
|
||||||
|
snapXML = ElementTree.fromstring(snap_xml)
|
||||||
|
disks = snapXML.findall('inactiveDomain/devices/disk')
|
||||||
|
if not disks: disks = snapXML.findall('domain/devices/disk')
|
||||||
|
|
||||||
|
self.start(flags=VIR_DOMAIN_START_PAUSED) if self.get_status() == 5 else None
|
||||||
|
|
||||||
|
disk_info = self.get_disk_devices()
|
||||||
|
for disk in disk_info:
|
||||||
|
vol_snap = self.get_volume_by_path(disk["path"])
|
||||||
|
pool = vol_snap.storagePoolLookupByVolume()
|
||||||
|
pool.refresh(0)
|
||||||
|
vol_snap.delete(0)
|
||||||
|
self.force_shutdown()
|
||||||
|
|
||||||
|
snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)
|
||||||
|
for disk in disks:
|
||||||
|
self.instance.updateDeviceFlags(ElementTree.tostring(disk).decode("UTF-8"))
|
||||||
|
name = name.replace("s1", "s2")
|
||||||
|
self.create_external_snapshot(name, date, desc)
|
||||||
|
pool.refresh() if pool else None
|
||||||
|
|
||||||
|
def get_snapshot(self, flag=VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL):
|
||||||
snapshots = []
|
snapshots = []
|
||||||
snapshot_list = self.instance.snapshotListNames(0)
|
snapshot_list = self.instance.snapshotListNames(flag)
|
||||||
for snapshot in snapshot_list:
|
for snapshot in snapshot_list:
|
||||||
snap = self.instance.snapshotLookupByName(snapshot, 0)
|
snap = self.instance.snapshotLookupByName(snapshot, 0)
|
||||||
snap_description = util.get_xml_path(
|
snap_description = util.get_xml_path(
|
||||||
|
|
Loading…
Reference in a new issue