mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
fix(instances): internal snapshot names are checked, snapshot dates carry the year
Only external snapshots had their name checked. An internal one could be taken with an empty name: libvirt named it, the job and its log row named nothing, and a recovery looked for "" and marked the job failed though the snapshot existed. Both kinds now pass the same check when queued, and the internal form asks for a name as the external one does. Snapshot dates showed no year, so last year's snapshot looked like this year's in the list one reverts from; they now read 2026-10-13 23:04:00 under "Date (time zone)", as on the Jobs page.
This commit is contained in:
parent
fa84ce8113
commit
e29a79c76d
4 changed files with 22 additions and 10 deletions
|
|
@ -13,7 +13,7 @@ from libvirt import (VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE, VIR_DOMAIN_JOB_NONE,
|
|||
from vrtManager import util
|
||||
from vrtManager.connection import wvmConnect
|
||||
from vrtManager.create import wvmCreate
|
||||
from vrtManager.instance import wvmInstance
|
||||
from vrtManager.instance import check_snapshot_name, wvmInstance
|
||||
|
||||
from accounts.models import UserInstance, UserSSHKey
|
||||
from appsettings.settings import app_settings
|
||||
|
|
@ -305,6 +305,7 @@ class CreateSnapshot(Kind):
|
|||
"memory": bool(raw.get("memory")), "quiesce": bool(raw.get("quiesce"))}
|
||||
|
||||
def validate(self, user, params):
|
||||
check_snapshot_name(params["name"])
|
||||
# recovery counts a listed snapshot as taken by the job
|
||||
if params["name"] in _proxy(_authorized(user, params["instance"])).instance.snapshotListNames(0):
|
||||
raise util.OperationError(f"A snapshot named {params['name']} already exists")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
{% load i18n %}
|
||||
{% load tz %}
|
||||
{% get_current_timezone as TIME_ZONE %}
|
||||
{% load bootstrap_icons %}
|
||||
{% load operations %}
|
||||
<div class="tab-pane" id="snapshots" role="tabpanel">
|
||||
|
|
@ -38,7 +40,7 @@
|
|||
<form action="{% url 'instances:snapshot' instance.id %}" method="post" role="form" aria-label="Create snapshot form">
|
||||
{% csrf_token %}{% submission_key %}
|
||||
<div class="input-group mb-3">
|
||||
<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="64" pattern="[A-Za-z0-9_\-][A-Za-z0-9._\-]{0,63}" title="{% trans "Letters, digits, '.', '_' or '-'; not starting with '.'" %}" required>
|
||||
<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 or busy or held %}
|
||||
|
|
@ -90,7 +92,7 @@
|
|||
<table class="table" id="internal-snapshots">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Date" %}</th>
|
||||
<th scope="col">{% blocktrans %}Date ({{ TIME_ZONE }}){% endblocktrans %}</th>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Description" %}</th>
|
||||
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
||||
|
|
@ -99,7 +101,7 @@
|
|||
<tbody>
|
||||
{% for snap in instance.internal_snapshots %}
|
||||
<tr>
|
||||
<td>{{ snap.date|date:"M d H:i:s" }}</td>
|
||||
<td>{{ snap.date|date:"Y-m-d H:i:s" }}</td>
|
||||
<td><strong>{{ snap.name }}</strong></td>
|
||||
<td>{{ snap.description }}</td>
|
||||
<td style="width:30px;">
|
||||
|
|
@ -138,7 +140,7 @@
|
|||
<table class="table" id="external-snapshots">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Date" %}</th>
|
||||
<th scope="col">{% blocktrans %}Date ({{ TIME_ZONE }}){% endblocktrans %}</th>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Description" %}</th>
|
||||
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
||||
|
|
@ -147,7 +149,7 @@
|
|||
<tbody>
|
||||
{% for ext_snap in instance.external_snapshots %}
|
||||
<tr>
|
||||
<td>{{ ext_snap.date|date:"M d H:i:s" }}</td>
|
||||
<td>{{ ext_snap.date|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>
|
||||
<strong>{{ ext_snap.name }}</strong>
|
||||
{% if ext_snap.current %}<span class="badge text-bg-secondary">{% trans "current" %}</span>{% endif %}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,13 @@ class SnapshotJobsTestCase(TransactionTestCase):
|
|||
{"name": "a", "submission_key": "key-snap0001"})
|
||||
self.assertFalse(Operation.objects.exists())
|
||||
|
||||
def test_a_name_that_is_empty_or_not_a_snapshot_name_is_refused_on_the_page(self):
|
||||
for name in ("", ".hidden", "a b"):
|
||||
response = self.client.post(reverse("instances:snapshot", args=[self.instance.pk]),
|
||||
{"name": name, "submission_key": "key-snap0001"}, follow=True)
|
||||
self.assertContains(response, "Snapshot names are 1-64 letters")
|
||||
self.assertFalse(Operation.objects.exists())
|
||||
|
||||
def test_create_recovery(self):
|
||||
op = Operation(params={"instance": self.instance.pk, "name": "a", "external": True},
|
||||
intent={"compute": self.instance.compute_id})
|
||||
|
|
|
|||
|
|
@ -348,6 +348,11 @@ class wvmInstances(wvmConnect):
|
|||
) from error
|
||||
|
||||
|
||||
def check_snapshot_name(name):
|
||||
if not SNAPSHOT_NAME.fullmatch(name):
|
||||
raise util.OperationError("Snapshot names are 1-64 letters, digits, '.', '_' or '-' and do not start with '.'")
|
||||
|
||||
|
||||
def agent_command(dom, command, arguments=None):
|
||||
"""The answer of one guest agent command that waits AGENT_TIMEOUT seconds
|
||||
at most: libvirt's own calls for it wait for a hung agent indefinitely,
|
||||
|
|
@ -1618,10 +1623,7 @@ class wvmInstance(wvmConnect):
|
|||
accepts it only without memory, which keeps the whole state anyway).
|
||||
free_percent, free_min_gib: the free space each pool keeps (see
|
||||
_check_snapshot_space)."""
|
||||
if not SNAPSHOT_NAME.fullmatch(name):
|
||||
raise util.OperationError(
|
||||
"Snapshot names are 1-64 letters, digits, '.', '_' or '-' and do not start with '.'"
|
||||
)
|
||||
check_snapshot_name(name)
|
||||
if quiesce:
|
||||
if memory:
|
||||
raise util.OperationError("A snapshot with memory is not quiesced; it keeps the whole guest state")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue