diff --git a/computes/migrations/0006_login_password_length.py b/computes/migrations/0006_login_password_length.py new file mode 100644 index 00000000..e32339ba --- /dev/null +++ b/computes/migrations/0006_login_password_length.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.18 on 2026-10-26 11:12 + +import computes.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('computes', '0005_alter_compute_hostname_alter_compute_login'), + ] + + operations = [ + migrations.AlterField( + model_name='compute', + name='login', + field=models.CharField(blank=True, max_length=32, validators=[computes.validators.validate_login], verbose_name='login'), + ), + migrations.AlterField( + model_name='compute', + name='password', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='password'), + ), + ] diff --git a/computes/models.py b/computes/models.py index 4f48941a..407f63ec 100644 --- a/computes/models.py +++ b/computes/models.py @@ -15,8 +15,8 @@ from vrtManager.storage import wvmStorages class Compute(Model): name = CharField(_("name"), max_length=64, unique=True) hostname = CharField(_("hostname"), max_length=64, validators=[validate_hostname]) - login = CharField(_("login"), max_length=20, blank=True, validators=[validate_login]) - password = CharField(_("password"), max_length=14, blank=True, null=True) + login = CharField(_("login"), max_length=32, blank=True, validators=[validate_login]) + password = CharField(_("password"), max_length=255, blank=True, null=True) details = CharField(_("details"), max_length=64, null=True, blank=True) type = IntegerField() migration_address = CharField( diff --git a/computes/templates/computes/form.html b/computes/templates/computes/form.html index 2f08c60c..b6375c95 100644 --- a/computes/templates/computes/form.html +++ b/computes/templates/computes/form.html @@ -3,12 +3,12 @@ {% load bootstrap_icons %} {% load i18n %} -{% block title %}{% trans "Add Compute" %}{% endblock %} +{% block title %}{% if form.instance.pk %}{% trans "Edit Compute" %}{% else %}{% trans "Add Compute" %}{% endif %}{% endblock %} {% block content %}
- +
diff --git a/computes/templates/computes/instances.html b/computes/templates/computes/instances.html index f4bfb2cf..0a83eee4 100644 --- a/computes/templates/computes/instances.html +++ b/computes/templates/computes/instances.html @@ -61,7 +61,6 @@ {% trans "Hypervisor doesn't have any Instances" %}
{% endif %} - {% else %}
diff --git a/computes/templates/overview.html b/computes/templates/overview.html index f3ccece8..5160d075 100644 --- a/computes/templates/overview.html +++ b/computes/templates/overview.html @@ -130,7 +130,7 @@ return span ? text + ' ({{ average_over|escapejs }} ' + span + ' s)' : text; }, }); - var memChart = lineChart(document.getElementById("memChart"), "MB", ["Usage"]); + var memChart = lineChart(document.getElementById("memChart"), "MiB", ["Usage"]); if (Boolean({{ status }}) === true) { // the CPU sample of the previous answer: usage is taken since then var cpuSample = null; diff --git a/computes/tests/test_compute_password.py b/computes/tests/test_compute_password.py index c0e7e5d6..a6ac2614 100644 --- a/computes/tests/test_compute_password.py +++ b/computes/tests/test_compute_password.py @@ -26,3 +26,8 @@ class ComputePasswordTests(TestCase): password="secret", type=conn_type) self.assertEqual(self.edit(compute, ""), "secret") self.assertEqual(self.edit(compute, "other"), "other") + + def test_a_long_password_is_kept_and_the_page_says_edit(self): + compute = Compute.objects.create(name="cp-long", hostname="192.0.2.7", login="admin", password="", type=1) + self.assertContains(self.client.get(reverse("compute_update", args=[compute.id])), "Edit Compute") + self.assertEqual(self.edit(compute, "p" * 40), "p" * 40) diff --git a/computes/tests/test_cpu_graph.py b/computes/tests/test_cpu_graph.py index f51f7d3b..bbb509f1 100644 --- a/computes/tests/test_cpu_graph.py +++ b/computes/tests/test_cpu_graph.py @@ -20,6 +20,7 @@ from lxml import html from computes.models import Compute from computes.views import CPU_SAMPLE_MAX_AGE, CPU_SAMPLE_SIGNER from vrtManager.hostdetails import cpu_percent, wvmHostDetails +from vrtManager.util import ConnectionFailed # the page script runs after the shared chart helpers CHARTS_JS = settings.BASE_DIR / "static" / "js" / "charts.js" @@ -77,7 +78,7 @@ class GraphTestCase(TestCase): self.other = Compute.objects.create(name="other", hostname="192.0.2.2", login="u", password="p", type=1) admin = get_user_model().objects.create_superuser("graph-admin", "graph@example.com", "pw") self.client.force_login(admin) - for target in ("computes.views.wvmConnect", "computes.views.wvmHostDetails"): + for target in ("computes.models.wvmHostDetails", "computes.views.wvmHostDetails"): patcher = patch(target) self.addCleanup(patcher.stop) self.conn = patcher.start().return_value # the graph reads through wvmHostDetails @@ -117,6 +118,16 @@ class GraphTestCase(TestCase): self.assertEqual((data["cpudata"], data["memdata"]["total"]), (None, None)) self.assertNotIn("cpu_sample", data) + def test_a_host_that_cannot_be_connected_to_answers_without_values(self): + down = ConnectionFailed("192.0.2.1 is down") + with patch("computes.views.wvmHostDetails", side_effect=down), patch( + "computes.models.wvmHostDetails", side_effect=down + ): + self.assertIsNone(self.graph()["cpudata"]) + for url in (reverse("machines", args=[self.compute.id, "x86_64"]), + reverse("buses", args=[self.compute.id, "x86_64", "q35", "disk"])): + self.assertEqual(json.loads(self.client.get(url).content), {}) + @unittest.skipUnless(shutil.which("node"), "node is not installed") class GraphScriptTestCase(TestCase): diff --git a/computes/tests/test_views.py b/computes/tests/test_views.py index 009dbd19..c14efcf7 100644 --- a/computes/tests/test_views.py +++ b/computes/tests/test_views.py @@ -180,7 +180,7 @@ class ComputesTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_a_disk_is_offered_the_hosts_disk_buses(self): - with patch("computes.views.wvmConnect") as conn: + with patch("computes.models.wvmHostDetails") as conn: conn.return_value.get_disk_device_types.return_value = ["disk", "cdrom", "lun"] conn.return_value.get_disk_bus_types.return_value = ["virtio", "sata", "scsi"] response = self.client.get(reverse("buses", args=[self.compute.id, "x86_64", "q35", "disk"])) diff --git a/computes/views.py b/computes/views.py index e608d069..62e246ef 100644 --- a/computes/views.py +++ b/computes/views.py @@ -12,7 +12,6 @@ from instances.models import Instance from libvirt import libvirtError from logs.audit import audit, changed_fields, snapshot from operations import claims -from vrtManager.connection import wvmConnect from vrtManager.hostdetails import wvmHostDetails from computes.forms import ( @@ -206,17 +205,11 @@ def get_compute_machine_types(request, compute_id, arch): class ComputeManager: def __init__(self, compute_id): self.compute = get_object_or_404(Compute, pk=compute_id) - self.conn = wvmConnect( - self.compute.hostname, - self.compute.login, - self.compute.password, - self.compute.type, - ) def get_machine_types(self, arch): data = dict() try: - data["machines"] = self.conn.get_machine_types(arch) + data["machines"] = self.compute.proxy.get_machine_types(arch) except libvirtError: pass @@ -225,11 +218,11 @@ class ComputeManager: def get_disk_buses(self, arch, machine, disk): data = dict() try: - disk_device_types = self.conn.get_disk_device_types(arch, machine) + disk_device_types = self.compute.proxy.get_disk_device_types(arch, machine) if disk in disk_device_types: if disk == "disk": - data["bus"] = sorted(self.conn.get_disk_bus_types(arch, machine)) + data["bus"] = sorted(self.compute.proxy.get_disk_bus_types(arch, machine)) elif disk == "cdrom": data["bus"] = ["ide", "sata", "scsi"] elif disk == "floppy": diff --git a/virtsecrets/templates/secrets.html b/virtsecrets/templates/secrets.html index 62cd0f36..6bc6be4e 100644 --- a/virtsecrets/templates/secrets.html +++ b/virtsecrets/templates/secrets.html @@ -66,7 +66,7 @@ - +
{{ secret.uuid }} {{ secret.usageType }}{{ secret.usage }}{{ secret.usage|default_if_none:"" }}
{% csrf_token %} diff --git a/virtsecrets/tests/test_secrets_page.py b/virtsecrets/tests/test_secrets_page.py index bb1e7c07..1a94f6a0 100644 --- a/virtsecrets/tests/test_secrets_page.py +++ b/virtsecrets/tests/test_secrets_page.py @@ -7,12 +7,12 @@ from computes.models import Compute from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse -from libvirt import VIR_SECRET_USAGE_TYPE_CEPH +from libvirt import VIR_SECRET_USAGE_TYPE_CEPH, VIR_SECRET_USAGE_TYPE_VTPM from lxml import html class SecretsPageTests(TestCase): - def test_the_page_holds_no_secret_value(self): + def page(self, usage_type): compute = Compute.objects.create(name="secrets-page", hostname="localhost", login="", password="", type=4) self.client.force_login(get_user_model().objects.create_superuser("page", "page@example.com", "pw")) with patch("virtsecrets.views.wvmSecrets") as conn: @@ -20,11 +20,18 @@ class SecretsPageTests(TestCase): secret = conn.return_value.get_secret.return_value secret.usageID.return_value = "client.libvirt" secret.UUIDString.return_value = "0e5b1f0a-8f3e-4c1a-9d2b-5c6d7e8f9a0b" - secret.usageType.return_value = VIR_SECRET_USAGE_TYPE_CEPH + secret.usageType.return_value = usage_type secret.value.return_value = b"cephx key" conn.return_value.get_secret_value.return_value = "Y2VwaHgga2V5" response = self.client.get(reverse("virtsecrets", args=[compute.id])) self.assertEqual(response.status_code, 200) + return response + + def test_a_vtpm_secret_is_listed(self): + self.assertContains(self.page(VIR_SECRET_USAGE_TYPE_VTPM), "vtpm") + + def test_the_page_holds_no_secret_value(self): + response = self.page(VIR_SECRET_USAGE_TYPE_CEPH) self.assertFalse(b"Y2VwaHgga2V5" in response.content, "the secret's value is on the page") field = html.fromstring(response.content).get_element_by_id("secret_value_0e5b1f0a-8f3e-4c1a-9d2b-5c6d7e8f9a0b") self.assertIsNone(field.get("value")) diff --git a/virtsecrets/views.py b/virtsecrets/views.py index e8c59395..0e2448a4 100644 --- a/virtsecrets/views.py +++ b/virtsecrets/views.py @@ -9,6 +9,7 @@ from libvirt import ( VIR_SECRET_USAGE_TYPE_NONE, VIR_SECRET_USAGE_TYPE_TLS, VIR_SECRET_USAGE_TYPE_VOLUME, + VIR_SECRET_USAGE_TYPE_VTPM, libvirtError, ) from logs.audit import audit @@ -34,6 +35,7 @@ def secrets(request, compute_id): VIR_SECRET_USAGE_TYPE_CEPH: "ceph", VIR_SECRET_USAGE_TYPE_ISCSI: "iscsi", VIR_SECRET_USAGE_TYPE_TLS: "tls", + VIR_SECRET_USAGE_TYPE_VTPM: "vtpm", } # the create dialog offers the usage types the form takes usage_choices = AddSecret.base_fields["usage_type"].choices