diff --git a/instances/models.py b/instances/models.py
index 5f55aeda..27dcc3d3 100644
--- a/instances/models.py
+++ b/instances/models.py
@@ -168,11 +168,6 @@ class Instance(models.Model):
edit does not post the live value back over it."""
return self.proxy.get_disk_devices(config=True)
- @cached_property
- def config_qos(self):
- """QoS of the persistent definition, which the QoS form changes."""
- return self.proxy.get_all_qos(config=True)
-
@cached_property
def config_vcpu(self) -> int:
"""vCPUs of the persistent definition, which the CPU form changes."""
diff --git a/instances/templates/add_instance_network_block.html b/instances/templates/add_instance_network_block.html
index 72bf2a8f..62028d35 100644
--- a/instances/templates/add_instance_network_block.html
+++ b/instances/templates/add_instance_network_block.html
@@ -1,16 +1,14 @@
{% load i18n %}
{% load bootstrap_icons %}
{% if request.user.is_superuser %}
-
+
{% blocktrans with nic=forloop.counter0 %}Edit eth{{ nic }}{% endblocktrans %}
+
-
-
-
- {% trans 'IPv6' %}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- {% trans 'Model' %}
-
+
-
-
-
-
- {% if network.type == 'direct' %}
- {% trans 'In most configurations, macvtap does not work for host to guest network communication' %}
- {% endif %}
-
-
- {% endfor %}
-
-
-
-
-
-
- {% if instance.config_qos %}
-
-
-
{% trans "QoS Configuration" %}
-
-
-
-
-
-
-
{% trans "MAC" %}/{% trans "Direction" %}
-
{% trans "Average" %}
-
{% trans "Peak" %}
-
{% trans "Burst" %}
-
{% trans "Actions" %}
-
-
-
- {% for q, attrs in instance.config_qos.items %}
- {% for att in attrs %}
-
-
- {% csrf_token %}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% csrf_token %}
-
-
-
-
-
-
- {% endfor %}
- {% endfor %}
-
-
+
+
+
+
+ {% endif %}
+
{% csrf_token %}
+
+ {% include 'add_network_qos.html' with id=forloop.counter0 %}
+
+ {% empty %}
+
{% trans "The VM has no network device." %}
+ {% endfor %}
-
- {% endif %}
-
+
{% if show_hostdevs %}
diff --git a/instances/tests/test_live_dataloss.py b/instances/tests/test_live_dataloss.py
index 3e6a0893..0c465659 100644
--- a/instances/tests/test_live_dataloss.py
+++ b/instances/tests/test_live_dataloss.py
@@ -602,9 +602,9 @@ class LiveDataLossTestCase(TestCase):
def row():
page = html.fromstring(self.client.get(reverse("instances:instance", args=[inst.id])).content)
- (label,) = page.xpath("//label[@class='col-form-label'][contains(., 'Inbound')]")
- fields = label.xpath("ancestor::tr[1]//input[@name]") # the browser posts the row's inputs
- return {f.get("name"): f.get("value") for f in fields}
+ # the browser posts the inputs of the NIC's inbound limit form
+ (form,) = page.xpath("//form[input[@name='qos_direction'][@value='inbound']]")
+ return {f.get("name"): f.get("value") for f in form.xpath(".//input[@name!='csrfmiddlewaretoken']")}
self.post("set_qos", inst, {**row(), "qos_average": "1000"})
form = row()
diff --git a/instances/tests/test_pending_edits.py b/instances/tests/test_pending_edits.py
index 3ea28e90..5d87ebcc 100644
--- a/instances/tests/test_pending_edits.py
+++ b/instances/tests/test_pending_edits.py
@@ -28,8 +28,8 @@ class PendingEditsTestCase(TestCase):
compute = Compute.objects.create(name="pe", hostname="127.0.0.1:1", login="root", password="", type=1)
self.instance = Instance.objects.create(compute=compute, name="pe-vm", uuid="aaaaaaaa-1111-2222-3333-444444444444")
- def page(self, live=(), config=(), qos=({}, {}), status=1, **getters):
- """live and config: disks; qos and getters (get_vcpu=(2, 4)): values of
+ def page(self, live=(), config=(), nics=((), ()), status=1, **getters):
+ """live and config: disks; nics and getters (get_vcpu=(2, 4)): values of
the live and the persistent definition."""
with patch("instances.models.wvmInstance") as wvm, patch.object(
Compute, "status", new_callable=PropertyMock, return_value=True
@@ -44,12 +44,12 @@ class PendingEditsTestCase(TestCase):
getattr(proxy, getter).side_effect = lambda config=False, values=values: values[config]
proxy.get_max_cpus.return_value = [1, 2, 3, 4]
proxy.get_vcpus.return_value = {}
- for getter in ("get_networks", "get_ifaces", "get_storages", "get_media_devices", "get_net_devices",
+ for getter in ("get_networks", "get_ifaces", "get_storages", "get_media_devices",
"get_snapshot", "get_snapshot_jobs"):
getattr(proxy, getter).return_value = []
proxy.get_hostdevs.return_value = ([], [], [])
proxy.get_disk_devices.side_effect = lambda **kw: config if kw.get("config") else live
- proxy.get_all_qos.side_effect = lambda **kw: qos[1] if kw.get("config") else qos[0]
+ proxy.get_net_devices.side_effect = lambda config=False: [dict(nic) for nic in nics[config]]
proxy.get_console_type.return_value = "vnc"
proxy.get_video_models.return_value = ["vga", "virtio"]
proxy.get_cache_modes.return_value = {"default": "Default", "none": "Disabled"}
@@ -70,13 +70,24 @@ class PendingEditsTestCase(TestCase):
self.assertEqual(vda.xpath("string(.//input[@name='vol_serial']/@value)"), "")
def test_the_qos_form_shows_the_pending_definition(self):
- mac = "52:54:10:00:00:01"
- inbound = {"direction": "inbound", "average": "500", "peak": "0", "burst": "0", "floor": None}
- doc = self.page(qos=({mac: [inbound]}, {mac: [{**inbound, "average": "1000"}, {**inbound, "direction": "outbound"}]}))
+ nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio",
+ "ipv4": None, "ipv6": None, "filterref": "", "target": "vnet0", "outbound": [],
+ "inbound": {"average": "500", "peak": "0", "burst": "0"}}
+ pending = {**nic, "inbound": {"average": "1000", "peak": "0", "burst": "0"}, "outbound": nic["inbound"]}
+ doc = self.page(nics=([nic], [pending]))
+ # the pending inbound value, and the pending outbound limit has a form
self.assertEqual(doc.xpath("//input[@name='qos_average']/@value"), ["1000", "500"])
- # the pending outbound has a row
- self.assertEqual([" ".join(t.split()) for t in doc.xpath("//label[@class='col-form-label']/text()")],
- [f"{mac} Inbound", f"{mac} Outbound"])
+ self.assertEqual(doc.xpath("//form[.//input[@name='qos_average']]//input[@name='qos_direction']/@value"),
+ ["inbound", "outbound"])
+
+ def test_one_nic_card_spans_the_row_and_more_sit_side_by_side(self):
+ nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio",
+ "ipv4": None, "ipv6": None, "filterref": "", "target": "", "inbound": [], "outbound": []}
+ second = {**nic, "mac": "52:54:10:00:00:02"}
+ for nics, wide in (([nic], ["col-12"]), ([nic, second], ["col-12 col-md-6"] * 2)):
+ with self.subTest(count=len(nics)):
+ doc = self.page(nics=(nics, nics))
+ self.assertEqual(doc.xpath("//div[@id='network']//div[article]/@class"), wide)
def test_one_disk_card_spans_the_row_and_more_sit_side_by_side(self):
for disks, wide in (([disk("vda", "default", "")], ["col-12"]),
diff --git a/instances/tests/test_views.py b/instances/tests/test_views.py
index f2ba1efd..1c48a3e2 100644
--- a/instances/tests/test_views.py
+++ b/instances/tests/test_views.py
@@ -1012,7 +1012,7 @@ class InstancesTestCase(TestCase):
self.assertEqual(self.instance.networks[0]["state"], "down")
def test_set_unset_qos(self):
- self.assertEqual(len(self.instance.config_qos.keys()), 0)
+ self.assertFalse(self.instance.config_networks[0]["inbound"])
net_mac = self.instance.networks[0]["mac"]
response = self.client.post(
reverse("instances:set_qos", args=[self.instance.id]),
@@ -1027,8 +1027,8 @@ class InstancesTestCase(TestCase):
)
self.assertEqual(response.status_code, 302)
- del self.instance.config_qos
- self.assertEqual(len(self.instance.config_qos.keys()), 1)
+ del self.instance.config_networks
+ self.assertEqual(self.instance.config_networks[0]["inbound"]["average"], "1")
response = self.client.post(
reverse("instances:unset_qos", args=[self.instance.id]),
@@ -1039,8 +1039,8 @@ class InstancesTestCase(TestCase):
HTTP_REFERER=reverse("index"),
)
self.assertEqual(response.status_code, 302)
- del self.instance.config_qos
- self.assertEqual(len(self.instance.config_qos.keys()), 0)
+ del self.instance.config_networks
+ self.assertFalse(self.instance.config_networks[0]["inbound"])
# test on running instance
# self.instance.proxy.start()
diff --git a/networks/templates/add_network_qos.html b/networks/templates/add_network_qos.html
index 3d0d248b..64bc425b 100644
--- a/networks/templates/add_network_qos.html
+++ b/networks/templates/add_network_qos.html
@@ -1,17 +1,12 @@
{% load i18n %}
-{% load bootstrap_icons %}
{% if request.user.is_superuser %}
-
- {% bs_icon 'qr-code' %}
-
-
-
{% trans "Add QoS for Network" %}
+
{% trans "Add QoS for Network" %}
diff --git a/static/icon_cache/ethernet_None_None_None.svg b/static/icon_cache/ethernet_None_None_None.svg
new file mode 100644
index 00000000..0171dcc2
--- /dev/null
+++ b/static/icon_cache/ethernet_None_None_None.svg
@@ -0,0 +1,11 @@
+
+
diff --git a/vrtManager/instance.py b/vrtManager/instance.py
index 0c970165..e2a0199e 100644
--- a/vrtManager/instance.py
+++ b/vrtManager/instance.py
@@ -2883,48 +2883,6 @@ class wvmInstance(wvmConnect):
def set_memory(self, size, flags=0):
self.instance.setMemoryFlags(size, flags)
- def get_all_qos(self, config=False):
- qos_values = dict()
- tree = etree.fromstring(self._XMLDesc(PERSISTENT_XML if config else 0))
- qos = tree.xpath("/domain/devices/interface")
-
- for q in qos:
- bound_list = list()
- mac = q.xpath("mac/@address")
- band = q.find("bandwidth")
- if band is not None:
- in_qos = band.find("inbound")
- if in_qos is not None:
- in_av = in_qos.get("average")
- in_peak = in_qos.get("peak")
- in_burst = in_qos.get("burst")
- in_floor = in_qos.get("floor")
- bound_list.append(
- {
- "direction": "inbound",
- "average": in_av,
- "peak": in_peak,
- "floor": in_floor,
- "burst": in_burst,
- }
- )
-
- out_qos = band.find("outbound")
- if out_qos is not None:
- out_av = out_qos.get("average")
- out_peak = out_qos.get("peak")
- out_burst = out_qos.get("burst")
- bound_list.append(
- {
- "direction": "outbound",
- "average": out_av,
- "peak": out_peak,
- "burst": out_burst,
- }
- )
- qos_values[mac[0]] = bound_list
- return qos_values
-
def set_qos(self, mac, direction, average, peak, burst):
if direction not in ("inbound", "outbound"):
raise ValueError("Direction must be inbound or outbound")
diff --git a/vrtManager/tests/test_device_edits.py b/vrtManager/tests/test_device_edits.py
index 0764f088..483a6866 100644
--- a/vrtManager/tests/test_device_edits.py
+++ b/vrtManager/tests/test_device_edits.py
@@ -110,10 +110,8 @@ class DeviceEditsTestCase(unittest.TestCase):
p._defineXML.assert_called_once()
p.wvm.defineXML.assert_not_called()
- def test_qos_is_read_from_either_definition_and_an_edit_keeps_the_floor(self):
+ def test_a_qos_edit_keeps_the_floor(self):
p = proxy()
- self.assertEqual(p.get_all_qos(config=True)["52:54:10:00:00:01"][0]["floor"], "4")
- self.assertEqual(p.get_all_qos(), {}) # the running VM has none
p.set_qos("52:54:10:00:00:01", "inbound", 10, 20, 30)
inbound = defined(p).find("devices/interface[@type='network']/bandwidth/inbound")
self.assertEqual(inbound.attrib, {"average": "10", "peak": "20", "burst": "30", "floor": "4"})