mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
fix(instances): a refused XML definition comes back in its editor
On the machine step, "Or define it from XML" lost the definition when it was refused: a taken name re-rendered the page with an empty, collapsed editor; malformed XML went on to libvirt, and libvirt's refusal sent the browser back to an empty page with the reason at the top. All three refusals now render the page with the section open, the submitted XML in the editor (and in the field it posts, so it can be sent again unchanged), and the reason above the editor, which takes the focus. Fixes #174 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
575077c8ac
commit
9c6e07a467
3 changed files with 48 additions and 13 deletions
|
|
@ -48,14 +48,15 @@
|
|||
<div class="card-body">
|
||||
<h2 class="h5 card-title mb-0" id="xml-heading">
|
||||
<button class="btn p-0 border-0 text-body fw-medium w-100 text-start focus-ring disclosure" type="button" data-bs-toggle="collapse" data-bs-target="#from-xml"
|
||||
aria-expanded="false" aria-controls="from-xml">
|
||||
aria-expanded="{% if xml_error %}true{% else %}false{% endif %}" aria-controls="from-xml">
|
||||
{% bs_icon 'chevron-down' %} {% trans "Or define it from XML" %}
|
||||
</button>
|
||||
</h2>
|
||||
<div class="collapse" id="from-xml">
|
||||
<div class="collapse{% if xml_error %} show{% endif %}" id="from-xml">
|
||||
<form method="post" class="pt-3" aria-label="{% trans "Create instance from XML" %}">{% csrf_token %}
|
||||
<input type="hidden" name="dom_xml">
|
||||
<div class="xmlheight mb-3"><textarea id="editor" aria-label="{% trans "Domain XML" %}"></textarea></div>
|
||||
{% if xml_error %}<div class="alert alert-danger form-errors" role="alert">{{ xml_error }}</div>{% endif %}
|
||||
<input type="hidden" name="dom_xml" value="{{ xml }}">
|
||||
<div class="xmlheight mb-3"><textarea id="editor" aria-label="{% trans "Domain XML" %}">{{ xml }}</textarea></div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-primary" name="create_xml" onclick="showPleaseWaitDialog()">
|
||||
{% trans "Create VM" %}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import json
|
|||
import uuid
|
||||
from unittest.mock import patch
|
||||
|
||||
import libvirt
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from instances.models import Flavor
|
||||
|
|
@ -262,3 +265,32 @@ class SubmittedListsTests(TestCase):
|
|||
def test_only_a_refused_form_sends_lists(self):
|
||||
_, response = render_create_page(self)
|
||||
self.assertIsNone(self.lists(response))
|
||||
|
||||
|
||||
class XmlRefusalTests(TestCase):
|
||||
"""A refused XML definition comes back in its open editor, its reason
|
||||
beside it."""
|
||||
|
||||
def post(self, xml):
|
||||
conn = libvirt.open("test:///default")
|
||||
self.addCleanup(conn.close)
|
||||
compute, _ = Compute.objects.get_or_create(name="testdrv", hostname="localhost", login="", password="", type=4)
|
||||
self.client.force_login(get_user_model().objects.create_superuser("xml", "xml@example.com", "pw"))
|
||||
with patch("vrtManager.connection.connection_manager.get_connection", return_value=conn):
|
||||
response = self.client.post(reverse("instances:create_instance_select_type", args=[compute.id]),
|
||||
{"create_xml": "", "dom_xml": xml})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
page = html.fromstring(response.content)
|
||||
self.assertIn("show", page.get_element_by_id("from-xml").classes)
|
||||
self.assertEqual(page.xpath("//*[@id='editor']/text()"), [xml])
|
||||
self.assertEqual(page.xpath("//input[@name='dom_xml']/@value"), [xml])
|
||||
return page.xpath("//*[@id='from-xml']//*[contains(@class, 'form-errors')]")[0].text_content()
|
||||
|
||||
def test_a_taken_name(self):
|
||||
self.assertIn("already exists", self.post("<domain type='test'><name>test</name></domain>"))
|
||||
|
||||
def test_malformed_xml(self):
|
||||
self.assertIn("not well-formed", self.post("<domain type='test'><name>new</name>"))
|
||||
|
||||
def test_a_definition_libvirt_refuses(self):
|
||||
self.assertIn("libvirt", self.post("<domain type='nope'><name>new</name><memory>1024</memory></domain>"))
|
||||
|
|
|
|||
|
|
@ -1975,22 +1975,24 @@ def create_instance_select_type(request, compute_id):
|
|||
default_machine = app_settings.INSTANCE_MACHINE_DEFAULT_TYPE
|
||||
default_arch = app_settings.INSTANCE_ARCH_DEFAULT_TYPE
|
||||
|
||||
if request.method == "POST":
|
||||
if "create_xml" in request.POST:
|
||||
xml = request.POST.get("dom_xml", "")
|
||||
try:
|
||||
name = util.get_xml_path(xml, "/domain/name")
|
||||
except util.etree.Error:
|
||||
name = None
|
||||
# a refused definition comes back in the editor, its reason beside it
|
||||
xml_error = None
|
||||
if request.method == "POST" and "create_xml" in request.POST:
|
||||
xml = request.POST.get("dom_xml", "")
|
||||
try:
|
||||
name = util.get_xml_path(xml, "/domain/name")
|
||||
if name in instances:
|
||||
error_msg = _("A virtual machine with this name already exists")
|
||||
messages.error(request, error_msg)
|
||||
xml_error = _("A virtual machine with this name already exists")
|
||||
else:
|
||||
with libvirt_compute_lock(compute):
|
||||
conn._defineXML(xml)
|
||||
utils.refr(compute)
|
||||
instance = compute.instance_set.get(name=name)
|
||||
return redirect(reverse("instances:instance", args=[instance.id]))
|
||||
except util.etree.Error as err:
|
||||
xml_error = _("The XML is not well-formed: %(error)s") % {"error": err}
|
||||
except libvirtError as err:
|
||||
xml_error = _("libvirt Error - %(exception)s") % {"exception": error_text(request, err)}
|
||||
|
||||
return render(request, "create_instance_w1.html", locals())
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue