mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 19:56:48 +00:00
The create pool modals kept their own pattern attributes, older and stricter than the server's check: a logical source took only lower-case letters, digits and "/", dir and NFS paths no "." or ":", and the NFS fields stopped at 40 characters (the form allows 100). The browser refused /dev/mapper/vg0-data, /dev/disk/by-id/wwn-..., /srv/vm.images or /export/vm_images.v2 before the server saw them. The path fields keep "required" and leave the check to the server, which shows its message.
76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
"""The storage pool target and source checks run in linear time and take
|
|
paths of letters, digits and "/", "_", ".", ":" and "-" (not first, not right
|
|
after "/"): both values go into the pool XML."""
|
|
|
|
import time
|
|
from types import SimpleNamespace
|
|
|
|
from django.template.loader import render_to_string
|
|
from django.test import SimpleTestCase
|
|
from lxml import html
|
|
from storages.forms import AddStgPool
|
|
|
|
|
|
def target_error(target):
|
|
form = AddStgPool({"name": "pool1", "stg_type": "dir", "target": target})
|
|
form.is_valid()
|
|
return "target" in form.errors
|
|
|
|
|
|
def source_error(source, stg_type="logical"):
|
|
form = AddStgPool({"name": "pool1", "stg_type": stg_type, "source": source, "target": "/mnt/pool1"})
|
|
form.is_valid()
|
|
return "source" in form.errors
|
|
|
|
|
|
class PoolTargetTests(SimpleTestCase):
|
|
def test_accepted_and_refused_targets(self):
|
|
for target, refused in (
|
|
("/var/lib/libvirt/images", False),
|
|
("/srv/iso-pool", False),
|
|
("/a//b", False),
|
|
("a-b.c_d/e", False),
|
|
("/a/-b", True), # a dash right after a slash
|
|
("-x", True),
|
|
("/a b", True),
|
|
("'/a", True), # any first character but "-" passed
|
|
("</a", True),
|
|
):
|
|
with self.subTest(target=target):
|
|
self.assertEqual(target_error(target), refused)
|
|
|
|
def test_accepted_and_refused_sources(self):
|
|
for source, refused in (
|
|
("/dev/vdb", False),
|
|
("/dev/mapper/vg0-data", False),
|
|
("/dev/disk/by-id/wwn-0x5000c500a1b2c3d4", False),
|
|
("/dev/disk/by-path/pci-0000:00:1f.2-ata-1", False),
|
|
("/export/vm_images.v2", False),
|
|
("", True),
|
|
("-x", True), # would read as an option
|
|
("/a/-b", True),
|
|
("/a b", True),
|
|
("/a'b", True), # the value goes into the pool XML
|
|
("/a<b", True),
|
|
("'/a", True),
|
|
):
|
|
for stg_type in ("logical", "netfs"):
|
|
with self.subTest(source=source, stg_type=stg_type):
|
|
self.assertEqual(source_error(source, stg_type), refused)
|
|
|
|
def test_a_crafted_target_is_checked_at_once(self):
|
|
# the previous expression backtracked exponentially: 22 dashes took over a second
|
|
start = time.monotonic()
|
|
self.assertTrue(target_error("," + "-" * 98 + "!"))
|
|
self.assertLess(time.monotonic() - start, 1)
|
|
|
|
def test_the_create_form_leaves_the_path_check_to_the_server(self):
|
|
# a stricter pattern or a shorter maxlength in the page refuses paths the form accepts
|
|
superuser = SimpleNamespace(user=SimpleNamespace(is_superuser=True))
|
|
page = html.fromstring(render_to_string("create_stg_block.html", {"request": superuser}))
|
|
fields = page.xpath("//input[@name='target' or @name='source']")
|
|
self.assertEqual(len(fields), 5)
|
|
for field in fields:
|
|
with self.subTest(form=field.xpath("string(ancestor::form//input[@name='stg_type']/@value)"), name=field.name):
|
|
self.assertIsNone(field.get("pattern"))
|
|
self.assertGreaterEqual(int(field.get("maxlength", 100)), 100)
|