mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 19:56:48 +00:00
feat(nodedevs): the host's PCI and USB devices on a Devices page
- A Devices section of each host lists its PCI devices by IOMMU group and its USB devices: what each is (vendor, product, IDs, class, driver), what the host itself has on it (its network interfaces, disks and graphics card, anywhere below it) and the VMs whose definitions name it. - A VM names a PCI device by its address, a USB device by its address or by its model only; a VM that names a model only is shown on every device of that model, which one it takes is known only when it starts. - A host without IOMMU groups says that no PCI device can be given to a VM and how to turn the IOMMU on. - One listing of the host's node devices, one XML read each (a device gone meanwhile is left out), and the VMs' definitions read once; nothing is shown when they cannot be read, as a device would look free. - Read only: giving devices to VMs comes in the next layers.
This commit is contained in:
parent
f745033d29
commit
0977aef587
15 changed files with 538 additions and 1 deletions
0
nodedevs/__init__.py
Normal file
0
nodedevs/__init__.py
Normal file
5
nodedevs/apps.py
Normal file
5
nodedevs/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class NodedevsConfig(AppConfig):
|
||||
name = "nodedevs"
|
||||
3
nodedevs/templates/nodedev_resources.html
Normal file
3
nodedevs/templates/nodedev_resources.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{% load i18n %}
|
||||
{# what the host has on a device (its network interfaces, disks, graphics card): giving it to a VM takes them from the host #}
|
||||
{% for resource in device.resources %}<span class="badge bg-warning-subtle text-warning-emphasis font-monospace me-1">{{ resource }}</span>{% empty %}–{% endfor %}
|
||||
2
nodedevs/templates/nodedev_users.html
Normal file
2
nodedevs/templates/nodedev_users.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
{# the VMs whose definitions name a device, linked when the panel has them #}
|
||||
{% for vm in device.vms %}{% if vm.id %}<a href="{% url 'instances:instance' vm.id %}">{{ vm.name }}</a>{% else %}{{ vm.name }}{% endif %}{% if not forloop.last %}, {% endif %}{% empty %}–{% endfor %}
|
||||
117
nodedevs/templates/nodedevs.html
Normal file
117
nodedevs/templates/nodedevs.html
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap_icons %}
|
||||
|
||||
{% block title %}{% trans "Devices" %} - {{ compute.name }}{% endblock %}
|
||||
|
||||
{% block page_heading %}{{ compute.name }} - {% trans "Devices" %}{% endblock page_heading %}
|
||||
|
||||
{% block content %}
|
||||
{% include "computes/host_nav.html" with section="nodedevs" %}
|
||||
|
||||
{% if devices_read %}
|
||||
{% if pci_count and not iommu %}
|
||||
<div class="alert alert-warning shadow-sm">
|
||||
{% bs_icon 'exclamation-triangle' %}
|
||||
{% blocktrans %}No IOMMU groups detected: a PCI device cannot be given to a VM. The host needs its IOMMU (Intel VT-d, AMD-Vi) turned on in the firmware and the kernel; on Intel, boot it with intel_iommu=on.{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<input id="filter" class="form-control form-control-sm w-auto" type="search" placeholder="{% trans "Search" %}" aria-label="{% trans "Search devices" %}">
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">{% trans "PCI devices" %} <span class="badge text-bg-light border">{{ pci_count }}</span></div>
|
||||
{% if pci_count %}
|
||||
<div class="table-responsive position-relative">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Address" %}</th>
|
||||
<th scope="col">{% trans "Device" %}</th>
|
||||
<th scope="col">{% trans "Type" %}</th>
|
||||
<th scope="col">{% trans "Driver" %}</th>
|
||||
<th scope="col">{% trans "Host resources" %}</th>
|
||||
<th scope="col">{% trans "Used by" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for group, devices in groups %}
|
||||
<tbody class="searchable">
|
||||
<tr data-group-header class="table-light">
|
||||
<th colspan="6" scope="rowgroup" class="small">
|
||||
{% if group is None %}{% trans "Without an IOMMU group" %}{% else %}{% blocktrans %}IOMMU group {{ group }}{% endblocktrans %}{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% for device in devices %}
|
||||
<tr data-search="{{ device.address }} {{ device.ids }} {{ device.vendor }} {{ device.product }} {{ device.kind }} {{ device.driver }}{% for resource in device.resources %} {{ resource }}{% endfor %}{% for vm in device.vms %} {{ vm.name }}{% endfor %}">
|
||||
<td class="font-monospace text-nowrap">{{ device.address }}</td>
|
||||
<td>
|
||||
<div>{{ device.product }}</div>
|
||||
<div class="small text-body-secondary">{{ device.vendor }} <span class="font-monospace">{{ device.ids }}</span></div>
|
||||
{% if device.physfn %}<div class="small text-body-secondary">{% blocktrans with address=device.physfn %}Virtual function of {{ address }}{% endblocktrans %}</div>{% endif %}
|
||||
{% if device.vfs %}<div class="small text-body-secondary">{% blocktrans count counter=device.vfs %}{{ counter }} virtual function{% plural %}{{ counter }} virtual functions{% endblocktrans %}</div>{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap">{{ device.kind }}</td>
|
||||
<td class="font-monospace">{{ device.driver|default:"–" }}</td>
|
||||
<td>{% include "nodedev_resources.html" %}</td>
|
||||
<td class="text-nowrap">{% include "nodedev_users.html" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card-body small text-body-secondary">{% trans "The host reports no PCI devices." %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">{% trans "USB devices" %} <span class="badge text-bg-light border">{{ usb|length }}</span></div>
|
||||
{% if usb %}
|
||||
<div class="table-responsive position-relative">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Bus / device" %}</th>
|
||||
<th scope="col">{% trans "Device" %}</th>
|
||||
<th scope="col">{% trans "Type" %}</th>
|
||||
<th scope="col">{% trans "Host resources" %}</th>
|
||||
<th scope="col">{% trans "Used by" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
{% for device in usb %}
|
||||
<tr data-search="{{ device.bus }}/{{ device.device }} {{ device.ids }} {{ device.vendor }} {{ device.product }}{% for kind in device.kinds %} {{ kind }}{% endfor %}{% for resource in device.resources %} {{ resource }}{% endfor %}{% for vm in device.vms %} {{ vm.name }}{% endfor %}{% for vm in device.by_model %} {{ vm }}{% endfor %}">
|
||||
<td class="font-monospace text-nowrap">{{ device.bus }} / {{ device.device }}</td>
|
||||
<td>
|
||||
<div>{{ device.product }}</div>
|
||||
<div class="small text-body-secondary">{{ device.vendor }} <span class="font-monospace">{{ device.ids }}</span></div>
|
||||
{% if device.same_model %}<div class="small text-body-secondary">{% trans "One of several of this model: a VM that names the model alone may take any of them." %}</div>{% endif %}
|
||||
</td>
|
||||
<td>{{ device.kinds|join:", "|default:"–" }}</td>
|
||||
<td>{% include "nodedev_resources.html" %}</td>
|
||||
<td>
|
||||
{% if device.vms or not device.by_model %}{% include "nodedev_users.html" %}{% endif %}
|
||||
{% if device.by_model %}<div class="small">{% blocktrans with vms=device.by_model|join:", " %}By model: {{ vms }}{% endblocktrans %}</div>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card-body small text-body-secondary">{% trans "The host reports no USB devices." %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div role="status">
|
||||
<div id="no-match" class="text-center text-body-secondary py-3" hidden>{% trans "No device matches the search." %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
{% if devices_read %}<script src="{% static 'js/table_filter.js' %}"></script>{% endif %}
|
||||
{% endblock %}
|
||||
0
nodedevs/tests/__init__.py
Normal file
0
nodedevs/tests/__init__.py
Normal file
201
nodedevs/tests/test_nodedevs.py
Normal file
201
nodedevs/tests/test_nodedevs.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
"""The host's devices page: PCI devices by IOMMU group and USB devices, each
|
||||
with what the host has on it (network interfaces, disks, its graphics card)
|
||||
and the VMs that name it, by PCI address, by USB address or by USB model
|
||||
only. Node device XML as libvirt 11.10 writes it; the connection is a mock,
|
||||
libvirt's test driver has no node devices."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import libvirt
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import SimpleTestCase, TestCase
|
||||
from django.urls import reverse
|
||||
from instances.models import Instance
|
||||
from lxml import html
|
||||
from vrtManager.nodedevs import pci_address, wvmNodeDevices
|
||||
|
||||
|
||||
def pci(slot, product, code, driver=None, group=None):
|
||||
iommu = f"<iommuGroup number='{group}'><address domain='0x0000' bus='0x00' slot='{slot}' function='0x0'/></iommuGroup>" \
|
||||
if group is not None else ""
|
||||
driver = f"<driver><name>{driver}</name></driver>" if driver else ""
|
||||
return (f"pci_0000_00_{int(slot, 16):02x}_0", f"""<device><name>pci_0000_00_{int(slot, 16):02x}_0</name><parent>computer</parent>{driver}
|
||||
<capability type='pci'><class>{code}</class><domain>0</domain><bus>0</bus><slot>{int(slot, 16)}</slot><function>0</function>
|
||||
<product id='0x1000'>{product}</product><vendor id='0x1af4'>Red Hat, Inc.</vendor>{iommu}</capability></device>""")
|
||||
|
||||
|
||||
def child(name, parent, capability):
|
||||
return name, f"<device><name>{name}</name><parent>{parent}</parent>{capability}</device>"
|
||||
|
||||
|
||||
def usb(name, parent, bus, device, product="QEMU Tablet", vendor_id="0x0627", product_id="0x0001"):
|
||||
return name, f"""<device><name>{name}</name><parent>{parent}</parent><capability type='usb_device'><bus>{bus}</bus>
|
||||
<device>{device}</device><product id='{product_id}'>{product}</product><vendor id='{vendor_id}'>Adomax</vendor>
|
||||
</capability></device>"""
|
||||
|
||||
|
||||
def interface(name, parent, code):
|
||||
return child(name, parent, f"<capability type='usb'><number>0</number><class>{code}</class></capability>")
|
||||
|
||||
|
||||
NODES = [
|
||||
pci("0x06", "Virtio network device", "0x020000", "virtio-pci", group=3),
|
||||
child("net_eth0", "pci_0000_00_06_0", "<capability type='net'><interface>eth0</interface></capability>"),
|
||||
pci("0x03", "Virtio block device", "0x010000", "virtio-pci", group=2),
|
||||
child("virtio1", "pci_0000_00_03_0", "<capability type='system'/>"), # a level between, not a resource
|
||||
child("block_vda", "virtio1", "<capability type='storage'><block>/dev/vda</block></capability>"),
|
||||
pci("0x02", "Virtio 1.0 GPU", "0x030000", "virtio-pci", group=2),
|
||||
child("drm_card0", "pci_0000_00_02_0", "<devnode type='dev'>/dev/dri/card0</devnode><capability type='drm'><type>primary</type></capability>"),
|
||||
child("drm_render", "pci_0000_00_02_0", "<devnode type='dev'>/dev/dri/renderD128</devnode><capability type='drm'><type>render</type></capability>"),
|
||||
pci("0x0a", "Ethernet controller", "0x020000", "vfio-pci", group=4),
|
||||
pci("0x01", "PIIX3 USB", "0x0c0300", "uhci_hcd"),
|
||||
usb("usb_usb1", "pci_0000_00_01_0", 1, 1, "1.1 root hub", "0x1d6b", "0x0001"),
|
||||
interface("usb_1_0_1_0", "usb_usb1", 9),
|
||||
usb("usb_1_1", "usb_usb1", 1, 2),
|
||||
interface("usb_1_1_1_0", "usb_1_1", 3),
|
||||
usb("usb_1_2", "usb_usb1", 1, 3),
|
||||
interface("usb_1_2_1_0", "usb_1_2", 3),
|
||||
]
|
||||
|
||||
|
||||
def node(name, xml):
|
||||
return MagicMock(**{"name.return_value": name, "XMLDesc.return_value": xml})
|
||||
|
||||
|
||||
def vm(name, hostdevs, saved=None):
|
||||
"""A VM with hostdevs as it runs and, given saved, persistent with those for its next start."""
|
||||
def describe(flags=0):
|
||||
return f"<domain><devices>{saved if flags else hostdevs}</devices></domain>"
|
||||
return MagicMock(**{"name.return_value": name, "XMLDesc.side_effect": describe, "isPersistent.return_value": saved is not None})
|
||||
|
||||
|
||||
PCI_0A = "<hostdev mode='subsystem' type='pci'><source><address domain='0x0000' bus='0x00' slot='0x0a' function='0x0'/></source></hostdev>"
|
||||
USB_1_3 = "<hostdev mode='subsystem' type='usb'><source><vendor id='0x0627'/><product id='0x0001'/><address bus='1' device='3'/></source></hostdev>"
|
||||
USB_MODEL = "<hostdev mode='subsystem' type='usb'><source><vendor id='0x0627'/><product id='0x0001'/></source></hostdev>"
|
||||
|
||||
|
||||
def devices(nodes=NODES, domains=()):
|
||||
conn = wvmNodeDevices.__new__(wvmNodeDevices)
|
||||
conn.wvm = MagicMock(**{"listAllDevices.return_value": [node(*n) for n in nodes],
|
||||
"listAllDomains.return_value": list(domains)})
|
||||
return conn.get_node_devices()
|
||||
|
||||
|
||||
class NodeDevicesTests(SimpleTestCase):
|
||||
def test_pci_devices_by_group_with_what_the_host_has_on_them(self):
|
||||
pci_devices, _ = devices()
|
||||
self.assertEqual([(d["address"], d["group"]) for d in pci_devices],
|
||||
[("0000:00:02.0", 2), ("0000:00:03.0", 2), ("0000:00:06.0", 3), ("0000:00:0a.0", 4), ("0000:00:01.0", None)])
|
||||
gpu, disk, nic, spare, _ = pci_devices
|
||||
# a graphics card's primary node is the host's; its render node is not a resource of its own
|
||||
self.assertEqual((gpu["resources"], disk["resources"], nic["resources"], spare["resources"]),
|
||||
(["/dev/dri/card0"], ["/dev/vda"], ["eth0"], []))
|
||||
self.assertEqual((nic["kind"], nic["driver"], nic["ids"]), ("Network controller", "virtio-pci", "1af4:1000"))
|
||||
|
||||
def test_usb_devices_with_their_classes_hubs_and_same_models(self):
|
||||
_, (hub, tablet_2, tablet_3) = devices()
|
||||
self.assertEqual((hub["kinds"], hub["same_model"]), (["Hub"], False))
|
||||
self.assertEqual((tablet_2["kinds"], tablet_2["same_model"], tablet_3["same_model"]), (["Human interface"], True, True))
|
||||
|
||||
def test_the_vms_that_name_a_device(self):
|
||||
pci_devices, (_, tablet_2, tablet_3) = devices(domains=[vm("db-1", PCI_0A + USB_1_3), vm("kiosk-1", USB_MODEL)])
|
||||
self.assertEqual([d["vms"] for d in pci_devices], [[], [], [], ["db-1"], []])
|
||||
# by address: that device; by model only: any device of the model
|
||||
self.assertEqual((tablet_3["vms"], tablet_3["by_model"]), (["db-1"], ["kiosk-1"]))
|
||||
self.assertEqual((tablet_2["vms"], tablet_2["by_model"]), ([], ["kiosk-1"]))
|
||||
|
||||
def test_a_usb_device_is_named_as_libvirt_picks_one(self):
|
||||
def named(hostdev):
|
||||
_, (_, tablet_2, tablet_3) = devices(domains=[vm("v", hostdev)])
|
||||
return [(d["vms"], d["by_model"]) for d in (tablet_2, tablet_3)]
|
||||
# an address with other IDs: another device now at that address
|
||||
other = USB_1_3.replace("0x0627", "0x046d")
|
||||
address_only = "<hostdev mode='subsystem' type='usb'><source><address bus='1' device='2'/></source></hostdev>"
|
||||
self.assertEqual(named(other), [([], []), ([], [])])
|
||||
self.assertEqual(named(address_only), [(["v"], []), ([], [])])
|
||||
self.assertEqual(named(USB_MODEL), [([], ["v"]), ([], ["v"])])
|
||||
|
||||
def test_a_pci_device_named_as_a_nic_or_only_for_the_next_start(self):
|
||||
nic = ("<interface type='hostdev'><source><address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>"
|
||||
"</source></interface>")
|
||||
pci_devices, _ = devices(domains=[vm("nic-1", nic), vm("next-1", "", saved=PCI_0A)])
|
||||
self.assertEqual({d["address"]: d["vms"] for d in pci_devices if d["vms"]},
|
||||
{"0000:00:06.0": ["nic-1"], "0000:00:0a.0": ["next-1"]})
|
||||
|
||||
def test_sr_iov_functions_and_unnamed_hardware(self):
|
||||
vf = pci("0x0b", "", "0x020000", group=5)[1].replace(
|
||||
"<product id='0x1000'></product>", "<product id='0x10ed'/>").replace(
|
||||
"</capability></device>", "<capability type='phys_function'><address domain='0x0000' bus='0x00' slot='0x0a'"
|
||||
" function='0x0'/></capability></capability></device>")
|
||||
pf = NODES[8][1].replace("</capability></device>", "<capability type='virt_functions' maxCount='7'><address domain='0x0000'"
|
||||
" bus='0x00' slot='0x0b' function='0x0'/></capability></capability></device>")
|
||||
pci_devices, _ = devices(nodes=[("pci_0000_00_0a_0", pf), ("pci_0000_00_0b_0", vf)])
|
||||
self.assertEqual([(d["physfn"], d["vfs"], d["product"]) for d in pci_devices],
|
||||
[("", 1, "Ethernet controller"), ("0000:00:0a.0", 0, "0x10ed")])
|
||||
|
||||
def test_a_device_gone_meanwhile_is_left_out_other_errors_are_raised(self):
|
||||
for code, raised in ((libvirt.VIR_ERR_NO_NODE_DEVICE, False), (libvirt.VIR_ERR_OPERATION_FAILED, True)):
|
||||
err = libvirt.libvirtError("gone")
|
||||
err.get_error_code = lambda code=code: code
|
||||
gone = node("pci_0000_00_0b_0", "")
|
||||
gone.XMLDesc.side_effect = err
|
||||
conn = wvmNodeDevices.__new__(wvmNodeDevices)
|
||||
conn.wvm = MagicMock(**{"listAllDevices.return_value": [gone, node(*NODES[0])], "listAllDomains.return_value": []})
|
||||
with self.subTest(code=code):
|
||||
if raised:
|
||||
self.assertRaises(libvirt.libvirtError, conn.get_node_devices)
|
||||
else:
|
||||
self.assertEqual([d["address"] for d in conn.get_node_devices()[0]], ["0000:00:06.0"])
|
||||
|
||||
def test_a_pci_address_from_libvirts_hex_or_numbers(self):
|
||||
self.assertEqual(pci_address("0x0000", "0x00", "0x1f", "0x3"), pci_address(0, 0, 31, 3))
|
||||
self.assertEqual(pci_address(0, 0, 31, 3), "0000:00:1f.3")
|
||||
|
||||
|
||||
class NodeDevicesPageTests(TestCase):
|
||||
def setUp(self):
|
||||
self.compute = Compute.objects.create(name="nd", hostname="192.0.2.30", login="root", password="", type=1)
|
||||
self.client.force_login(get_user_model().objects.create_superuser("nd", "nd@example.com", "pw"))
|
||||
self.url = reverse("nodedevs", args=[self.compute.id])
|
||||
|
||||
def page(self, nodes=NODES, domains=()):
|
||||
with patch("vrtManager.connection.connection_manager.get_connection") as get:
|
||||
get.return_value.listAllDevices.return_value = [node(*n) for n in nodes]
|
||||
get.return_value.listAllDomains.return_value = list(domains)
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
return html.fromstring(response.content)
|
||||
|
||||
def test_the_page_groups_pci_devices_and_links_their_vms(self):
|
||||
instance = Instance.objects.create(compute=self.compute, name="db-1", uuid="00000000-0000-4000-8000-0000000000d1")
|
||||
page = self.page(domains=[vm("db-1", PCI_0A), vm("outside-1", USB_1_3)])
|
||||
headers = [" ".join(h.text_content().split()) for h in page.xpath("//tr[@data-group-header]")]
|
||||
self.assertEqual(headers, ["IOMMU group 2", "IOMMU group 3", "IOMMU group 4", "Without an IOMMU group"])
|
||||
self.assertEqual(page.xpath("//a[text()='db-1']/@href"), [reverse("instances:instance", args=[instance.id])])
|
||||
self.assertIn("outside-1", page.text_content()) # a VM the panel does not have: named, not linked
|
||||
self.assertNotIn("No IOMMU groups detected", page.text_content())
|
||||
|
||||
def test_a_host_without_iommu_groups_says_so(self):
|
||||
page = self.page(nodes=[n for n in NODES if "iommuGroup" not in n[1]])
|
||||
self.assertIn("No IOMMU groups detected", page.text_content())
|
||||
|
||||
def test_a_host_without_pci_or_usb_devices_says_so(self):
|
||||
page = self.page(nodes=[n for n in NODES if "usb" in n[0]])
|
||||
self.assertIn("The host reports no PCI devices.", page.text_content())
|
||||
self.assertNotIn("No IOMMU groups detected", page.text_content()) # nothing to give
|
||||
self.assertIn("The host reports no USB devices.", self.page(nodes=NODES[:2]).text_content())
|
||||
|
||||
def test_only_superusers(self):
|
||||
self.client.force_login(get_user_model().objects.create_user("nd-user", "u@example.com", "pw"))
|
||||
self.assertEqual(self.client.get(self.url).status_code, 403)
|
||||
|
||||
def test_a_host_not_read_shows_no_device(self):
|
||||
# without the VMs' definitions a device would look free
|
||||
with patch("vrtManager.connection.connection_manager.get_connection") as get:
|
||||
get.return_value.listAllDevices.return_value = [node(*n) for n in NODES]
|
||||
get.return_value.listAllDomains.side_effect = libvirt.libvirtError("unreachable")
|
||||
response = self.client.get(self.url)
|
||||
page = html.fromstring(response.content)
|
||||
self.assertEqual(page.xpath("//table"), [])
|
||||
self.assertIn("unreachable", page.text_content())
|
||||
29
nodedevs/views.py
Normal file
29
nodedevs/views.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from admin.decorators import superuser_only
|
||||
from computes.models import Compute
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from instances.models import Instance
|
||||
from libvirt import libvirtError
|
||||
from vrtManager.nodedevs import wvmNodeDevices
|
||||
|
||||
|
||||
@superuser_only
|
||||
def nodedevs(request, compute_id):
|
||||
"""The host's PCI and USB devices: what each is, what the host has on it
|
||||
and the VMs that name it. Read only."""
|
||||
compute = get_object_or_404(Compute, pk=compute_id)
|
||||
context = {"compute": compute}
|
||||
try:
|
||||
conn = wvmNodeDevices(compute.hostname, compute.login, compute.password, compute.type)
|
||||
pci, usb = conn.get_node_devices()
|
||||
vm_ids = dict(Instance.objects.filter(compute=compute).values_list("name", "id"))
|
||||
for device in pci + usb:
|
||||
device["vms"] = [{"name": name, "id": vm_ids.get(name)} for name in device["vms"]]
|
||||
groups = {}
|
||||
for device in pci:
|
||||
groups.setdefault(device["group"], []).append(device)
|
||||
context.update(devices_read=True, groups=list(groups.items()), pci_count=len(pci), usb=usb,
|
||||
iommu=any(group is not None for group in groups))
|
||||
except libvirtError as err: # nothing is shown without the VMs: a device would look free
|
||||
messages.error(request, err)
|
||||
return render(request, "nodedevs.html", context)
|
||||
Loading…
Add table
Add a link
Reference in a new issue