[PATCH v5 00/18] *** qemu: block: Support block disk along with throttle filters ***

*** BLURB HERE *** Chun Feng Wu (17): schema: Add new domain elements to support multiple throttle groups schema: Add new domain elements to support multiple throttle filters config: Introduce ThrottleGroup and corresponding XML parsing config: Introduce ThrottleFilter and corresponding XML parsing qemu: monitor: Add support for ThrottleGroup operations tests: Test qemuMonitorJSONGetThrottleGroup and qemuMonitorJSONUpdateThrottleGroup remote: New APIs for ThrottleGroup lifecycle management qemu: Refactor qemuDomainSetBlockIoTune to extract common methods qemu: Implement qemu driver for throttle API qemu: helper: throttle filter nodename and preparation processing qemu: block: Support block disk along with throttle filters config: validate: Verify iotune, throttle group and filter qemuxmlconftest: Add 'throttlefilter' tests test_driver: Test throttle group lifecycle APIs virsh: Refactor iotune options for re-use virsh: Add support for throttle group operations virsh: Add option "throttle-groups" to "attach_disk" Harikumar Rajkumar (1): tests: Test qemuxmlactivetestThrottleGroup docs/formatdomain.rst | 47 ++ docs/manpages/virsh.rst | 135 +++- include/libvirt/libvirt-domain.h | 21 + src/conf/domain_conf.c | 398 ++++++++++ src/conf/domain_conf.h | 45 ++ src/conf/domain_validate.c | 119 ++- src/conf/schemas/domaincommon.rng | 293 ++++---- src/conf/virconftypes.h | 4 + src/driver-hypervisor.h | 22 + src/libvirt-domain.c | 174 +++++ src/libvirt_private.syms | 8 + src/libvirt_public.syms | 7 + src/qemu/qemu_block.c | 136 ++++ src/qemu/qemu_block.h | 49 ++ src/qemu/qemu_command.c | 180 +++++ src/qemu/qemu_command.h | 6 + src/qemu/qemu_domain.c | 73 +- src/qemu/qemu_driver.c | 619 +++++++++++++--- src/qemu/qemu_hotplug.c | 33 + src/qemu/qemu_monitor.c | 34 + src/qemu/qemu_monitor.h | 14 + src/qemu/qemu_monitor_json.c | 134 ++++ src/qemu/qemu_monitor_json.h | 14 + src/remote/remote_daemon_dispatch.c | 44 ++ src/remote/remote_driver.c | 40 ++ src/remote/remote_protocol.x | 48 +- src/remote_protocol-structs | 28 + src/test/test_driver.c | 452 ++++++++---- tests/qemumonitorjsontest.c | 86 +++ .../throttlefilter-in.xml | 392 ++++++++++ .../throttlefilter-out.xml | 393 ++++++++++ tests/qemuxmlactivetest.c | 1 + .../throttlefilter-invalid.x86_64-latest.err | 1 + .../throttlefilter-invalid.xml | 89 +++ .../throttlefilter.x86_64-latest.args | 55 ++ .../throttlefilter.x86_64-latest.xml | 105 +++ tests/qemuxmlconfdata/throttlefilter.xml | 95 +++ tests/qemuxmlconftest.c | 2 + tools/virsh-completer-domain.c | 82 +++ tools/virsh-completer-domain.h | 16 + tools/virsh-domain.c | 680 ++++++++++++++---- 41 files changed, 4649 insertions(+), 525 deletions(-) create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-in.xml create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-out.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.x86_64-latest.err create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.xml -- 2.39.5 (Apple Git-154)

From: Chun Feng Wu <danielwuwy@163.com> Introduce schema for defining '<throttlegroups>' element which configures throttling groups which can be configured for multiple disks. * Refactor "diskIoTune" to extract common schema "iotune" * Add new elements '<throttlegroups>' * <ThrottleGroups> contains <ThrottleGroup> defintion, which references "iotune" Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/formatdomain.rst | 26 +++ src/conf/schemas/domaincommon.rng | 274 ++++++++++++++++-------------- 2 files changed, 174 insertions(+), 126 deletions(-) diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index f0b79d5f8d..5b7f18b2f0 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -1964,6 +1964,32 @@ advertisements to the guest OS. (NB: Only qemu driver support) the guest OS itself can choose to circumvent the unavailability of the sleep states (e.g. S4 by turning off completely). +Disk Throttle Group Management +------------------------------ + +:since:`Since 10.7.0` it is possible to create multiple named throttle groups +and then reference them within ``throttlefilters``(sub-element of ``disk`` element) +to form filter chain in QEMU for specific disk. The limits(throttlegroups) are +shared within domain, hence the same group can be referenced by different filters. + +:: + + <domain> + ... + <throttlegroups> + <throttlegroup> + <group_name>limit0</group_name> + <total_bytes_sec>10000000</total_bytes_sec> + <read_iops_sec>400000</read_iops_sec> + <write_iops_sec>100000</write_iops_sec> + </throttlegroup> + </throttlegroups> + ... + </domain> + +``throttlegroup`` + It has the same sub-elements as ``iotune`` (See `Hard drives, floppy disks, CDROMs`_), + The difference is that <group_name> is required. Hypervisor features ------------------- diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index bfd0044805..6f08ac4da5 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -51,6 +51,7 @@ </zeroOrMore> <ref name="os"/> <ref name="clock"/> + <ref name="throttlegroups"/> <ref name="resources"/> <ref name="features"/> <ref name="events"/> @@ -6822,6 +6823,23 @@ </interleave> </element> </define> + <define name="throttlegroup"> + <element name="throttlegroup"> + <ref name="iotune"/> + </element> + </define> + <!-- + A set of optional throttlegroups + --> + <define name="throttlegroups"> + <optional> + <element name="throttlegroups"> + <zeroOrMore> + <ref name="throttlegroup"/> + </zeroOrMore> + </element> + </optional> + </define> <!-- A set of optional features: PAE, APIC, ACPI, GIC, TCG, HyperV Enlightenment, KVM features, paravirtual spinlocks and HAP support @@ -7789,134 +7807,138 @@ </element> </define> + <define name="iotune"> + <interleave> + <choice> + <element name="total_bytes_sec"> + <data type="unsignedLong"/> + </element> + <group> + <interleave> + <optional> + <element name="read_bytes_sec"> + <data type="unsignedLong"/> + </element> + </optional> + <optional> + <element name="write_bytes_sec"> + <data type="unsignedLong"/> + </element> + </optional> + </interleave> + </group> + </choice> + <choice> + <element name="total_iops_sec"> + <data type="unsignedLong"/> + </element> + <group> + <interleave> + <optional> + <element name="read_iops_sec"> + <data type="unsignedLong"/> + </element> + </optional> + <optional> + <element name="write_iops_sec"> + <data type="unsignedLong"/> + </element> + </optional> + </interleave> + </group> + </choice> + <choice> + <element name="total_bytes_sec_max"> + <data type="unsignedLong"/> + </element> + <group> + <interleave> + <optional> + <element name="read_bytes_sec_max"> + <data type="unsignedLong"/> + </element> + </optional> + <optional> + <element name="write_bytes_sec_max"> + <data type="unsignedLong"/> + </element> + </optional> + </interleave> + </group> + </choice> + <choice> + <element name="total_iops_sec_max"> + <data type="unsignedLong"/> + </element> + <group> + <interleave> + <optional> + <element name="read_iops_sec_max"> + <data type="unsignedLong"/> + </element> + </optional> + <optional> + <element name="write_iops_sec_max"> + <data type="unsignedLong"/> + </element> + </optional> + </interleave> + </group> + </choice> + <optional> + <element name="size_iops_sec"> + <data type="unsignedLong"/> + </element> + </optional> + <optional> + <element name="group_name"> + <text/> + </element> + </optional> + <choice> + <element name="total_bytes_sec_max_length"> + <data type="unsignedLong"/> + </element> + <group> + <interleave> + <optional> + <element name="read_bytes_sec_max_length"> + <data type="unsignedLong"/> + </element> + </optional> + <optional> + <element name="write_bytes_sec_max_length"> + <data type="unsignedLong"/> + </element> + </optional> + </interleave> + </group> + </choice> + <choice> + <element name="total_iops_sec_max_length"> + <data type="unsignedLong"/> + </element> + <group> + <interleave> + <optional> + <element name="read_iops_sec_max_length"> + <data type="unsignedLong"/> + </element> + </optional> + <optional> + <element name="write_iops_sec_max_length"> + <data type="unsignedLong"/> + </element> + </optional> + </interleave> + </group> + </choice> + </interleave> + </define> + <define name="diskIoTune"> <element name="iotune"> - <interleave> - <choice> - <element name="total_bytes_sec"> - <data type="unsignedLong"/> - </element> - <group> - <interleave> - <optional> - <element name="read_bytes_sec"> - <data type="unsignedLong"/> - </element> - </optional> - <optional> - <element name="write_bytes_sec"> - <data type="unsignedLong"/> - </element> - </optional> - </interleave> - </group> - </choice> - <choice> - <element name="total_iops_sec"> - <data type="unsignedLong"/> - </element> - <group> - <interleave> - <optional> - <element name="read_iops_sec"> - <data type="unsignedLong"/> - </element> - </optional> - <optional> - <element name="write_iops_sec"> - <data type="unsignedLong"/> - </element> - </optional> - </interleave> - </group> - </choice> - <choice> - <element name="total_bytes_sec_max"> - <data type="unsignedLong"/> - </element> - <group> - <interleave> - <optional> - <element name="read_bytes_sec_max"> - <data type="unsignedLong"/> - </element> - </optional> - <optional> - <element name="write_bytes_sec_max"> - <data type="unsignedLong"/> - </element> - </optional> - </interleave> - </group> - </choice> - <choice> - <element name="total_iops_sec_max"> - <data type="unsignedLong"/> - </element> - <group> - <interleave> - <optional> - <element name="read_iops_sec_max"> - <data type="unsignedLong"/> - </element> - </optional> - <optional> - <element name="write_iops_sec_max"> - <data type="unsignedLong"/> - </element> - </optional> - </interleave> - </group> - </choice> - <optional> - <element name="size_iops_sec"> - <data type="unsignedLong"/> - </element> - </optional> - <optional> - <element name="group_name"> - <text/> - </element> - </optional> - <choice> - <element name="total_bytes_sec_max_length"> - <data type="unsignedLong"/> - </element> - <group> - <interleave> - <optional> - <element name="read_bytes_sec_max_length"> - <data type="unsignedLong"/> - </element> - </optional> - <optional> - <element name="write_bytes_sec_max_length"> - <data type="unsignedLong"/> - </element> - </optional> - </interleave> - </group> - </choice> - <choice> - <element name="total_iops_sec_max_length"> - <data type="unsignedLong"/> - </element> - <group> - <interleave> - <optional> - <element name="read_iops_sec_max_length"> - <data type="unsignedLong"/> - </element> - </optional> - <optional> - <element name="write_iops_sec_max_length"> - <data type="unsignedLong"/> - </element> - </optional> - </interleave> - </group> - </choice> - </interleave> + <ref name="iotune"/> </element> </define> -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:09 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Introduce schema for defining '<throttlegroups>' element which configures throttling groups which can be configured for multiple disks.
* Refactor "diskIoTune" to extract common schema "iotune" * Add new elements '<throttlegroups>' * <ThrottleGroups> contains <ThrottleGroup> defintion, which references "iotune"
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/formatdomain.rst | 26 +++ src/conf/schemas/domaincommon.rng | 274 ++++++++++++++++-------------- 2 files changed, 174 insertions(+), 126 deletions(-)
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index f0b79d5f8d..5b7f18b2f0 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -1964,6 +1964,32 @@ advertisements to the guest OS. (NB: Only qemu driver support) the guest OS itself can choose to circumvent the unavailability of the sleep states (e.g. S4 by turning off completely).
+Disk Throttle Group Management +------------------------------ + +:since:`Since 10.7.0` it is possible to create multiple named throttle groups
This will need to become 11.0.0
+and then reference them within ``throttlefilters``(sub-element of ``disk`` element) +to form filter chain in QEMU for specific disk. The limits(throttlegroups) are +shared within domain, hence the same group can be referenced by different filters. + +:: + + <domain> + ... + <throttlegroups>
I wanted to suggest renaming this to 'diskthrottlegroups' but I guess this name will not collide in the future.
+ <throttlegroup> + <group_name>limit0</group_name> + <total_bytes_sec>10000000</total_bytes_sec> + <read_iops_sec>400000</read_iops_sec> + <write_iops_sec>100000</write_iops_sec> + </throttlegroup> + </throttlegroups> + ... + </domain> + +``throttlegroup`` + It has the same sub-elements as ``iotune`` (See `Hard drives, floppy disks, CDROMs`_), + The difference is that <group_name> is required.
With the changes above: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Fri, Nov 29, 2024 at 14:10:00 +0100, Peter Krempa wrote:
On Mon, Nov 18, 2024 at 19:24:09 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Introduce schema for defining '<throttlegroups>' element which configures throttling groups which can be configured for multiple disks.
* Refactor "diskIoTune" to extract common schema "iotune" * Add new elements '<throttlegroups>' * <ThrottleGroups> contains <ThrottleGroup> defintion, which references "iotune"
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/formatdomain.rst | 26 +++ src/conf/schemas/domaincommon.rng | 274 ++++++++++++++++-------------- 2 files changed, 174 insertions(+), 126 deletions(-)
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index f0b79d5f8d..5b7f18b2f0 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -1964,6 +1964,32 @@ advertisements to the guest OS. (NB: Only qemu driver support) the guest OS itself can choose to circumvent the unavailability of the sleep states (e.g. S4 by turning off completely).
+Disk Throttle Group Management +------------------------------ + +:since:`Since 10.7.0` it is possible to create multiple named throttle groups
This will need to become 11.0.0
11.1.0 at this point.

From: Chun Feng Wu <danielwuwy@163.com> Introduce schema for defining '<throttlefilters>' element which references throttling groups to form filter chain in qemu for specific disk * Add new elements '<throttlefilters>' * <ThrottleFilters> can include multiple throttlegroup references to form filter chain in qemu * Chained throttle filters feature in qemu is described at https://github.com/qemu/qemu/blob/master/docs/throttle.txt Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/formatdomain.rst | 21 +++++++++++++++++++++ src/conf/schemas/domaincommon.rng | 19 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 5b7f18b2f0..0c41b47a77 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -2753,6 +2753,15 @@ paravirtualized driver is specified via the ``disk`` element. <source dev='/dev/vhost-vdpa-0' /> <target dev='vdg' bus='virtio'/> </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2' /> + <source file='/var/lib/libvirt/images/disk.qcow2'/> + <target dev='vdh' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit2'/> + <throttlefilter group='limit012'/> + </throttlefilters> + </disk> </devices> ... @@ -3233,6 +3242,18 @@ paravirtualized driver is specified via the ``disk`` element. :since:`since after 0.4.4`; "sata" attribute value :since:`since 0.9.7`; "removable" attribute value :since:`since 1.1.3`; "rotation_rate" attribute value :since:`since 7.3.0` +``throttlefilters`` + The optional ``throttlefilters`` element provides the ability to provide additional + per-device throttle chain :since:`Since 10.7.0` + For example, if we have four different disks and we want to limit I/O for each one + and we also want to limit combined I/O of all four disks, we can leverage + ``throttlefilters`` to achieve this goal by setting two ``throttlefilter`` for + each disk: disk's own filter(e.g. limit2) and combined filter(e.g. limit012). + The order of such ``throttlefilter`` doesn't matter within ``throttlefilters``. + ``throttlefilters`` and ``iotune`` should be used exclusively. + + ``throttlefilter`` + The optional ``throttlefilter`` element is to reference defined throttle group. ``iotune`` The optional ``iotune`` element provides the ability to provide additional per-device I/O tuning, with values that can vary for each device (contrast diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index 6f08ac4da5..131fd396a4 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -1636,7 +1636,10 @@ <ref name="encryption"/> </optional> <optional> - <ref name="diskIoTune"/> + <choice> + <ref name="throttlefilters"/> + <ref name="diskIoTune"/> + </choice> </optional> <optional> <ref name="alias"/> @@ -6840,6 +6843,20 @@ </element> </optional> </define> + <!-- + A set of throttlefilters to reference throttlegroups + --> + <define name="throttlefilters"> + <element name="throttlefilters"> + <zeroOrMore> + <element name="throttlefilter"> + <attribute name="group"> + <data type="string"/> + </attribute> + </element> + </zeroOrMore> + </element> + </define> <!-- A set of optional features: PAE, APIC, ACPI, GIC, TCG, HyperV Enlightenment, KVM features, paravirtual spinlocks and HAP support -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:10 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Introduce schema for defining '<throttlefilters>' element which references throttling groups to form filter chain in qemu for specific disk
* Add new elements '<throttlefilters>' * <ThrottleFilters> can include multiple throttlegroup references to form filter chain in qemu * Chained throttle filters feature in qemu is described at https://github.com/qemu/qemu/blob/master/docs/throttle.txt
qemu uses gitlab as main project forge so https://gitlab.com/qemu-project/qemu/ ...
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/formatdomain.rst | 21 +++++++++++++++++++++ src/conf/schemas/domaincommon.rng | 19 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 5b7f18b2f0..0c41b47a77 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -2753,6 +2753,15 @@ paravirtualized driver is specified via the ``disk`` element. <source dev='/dev/vhost-vdpa-0' /> <target dev='vdg' bus='virtio'/> </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2' /> + <source file='/var/lib/libvirt/images/disk.qcow2'/> + <target dev='vdh' bus='virtio'/>
This had a rebase conflict as another example was added recently, don't forget to bump to 'vdg' when resovling the conflict.
+ <throttlefilters> + <throttlefilter group='limit2'/> + <throttlefilter group='limit012'/> + </throttlefilters> + </disk> </devices> ...
@@ -3233,6 +3242,18 @@ paravirtualized driver is specified via the ``disk`` element. :since:`since after 0.4.4`; "sata" attribute value :since:`since 0.9.7`; "removable" attribute value :since:`since 1.1.3`; "rotation_rate" attribute value :since:`since 7.3.0` +``throttlefilters`` + The optional ``throttlefilters`` element provides the ability to provide additional + per-device throttle chain :since:`Since 10.7.0`
11.0.0
+ For example, if we have four different disks and we want to limit I/O for each one + and we also want to limit combined I/O of all four disks, we can leverage + ``throttlefilters`` to achieve this goal by setting two ``throttlefilter`` for + each disk: disk's own filter(e.g. limit2) and combined filter(e.g. limit012). + The order of such ``throttlefilter`` doesn't matter within ``throttlefilters``. + ``throttlefilters`` and ``iotune`` should be used exclusively. + + ``throttlefilter`` + The optional ``throttlefilter`` element is to reference defined throttle group. ``iotune`` The optional ``iotune`` element provides the ability to provide additional per-device I/O tuning, with values that can vary for each device (contrast
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <danielwuwy@163.com> Introduce throttlegroup into domain and provide corresponding methods * Define new struct 'virDomainThrottleGroupDef' and corresponding destructor * Add operations(Add, Update, Del, ByName, Copy, Free) for 'virDomainThrottleGroupDef' * Update _virDomainDef to include virDomainThrottleGroupDef * Support new resource "Parse" and "Format" for operations between struct and DOM XML * Make sure "group_name" is defined in xml Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/conf/domain_conf.c | 293 ++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 27 ++++ src/conf/virconftypes.h | 2 + 3 files changed, 322 insertions(+) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 295707ec1f..ecad148783 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3790,6 +3790,32 @@ virDomainIOThreadIDDefArrayInit(virDomainDef *def, } +void +virDomainThrottleGroupDefFree(virDomainThrottleGroupDef *def) +{ + if (!def) + return; + g_free(def->group_name); + g_free(def); +} + + +static void +virDomainThrottleGroupDefArrayFree(virDomainThrottleGroupDef **def, + int nthrottlegroups) +{ + size_t i; + + if (!def) + return; + + for (i = 0; i < nthrottlegroups; i++) + virDomainThrottleGroupDefFree(def[i]); + + g_free(def); +} + + void virDomainResourceDefFree(virDomainResourceDef *resource) { @@ -4077,6 +4103,8 @@ void virDomainDefFree(virDomainDef *def) virDomainIOThreadIDDefArrayFree(def->iothreadids, def->niothreadids); + virDomainThrottleGroupDefArrayFree(def->throttlegroups, def->nthrottlegroups); + g_free(def->defaultIOThread); virBitmapFree(def->cputune.emulatorpin); @@ -7771,6 +7799,120 @@ virDomainDiskDefIotuneParse(virDomainDiskDef *def, #undef PARSE_IOTUNE +#define PARSE_THROTTLEGROUP(val) \ + if (virXPathULongLong("string(./" #val ")", \ + ctxt, &group->val) == -2) { \ + virReportError(VIR_ERR_XML_ERROR, \ + _("throttle group field '%1$s' must be an integer"), #val); \ + return NULL; \ + } + + +static virDomainThrottleGroupDef * +virDomainThrottleGroupDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt) +{ + g_autoptr(virDomainThrottleGroupDef) group = g_new0(virDomainThrottleGroupDef, 1); + + VIR_XPATH_NODE_AUTORESTORE(ctxt) + ctxt->node = node; + + PARSE_THROTTLEGROUP(total_bytes_sec); + PARSE_THROTTLEGROUP(read_bytes_sec); + PARSE_THROTTLEGROUP(write_bytes_sec); + PARSE_THROTTLEGROUP(total_iops_sec); + PARSE_THROTTLEGROUP(read_iops_sec); + PARSE_THROTTLEGROUP(write_iops_sec); + + PARSE_THROTTLEGROUP(total_bytes_sec_max); + PARSE_THROTTLEGROUP(read_bytes_sec_max); + PARSE_THROTTLEGROUP(write_bytes_sec_max); + PARSE_THROTTLEGROUP(total_iops_sec_max); + PARSE_THROTTLEGROUP(read_iops_sec_max); + PARSE_THROTTLEGROUP(write_iops_sec_max); + + PARSE_THROTTLEGROUP(size_iops_sec); + + PARSE_THROTTLEGROUP(total_bytes_sec_max_length); + PARSE_THROTTLEGROUP(read_bytes_sec_max_length); + PARSE_THROTTLEGROUP(write_bytes_sec_max_length); + PARSE_THROTTLEGROUP(total_iops_sec_max_length); + PARSE_THROTTLEGROUP(read_iops_sec_max_length); + PARSE_THROTTLEGROUP(write_iops_sec_max_length); + + /* group_name is required */ + if (!(group->group_name = virXPathString("string(./group_name)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing group name")); + return NULL; + } + + return g_steal_pointer(&group); +} +#undef PARSE_THROTTLEGROUP + + +/** + * virDomainThrottleGroupByName: + * @def: domain definition + * @name: throttle group name + * + * search throttle group within domain definition + * by @name + * + * Returns a pointer to throttle group found. + */ +virDomainThrottleGroupDef * +virDomainThrottleGroupByName(const virDomainDef *def, + const char *name) +{ + size_t i; + + if (!def->throttlegroups || def->nthrottlegroups == 0) + return NULL; + + for (i = 0; i < def->nthrottlegroups; i++) { + if (STREQ(def->throttlegroups[i]->group_name, name)) + return def->throttlegroups[i]; + } + + return NULL; +} + + +static int +virDomainDefThrottleGroupsParse(virDomainDef *def, + xmlXPathContextPtr ctxt) +{ + size_t i; + int n = 0; + g_autofree xmlNodePtr *nodes = NULL; + + if ((n = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, &nodes)) < 0) + return -1; + + if (n) + def->throttlegroups = g_new0(virDomainThrottleGroupDef *, n); + + for (i = 0; i < n; i++) { + g_autoptr(virDomainThrottleGroupDef) group = NULL; + + if (!(group = virDomainThrottleGroupDefParseXML(nodes[i], ctxt))) { + return -1; + } + + if (virDomainThrottleGroupByName(def, group->group_name)) { + virReportError(VIR_ERR_XML_ERROR, + _("duplicate group name '%1$s' found"), + group->group_name); + return -1; + } + def->throttlegroups[def->nthrottlegroups++] = g_steal_pointer(&group); + } + return 0; +} + + static int virDomainDiskDefMirrorParse(virDomainDiskDef *def, xmlNodePtr cur, @@ -19112,6 +19254,9 @@ virDomainDefParseXML(xmlXPathContextPtr ctxt, if (virDomainDefParseBootOptions(def, ctxt, xmlopt, flags) < 0) return NULL; + if (virDomainDefThrottleGroupsParse(def, ctxt) < 0) + return NULL; + /* analysis of the disk devices */ if ((n = virXPathNodeSet("./devices/disk", ctxt, &nodes)) < 0) return NULL; @@ -22387,6 +22532,95 @@ virDomainIOThreadIDDel(virDomainDef *def, } +/** + * virDomainThrottleGroupDefCopy: + * @src: throttle group to be copiped from + * @dst: throttle group to be copiped to + * + * copy throttle group content from @src to @dst + */ +void +virDomainThrottleGroupDefCopy(const virDomainThrottleGroupDef *src, + virDomainThrottleGroupDef *dst) +{ + *dst = *src; + dst->group_name = g_strdup(src->group_name); +} + + +/** + * virDomainThrottleGroupAdd: + * @def: domain definition + * @throttle_group: throttle group definition within domain + * + * add new throttle group into @def + * + * return a pointer to throttle group added + */ +virDomainThrottleGroupDef * +virDomainThrottleGroupAdd(virDomainDef *def, + virDomainThrottleGroupDef *throttle_group) +{ + virDomainThrottleGroupDef * new_group = g_new0(virDomainThrottleGroupDef, 1); + virDomainThrottleGroupDefCopy(throttle_group, new_group); + VIR_APPEND_ELEMENT_COPY(def->throttlegroups, def->nthrottlegroups, new_group); + return new_group; +} + + +/** + * virDomainThrottleGroupUpdate: + * @def: domain definition + * @info: throttle group definition within domain + * + * search existing throttle group within domain definition + * by group_name and then overwrite it with @info, + * it's to update existing throttle group + */ +void +virDomainThrottleGroupUpdate(virDomainDef *def, + virDomainThrottleGroupDef *info) +{ + size_t i; + + if (!info->group_name) + return; + + for (i = 0; i < def->nthrottlegroups; i++) { + virDomainThrottleGroupDef *t = def->throttlegroups[i]; + + if (STREQ_NULLABLE(t->group_name, info->group_name)) { + VIR_FREE(t->group_name); + virDomainThrottleGroupDefCopy(info, t); + } + } +} + + +/** + * virDomainThrottleGroupDel: + * @def: domain definition + * @name: throttle group name + * + * search existing throttle group within domain definition + * by group_name and then delete it with @name, + * it's to delete existing throttle group + */ +void +virDomainThrottleGroupDel(virDomainDef *def, + const char *name) +{ + size_t i; + for (i = 0; i < def->nthrottlegroups; i++) { + if (STREQ_NULLABLE(def->throttlegroups[i]->group_name, name)) { + virDomainThrottleGroupDefFree(def->throttlegroups[i]); + VIR_DELETE_ELEMENT(def->throttlegroups, i, def->nthrottlegroups); + return; + } + } +} + + static int virDomainEventActionDefFormat(virBuffer *buf, int type, @@ -27537,6 +27771,63 @@ virDomainDefIOThreadsFormat(virBuffer *buf, } +#define FORMAT_THROTTLE_GROUP(val) \ + if (group->val > 0) { \ + virBufferAsprintf(&childBuf, "<" #val ">%llu</" #val ">\n", \ + group->val); \ + } + + +static void +virDomainThrottleGroupFormat(virBuffer *buf, + virDomainThrottleGroupDef *group) +{ + g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); + + FORMAT_THROTTLE_GROUP(total_bytes_sec); + FORMAT_THROTTLE_GROUP(read_bytes_sec); + FORMAT_THROTTLE_GROUP(write_bytes_sec); + FORMAT_THROTTLE_GROUP(total_iops_sec); + FORMAT_THROTTLE_GROUP(read_iops_sec); + FORMAT_THROTTLE_GROUP(write_iops_sec); + + FORMAT_THROTTLE_GROUP(total_bytes_sec_max); + FORMAT_THROTTLE_GROUP(read_bytes_sec_max); + FORMAT_THROTTLE_GROUP(write_bytes_sec_max); + FORMAT_THROTTLE_GROUP(total_iops_sec_max); + FORMAT_THROTTLE_GROUP(read_iops_sec_max); + FORMAT_THROTTLE_GROUP(write_iops_sec_max); + + FORMAT_THROTTLE_GROUP(total_bytes_sec_max_length); + FORMAT_THROTTLE_GROUP(read_bytes_sec_max_length); + FORMAT_THROTTLE_GROUP(write_bytes_sec_max_length); + FORMAT_THROTTLE_GROUP(total_iops_sec_max_length); + FORMAT_THROTTLE_GROUP(read_iops_sec_max_length); + FORMAT_THROTTLE_GROUP(write_iops_sec_max_length); + + virBufferEscapeString(&childBuf, "<group_name>%s</group_name>\n", + group->group_name); + + virXMLFormatElement(buf, "throttlegroup", NULL, &childBuf); +} + +#undef FORMAT_THROTTLE_GROUP + +static void +virDomainDefThrottleGroupsFormat(virBuffer *buf, + const virDomainDef *def) +{ + g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf); + ssize_t n; + + for (n = 0; n < def->nthrottlegroups; n++) { + virDomainThrottleGroupFormat(&childrenBuf, def->throttlegroups[n]); + } + + virXMLFormatElement(buf, "throttlegroups", NULL, &childrenBuf); +} + + static void virDomainIOMMUDefFormat(virBuffer *buf, const virDomainIOMMUDef *iommu) @@ -28229,6 +28520,8 @@ virDomainDefFormatInternalSetRootName(virDomainDef *def, virDomainDefIOThreadsFormat(buf, def); + virDomainDefThrottleGroupsFormat(buf, def); + if (virDomainCputuneDefFormat(buf, def, flags) < 0) return -1; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index a187ab4083..2f40d304b4 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3067,6 +3067,9 @@ struct _virDomainDef { virDomainDefaultIOThreadDef *defaultIOThread; + size_t nthrottlegroups; + virDomainThrottleGroupDef **throttlegroups; + virDomainCputune cputune; virDomainResctrlDef **resctrls; @@ -4581,3 +4584,27 @@ virDomainObjGetMessages(virDomainObj *vm, bool virDomainDefHasSpiceGraphics(const virDomainDef *def); + +void +virDomainThrottleGroupDefFree(virDomainThrottleGroupDef *def); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainThrottleGroupDef, virDomainThrottleGroupDefFree); + +virDomainThrottleGroupDef * +virDomainThrottleGroupAdd(virDomainDef *def, + virDomainThrottleGroupDef *throttle_group); + +void +virDomainThrottleGroupUpdate(virDomainDef *def, + virDomainThrottleGroupDef *info); + +void +virDomainThrottleGroupDel(virDomainDef *def, + const char *name); + +virDomainThrottleGroupDef * +virDomainThrottleGroupByName(const virDomainDef *def, + const char *name); + +void +virDomainThrottleGroupDefCopy(const virDomainThrottleGroupDef *src, + virDomainThrottleGroupDef *dst); diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h index 59be61cea4..1936ef6ab1 100644 --- a/src/conf/virconftypes.h +++ b/src/conf/virconftypes.h @@ -80,6 +80,8 @@ typedef struct _virDomainBlkiotune virDomainBlkiotune; typedef struct _virDomainBlockIoTuneInfo virDomainBlockIoTuneInfo; +typedef struct _virDomainBlockIoTuneInfo virDomainThrottleGroupDef; + typedef struct _virDomainCheckpointDef virDomainCheckpointDef; typedef struct _virDomainCheckpointObj virDomainCheckpointObj; -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:11 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Introduce throttlegroup into domain and provide corresponding methods
* Define new struct 'virDomainThrottleGroupDef' and corresponding destructor * Add operations(Add, Update, Del, ByName, Copy, Free) for 'virDomainThrottleGroupDef' * Update _virDomainDef to include virDomainThrottleGroupDef * Support new resource "Parse" and "Format" for operations between struct and DOM XML * Make sure "group_name" is defined in xml
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/conf/domain_conf.c | 293 ++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 27 ++++ src/conf/virconftypes.h | 2 + 3 files changed, 322 insertions(+)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 295707ec1f..ecad148783 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3790,6 +3790,32 @@ virDomainIOThreadIDDefArrayInit(virDomainDef *def, }
+void +virDomainThrottleGroupDefFree(virDomainThrottleGroupDef *def) +{ + if (!def) + return; + g_free(def->group_name); + g_free(def); +} + + +static void +virDomainThrottleGroupDefArrayFree(virDomainThrottleGroupDef **def, + int nthrottlegroups) +{ + size_t i; + + if (!def) + return; + + for (i = 0; i < nthrottlegroups; i++) + virDomainThrottleGroupDefFree(def[i]); + + g_free(def); +} + + void virDomainResourceDefFree(virDomainResourceDef *resource) { @@ -4077,6 +4103,8 @@ void virDomainDefFree(virDomainDef *def)
virDomainIOThreadIDDefArrayFree(def->iothreadids, def->niothreadids);
+ virDomainThrottleGroupDefArrayFree(def->throttlegroups, def->nthrottlegroups); + g_free(def->defaultIOThread);
virBitmapFree(def->cputune.emulatorpin); @@ -7771,6 +7799,120 @@ virDomainDiskDefIotuneParse(virDomainDiskDef *def, #undef PARSE_IOTUNE
+#define PARSE_THROTTLEGROUP(val) \ + if (virXPathULongLong("string(./" #val ")", \ + ctxt, &group->val) == -2) { \ + virReportError(VIR_ERR_XML_ERROR, \ + _("throttle group field '%1$s' must be an integer"), #val); \ + return NULL; \ + } + + +static virDomainThrottleGroupDef * +virDomainThrottleGroupDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt) +{ + g_autoptr(virDomainThrottleGroupDef) group = g_new0(virDomainThrottleGroupDef, 1);
Since this is not the same code that parses/formats the <disk> throttling definition add a comment to both places stating that anyone adding fields must also change the other place as well.
+ + VIR_XPATH_NODE_AUTORESTORE(ctxt) + ctxt->node = node; + + PARSE_THROTTLEGROUP(total_bytes_sec); + PARSE_THROTTLEGROUP(read_bytes_sec); + PARSE_THROTTLEGROUP(write_bytes_sec); + PARSE_THROTTLEGROUP(total_iops_sec); + PARSE_THROTTLEGROUP(read_iops_sec); + PARSE_THROTTLEGROUP(write_iops_sec); + + PARSE_THROTTLEGROUP(total_bytes_sec_max); + PARSE_THROTTLEGROUP(read_bytes_sec_max); + PARSE_THROTTLEGROUP(write_bytes_sec_max); + PARSE_THROTTLEGROUP(total_iops_sec_max); + PARSE_THROTTLEGROUP(read_iops_sec_max); + PARSE_THROTTLEGROUP(write_iops_sec_max); + + PARSE_THROTTLEGROUP(size_iops_sec); + + PARSE_THROTTLEGROUP(total_bytes_sec_max_length); + PARSE_THROTTLEGROUP(read_bytes_sec_max_length); + PARSE_THROTTLEGROUP(write_bytes_sec_max_length); + PARSE_THROTTLEGROUP(total_iops_sec_max_length); + PARSE_THROTTLEGROUP(read_iops_sec_max_length); + PARSE_THROTTLEGROUP(write_iops_sec_max_length); + + /* group_name is required */ + if (!(group->group_name = virXPathString("string(./group_name)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing group name")); + return NULL; + } + + return g_steal_pointer(&group); +} +#undef PARSE_THROTTLEGROUP + + +/** + * virDomainThrottleGroupByName: + * @def: domain definition + * @name: throttle group name + * + * search throttle group within domain definition + * by @name + * + * Returns a pointer to throttle group found. + */ +virDomainThrottleGroupDef * +virDomainThrottleGroupByName(const virDomainDef *def, + const char *name) +{ + size_t i; + + if (!def->throttlegroups || def->nthrottlegroups == 0) + return NULL; + + for (i = 0; i < def->nthrottlegroups; i++) { + if (STREQ(def->throttlegroups[i]->group_name, name)) + return def->throttlegroups[i]; + } + + return NULL; +} + + +static int +virDomainDefThrottleGroupsParse(virDomainDef *def, + xmlXPathContextPtr ctxt) +{ + size_t i; + int n = 0; + g_autofree xmlNodePtr *nodes = NULL; + + if ((n = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, &nodes)) < 0) + return -1; + + if (n)
We prefer explicit tests for (non-)zero values for anything that's numeric (e.g. not a boolean or pointer).
+ def->throttlegroups = g_new0(virDomainThrottleGroupDef *, n);
Also there's no point to continue if 'n' is zero here.
+ + for (i = 0; i < n; i++) { + g_autoptr(virDomainThrottleGroupDef) group = NULL; + + if (!(group = virDomainThrottleGroupDefParseXML(nodes[i], ctxt))) { + return -1; + } + + if (virDomainThrottleGroupByName(def, group->group_name)) { + virReportError(VIR_ERR_XML_ERROR, + _("duplicate group name '%1$s' found"), + group->group_name); + return -1; + } + def->throttlegroups[def->nthrottlegroups++] = g_steal_pointer(&group); + } + return 0; +} + + static int virDomainDiskDefMirrorParse(virDomainDiskDef *def, xmlNodePtr cur, @@ -19112,6 +19254,9 @@ virDomainDefParseXML(xmlXPathContextPtr ctxt, if (virDomainDefParseBootOptions(def, ctxt, xmlopt, flags) < 0) return NULL;
+ if (virDomainDefThrottleGroupsParse(def, ctxt) < 0) + return NULL; + /* analysis of the disk devices */ if ((n = virXPathNodeSet("./devices/disk", ctxt, &nodes)) < 0) return NULL; @@ -22387,6 +22532,95 @@ virDomainIOThreadIDDel(virDomainDef *def, }
+/** + * virDomainThrottleGroupDefCopy: + * @src: throttle group to be copiped from + * @dst: throttle group to be copiped to + * + * copy throttle group content from @src to @dst
Usually copy functions also allocate memory; document that this one does not.
+ */ +void +virDomainThrottleGroupDefCopy(const virDomainThrottleGroupDef *src, + virDomainThrottleGroupDef *dst) +{ + *dst = *src; + dst->group_name = g_strdup(src->group_name); +} + + +/** + * virDomainThrottleGroupAdd: + * @def: domain definition + * @throttle_group: throttle group definition within domain + * + * add new throttle group into @def + * + * return a pointer to throttle group added + */ +virDomainThrottleGroupDef * +virDomainThrottleGroupAdd(virDomainDef *def, + virDomainThrottleGroupDef *throttle_group) +{ + virDomainThrottleGroupDef * new_group = g_new0(virDomainThrottleGroupDef, 1); + virDomainThrottleGroupDefCopy(throttle_group, new_group); + VIR_APPEND_ELEMENT_COPY(def->throttlegroups, def->nthrottlegroups, new_group);
Since 'virDomainThrottleGroupUpdate' below has weird semantics of not doing anything when given group doesn't exist consider merging these two together. This will also prevent the situation when virDomainThrottleGroupAdd is called on an existing definition which would add it without checking that it already exists and none of the other functions consider that scenario. At the very least document that this does absolutely no checking.
+ return new_group; +} + + +/** + * virDomainThrottleGroupUpdate: + * @def: domain definition + * @info: throttle group definition within domain + * + * search existing throttle group within domain definition + * by group_name and then overwrite it with @info, + * it's to update existing throttle group
Replace by Update corresponding throttle group in @def new config @info. If a throttle group with given name doesn't exist this function does nothing.
+ */ +void +virDomainThrottleGroupUpdate(virDomainDef *def, + virDomainThrottleGroupDef *info) +{ + size_t i; + + if (!info->group_name) + return; + + for (i = 0; i < def->nthrottlegroups; i++) { + virDomainThrottleGroupDef *t = def->throttlegroups[i]; + + if (STREQ_NULLABLE(t->group_name, info->group_name)) { + VIR_FREE(t->group_name); + virDomainThrottleGroupDefCopy(info, t); + } + } +} + + +/** + * virDomainThrottleGroupDel: + * @def: domain definition + * @name: throttle group name + * + * search existing throttle group within domain definition + * by group_name and then delete it with @name, + * it's to delete existing throttle group
Replace by: Delete throttle group @name in @def.
+ */ +void +virDomainThrottleGroupDel(virDomainDef *def, + const char *name) +{ + size_t i; + for (i = 0; i < def->nthrottlegroups; i++) { + if (STREQ_NULLABLE(def->throttlegroups[i]->group_name, name)) { + virDomainThrottleGroupDefFree(def->throttlegroups[i]); + VIR_DELETE_ELEMENT(def->throttlegroups, i, def->nthrottlegroups); + return; + } + } +} + + static int virDomainEventActionDefFormat(virBuffer *buf, int type, @@ -27537,6 +27771,63 @@ virDomainDefIOThreadsFormat(virBuffer *buf, }
+#define FORMAT_THROTTLE_GROUP(val) \ + if (group->val > 0) { \ + virBufferAsprintf(&childBuf, "<" #val ">%llu</" #val ">\n", \ + group->val); \ + } + +
Since this is not the same code that parses/formats the <disk> throttling definition add a comment to both places stating that anyone adding fields must also change the other place as well.
+static void +virDomainThrottleGroupFormat(virBuffer *buf, + virDomainThrottleGroupDef *group) +{ + g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); + + FORMAT_THROTTLE_GROUP(total_bytes_sec); + FORMAT_THROTTLE_GROUP(read_bytes_sec); + FORMAT_THROTTLE_GROUP(write_bytes_sec); + FORMAT_THROTTLE_GROUP(total_iops_sec); + FORMAT_THROTTLE_GROUP(read_iops_sec); + FORMAT_THROTTLE_GROUP(write_iops_sec); + + FORMAT_THROTTLE_GROUP(total_bytes_sec_max); + FORMAT_THROTTLE_GROUP(read_bytes_sec_max); + FORMAT_THROTTLE_GROUP(write_bytes_sec_max); + FORMAT_THROTTLE_GROUP(total_iops_sec_max); + FORMAT_THROTTLE_GROUP(read_iops_sec_max); + FORMAT_THROTTLE_GROUP(write_iops_sec_max); + + FORMAT_THROTTLE_GROUP(total_bytes_sec_max_length); + FORMAT_THROTTLE_GROUP(read_bytes_sec_max_length); + FORMAT_THROTTLE_GROUP(write_bytes_sec_max_length); + FORMAT_THROTTLE_GROUP(total_iops_sec_max_length); + FORMAT_THROTTLE_GROUP(read_iops_sec_max_length); + FORMAT_THROTTLE_GROUP(write_iops_sec_max_length); + + virBufferEscapeString(&childBuf, "<group_name>%s</group_name>\n", + group->group_name); + + virXMLFormatElement(buf, "throttlegroup", NULL, &childBuf); +} + +#undef FORMAT_THROTTLE_GROUP + +static void +virDomainDefThrottleGroupsFormat(virBuffer *buf, + const virDomainDef *def) +{ + g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf); + ssize_t n; + + for (n = 0; n < def->nthrottlegroups; n++) {
'nthrottlegroups' is 'size_t' (see below) so 'n' ought to be as well.
+ virDomainThrottleGroupFormat(&childrenBuf, def->throttlegroups[n]); + } + + virXMLFormatElement(buf, "throttlegroups", NULL, &childrenBuf); +} + + static void virDomainIOMMUDefFormat(virBuffer *buf, const virDomainIOMMUDef *iommu) @@ -28229,6 +28520,8 @@ virDomainDefFormatInternalSetRootName(virDomainDef *def,
virDomainDefIOThreadsFormat(buf, def);
+ virDomainDefThrottleGroupsFormat(buf, def); + if (virDomainCputuneDefFormat(buf, def, flags) < 0) return -1;
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index a187ab4083..2f40d304b4 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3067,6 +3067,9 @@ struct _virDomainDef {
virDomainDefaultIOThreadDef *defaultIOThread;
+ size_t nthrottlegroups;
[ ^^^^ ]
+ virDomainThrottleGroupDef **throttlegroups; + virDomainCputune cputune;
virDomainResctrlDef **resctrls;

From: Chun Feng Wu <danielwuwy@163.com> Introduce throttle filter along with corresponding operations. * Define new struct 'virDomainThrottleFilterDef' and corresponding destructor * Update _virDomainDiskDef to include virDomainThrottleFilterDef * Support throttle filter "Parse" and "Format" for operations between DOM XML and structs. Note, this commit just contains parse/format of group name for throttle filter in domain_conf.c, there is other commit to handle throttle filter nodename parse/format between throttlefilter and diskPrivateData for statusxml in qemu_domain.c when processing qemuDomainDiskPrivate and qemuDomainDiskPrivate Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/conf/domain_conf.c | 105 ++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 18 +++++++ src/conf/virconftypes.h | 2 + 3 files changed, 125 insertions(+) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index ecad148783..8841425adb 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3816,6 +3816,16 @@ virDomainThrottleGroupDefArrayFree(virDomainThrottleGroupDef **def, } +void +virDomainThrottleFilterDefFree(virDomainThrottleFilterDef *def) +{ + if (!def) + return; + g_free(def->group_name); + g_free(def->nodename); +} + + void virDomainResourceDefFree(virDomainResourceDef *resource) { @@ -7913,6 +7923,53 @@ virDomainDefThrottleGroupsParse(virDomainDef *def, } +static virDomainThrottleFilterDef * +virDomainDiskThrottleFilterDefParse(xmlNodePtr node) +{ + g_autoptr(virDomainThrottleFilterDef) filter = g_new0(virDomainThrottleFilterDef, 1); + + filter->group_name = virXMLPropString(node, "group"); + + if (!filter->group_name) + return NULL; + + return g_steal_pointer(&filter); +} + + +static int +virDomainDiskDefThrottleFiltersParse(virDomainDiskDef *def, + xmlXPathContextPtr ctxt) +{ + size_t i; + int n = 0; + g_autofree xmlNodePtr *nodes = NULL; + + if ((n = virXPathNodeSet("./throttlefilters/throttlefilter", ctxt, &nodes)) < 0) + return -1; + + if (n) + def->throttlefilters = g_new0(virDomainThrottleFilterDef *, n); + + for (i = 0; i < n; i++) { + g_autoptr(virDomainThrottleFilterDef) filter = NULL; + + if (!(filter = virDomainDiskThrottleFilterDefParse(nodes[i]))) { + return -1; + } + + if (virDomainThrottleFilterFind(def, filter->group_name)) { + virReportError(VIR_ERR_XML_ERROR, + _("duplicate filter name '%1$s' found"), + filter->group_name); + return -1; + } + def->throttlefilters[def->nthrottlefilters++] = g_steal_pointer(&filter); + } + return 0; +} + + static int virDomainDiskDefMirrorParse(virDomainDiskDef *def, xmlNodePtr cur, @@ -8403,6 +8460,9 @@ virDomainDiskDefParseXML(virDomainXMLOption *xmlopt, if (virDomainDiskDefIotuneParse(def, ctxt) < 0) return NULL; + if (virDomainDiskDefThrottleFiltersParse(def, ctxt) < 0) + return NULL; + def->domain_name = virXPathString("string(./backenddomain/@name)", ctxt); def->serial = virXPathString("string(./serial)", ctxt); def->wwn = virXPathString("string(./wwn)", ctxt); @@ -22621,6 +22681,34 @@ virDomainThrottleGroupDel(virDomainDef *def, } +/** + * virDomainThrottleFilterFind: + * @def: domain disk definition + * @name: throttle group name + * + * Search domain disk to find throttle filter referencing + * throttle group with name @name. + * + * Return a pointer to throttle filter found + */ +virDomainThrottleFilterDef * +virDomainThrottleFilterFind(const virDomainDiskDef *def, + const char *name) +{ + size_t i; + + if (!def->throttlefilters || def->nthrottlefilters == 0) + return NULL; + + for (i = 0; i < def->nthrottlefilters; i++) { + if (STREQ(name, def->throttlefilters[i]->group_name)) + return def->throttlefilters[i]; + } + + return NULL; +} + + static int virDomainEventActionDefFormat(virBuffer *buf, int type, @@ -23235,6 +23323,21 @@ virDomainDiskDefFormatIotune(virBuffer *buf, #undef FORMAT_IOTUNE +static void +virDomainDiskDefFormatThrottleFilters(virBuffer *buf, + virDomainDiskDef *disk) +{ + size_t i; + g_auto(virBuffer) throttleChildBuf = VIR_BUFFER_INIT_CHILD(buf); + for (i = 0; i < disk->nthrottlefilters; i++) { + g_auto(virBuffer) throttleAttrBuf = VIR_BUFFER_INITIALIZER; + virBufferEscapeString(&throttleAttrBuf, " group='%s'", disk->throttlefilters[i]->group_name); + virXMLFormatElement(&throttleChildBuf, "throttlefilter", &throttleAttrBuf, NULL); + } + virXMLFormatElement(buf, "throttlefilters", NULL, &throttleChildBuf); +} + + static void virDomainDiskDefFormatDriver(virBuffer *buf, virDomainDiskDef *disk) @@ -23521,6 +23624,8 @@ virDomainDiskDefFormat(virBuffer *buf, virDomainDiskDefFormatIotune(&childBuf, def); + virDomainDiskDefFormatThrottleFilters(&childBuf, def); + if (def->src->readonly) virBufferAddLit(&childBuf, "<readonly/>\n"); if (def->src->shared) diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 2f40d304b4..f1e85ab42f 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -520,6 +520,13 @@ void virDomainDiskIothreadDefFree(virDomainDiskIothreadDef *def); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainDiskIothreadDef, virDomainDiskIothreadDefFree); +/* Stores information related to a ThrottleFilter resource. */ +struct _virDomainThrottleFilterDef { + char *group_name; + char *nodename; /* node name of throttle filter object */ +}; + + /* Stores the virtual disk configuration */ struct _virDomainDiskDef { virStorageSource *src; /* non-NULL. XXX Allow NULL for empty cdrom? */ @@ -552,6 +559,9 @@ struct _virDomainDiskDef { virDomainBlockIoTuneInfo blkdeviotune; + size_t nthrottlefilters; + virDomainThrottleFilterDef **throttlefilters; + char *driverName; char *serial; @@ -4608,3 +4618,11 @@ virDomainThrottleGroupByName(const virDomainDef *def, void virDomainThrottleGroupDefCopy(const virDomainThrottleGroupDef *src, virDomainThrottleGroupDef *dst); + +void +virDomainThrottleFilterDefFree(virDomainThrottleFilterDef *def); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainThrottleFilterDef, virDomainThrottleFilterDefFree); + +virDomainThrottleFilterDef * +virDomainThrottleFilterFind(const virDomainDiskDef *def, + const char *name); diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h index 1936ef6ab1..c70437bc05 100644 --- a/src/conf/virconftypes.h +++ b/src/conf/virconftypes.h @@ -82,6 +82,8 @@ typedef struct _virDomainBlockIoTuneInfo virDomainBlockIoTuneInfo; typedef struct _virDomainBlockIoTuneInfo virDomainThrottleGroupDef; +typedef struct _virDomainThrottleFilterDef virDomainThrottleFilterDef; + typedef struct _virDomainCheckpointDef virDomainCheckpointDef; typedef struct _virDomainCheckpointObj virDomainCheckpointObj; -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:12 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Introduce throttle filter along with corresponding operations.
* Define new struct 'virDomainThrottleFilterDef' and corresponding destructor * Update _virDomainDiskDef to include virDomainThrottleFilterDef * Support throttle filter "Parse" and "Format" for operations between DOM XML and structs. Note, this commit just contains parse/format of group name for throttle filter in domain_conf.c, there is other commit to handle throttle filter nodename parse/format between throttlefilter and diskPrivateData for statusxml in qemu_domain.c when processing qemuDomainDiskPrivate and qemuDomainDiskPrivate
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/conf/domain_conf.c | 105 ++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 18 +++++++ src/conf/virconftypes.h | 2 + 3 files changed, 125 insertions(+)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index ecad148783..8841425adb 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3816,6 +3816,16 @@ virDomainThrottleGroupDefArrayFree(virDomainThrottleGroupDef **def, }
+void +virDomainThrottleFilterDefFree(virDomainThrottleFilterDef *def) +{ + if (!def) + return; + g_free(def->group_name); + g_free(def->nodename);
This doesn't free 'def' itself. If it's just meant to free the contents please use the 'Clear' naming instead of 'Free'
+} + + void virDomainResourceDefFree(virDomainResourceDef *resource) { @@ -7913,6 +7923,53 @@ virDomainDefThrottleGroupsParse(virDomainDef *def, }
+static virDomainThrottleFilterDef * +virDomainDiskThrottleFilterDefParse(xmlNodePtr node) +{ + g_autoptr(virDomainThrottleFilterDef) filter = g_new0(virDomainThrottleFilterDef, 1); + + filter->group_name = virXMLPropString(node, "group");
This doesn't report error ...
+ + if (!filter->group_name) + return NULL;
And you break out here ..
+ + return g_steal_pointer(&filter); +} + + +static int +virDomainDiskDefThrottleFiltersParse(virDomainDiskDef *def, + xmlXPathContextPtr ctxt) +{ + size_t i; + int n = 0; + g_autofree xmlNodePtr *nodes = NULL; + + if ((n = virXPathNodeSet("./throttlefilters/throttlefilter", ctxt, &nodes)) < 0) + return -1; + + if (n) + def->throttlefilters = g_new0(virDomainThrottleFilterDef *, n); + + for (i = 0; i < n; i++) { + g_autoptr(virDomainThrottleFilterDef) filter = NULL; + + if (!(filter = virDomainDiskThrottleFilterDefParse(nodes[i]))) { + return -1;
... and here too so this function will not report an error. Use virXMLPropStringRequired instead if the function field is required.
+ } + + if (virDomainThrottleFilterFind(def, filter->group_name)) { + virReportError(VIR_ERR_XML_ERROR, + _("duplicate filter name '%1$s' found"), + filter->group_name); + return -1; + } + def->throttlefilters[def->nthrottlefilters++] = g_steal_pointer(&filter); + } + return 0; +} + + static int virDomainDiskDefMirrorParse(virDomainDiskDef *def, xmlNodePtr cur, @@ -8403,6 +8460,9 @@ virDomainDiskDefParseXML(virDomainXMLOption *xmlopt, if (virDomainDiskDefIotuneParse(def, ctxt) < 0) return NULL;
+ if (virDomainDiskDefThrottleFiltersParse(def, ctxt) < 0) + return NULL; + def->domain_name = virXPathString("string(./backenddomain/@name)", ctxt); def->serial = virXPathString("string(./serial)", ctxt); def->wwn = virXPathString("string(./wwn)", ctxt); @@ -22621,6 +22681,34 @@ virDomainThrottleGroupDel(virDomainDef *def, }
+/** + * virDomainThrottleFilterFind: + * @def: domain disk definition + * @name: throttle group name + * + * Search domain disk to find throttle filter referencing + * throttle group with name @name. + * + * Return a pointer to throttle filter found + */ +virDomainThrottleFilterDef * +virDomainThrottleFilterFind(const virDomainDiskDef *def, + const char *name) +{ + size_t i; + + if (!def->throttlefilters || def->nthrottlefilters == 0) + return NULL;
virDomainDiskDefFormatThrottleFilters below checks only the 'nthrottlefilters'. In fact all of the above check is not needed as the loop simply won't find anything in a 0 sized array and return NULL at the end.
+ + for (i = 0; i < def->nthrottlefilters; i++) { + if (STREQ(name, def->throttlefilters[i]->group_name)) + return def->throttlefilters[i]; + } + + return NULL; +} + + static int virDomainEventActionDefFormat(virBuffer *buf, int type, @@ -23235,6 +23323,21 @@ virDomainDiskDefFormatIotune(virBuffer *buf, #undef FORMAT_IOTUNE
+static void +virDomainDiskDefFormatThrottleFilters(virBuffer *buf, + virDomainDiskDef *disk) +{ + size_t i; + g_auto(virBuffer) throttleChildBuf = VIR_BUFFER_INIT_CHILD(buf); + for (i = 0; i < disk->nthrottlefilters; i++) { + g_auto(virBuffer) throttleAttrBuf = VIR_BUFFER_INITIALIZER; + virBufferEscapeString(&throttleAttrBuf, " group='%s'", disk->throttlefilters[i]->group_name); + virXMLFormatElement(&throttleChildBuf, "throttlefilter", &throttleAttrBuf, NULL); + } + virXMLFormatElement(buf, "throttlefilters", NULL, &throttleChildBuf); +} + + static void virDomainDiskDefFormatDriver(virBuffer *buf, virDomainDiskDef *disk) @@ -23521,6 +23624,8 @@ virDomainDiskDefFormat(virBuffer *buf,
virDomainDiskDefFormatIotune(&childBuf, def);
+ virDomainDiskDefFormatThrottleFilters(&childBuf, def); + if (def->src->readonly) virBufferAddLit(&childBuf, "<readonly/>\n"); if (def->src->shared) diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 2f40d304b4..f1e85ab42f 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -520,6 +520,13 @@ void virDomainDiskIothreadDefFree(virDomainDiskIothreadDef *def); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainDiskIothreadDef, virDomainDiskIothreadDefFree);
+/* Stores information related to a ThrottleFilter resource. */ +struct _virDomainThrottleFilterDef { + char *group_name;
Add a separator comment here here as node name is a qemu driver implementation detail and not a configuration field.
+ char *nodename; /* node name of throttle filter object */
This will also later have to be serialized into the qemu driver's disk private data (I didn't check if it is). Rest looks okay to me.

From: Chun Feng Wu <danielwuwy@163.com> This change contains QMP requests for ThrottleGroup * ThrottleGroup is updated through "qemuMonitorJSONUpdateThrottleGroup" * ThrottleGroup is retrieved through "qemuMonitorJSONGetThrottleGroup" * ThrottleGroup is deleted by reusing "qemuMonitorDelObject" * ThrottleGroup is added by reusing "qemuMonitorAddObject" * "qemuMonitorMakeThrottleGroupLimits" will be used by building qemu cmd as well Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_monitor.c | 34 +++++++++ src/qemu/qemu_monitor.h | 14 ++++ src/qemu/qemu_monitor_json.c | 134 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 14 ++++ 4 files changed, 196 insertions(+) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index fd888e2468..a908e17606 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -3027,6 +3027,40 @@ qemuMonitorGetBlockIoThrottle(qemuMonitor *mon, } +int +qemuMonitorThrottleGroupLimits(virJSONValue *limits, + const virDomainThrottleGroupDef *group) +{ + return qemuMonitorMakeThrottleGroupLimits(limits, group); +} + + +int +qemuMonitorUpdateThrottleGroup(qemuMonitor *mon, + const char *qomid, + virDomainBlockIoTuneInfo *info) +{ + VIR_DEBUG("qomid=%s, info=%p", qomid, info); + + QEMU_CHECK_MONITOR(mon); + + return qemuMonitorJSONUpdateThrottleGroup(mon, qomid, info); +} + + +int +qemuMonitorGetThrottleGroup(qemuMonitor *mon, + const char *groupname, + virDomainBlockIoTuneInfo *reply) +{ + VIR_DEBUG("throttle-group=%s, reply=%p", groupname, reply); + + QEMU_CHECK_MONITOR(mon); + + return qemuMonitorJSONGetThrottleGroup(mon, groupname, reply); +} + + int qemuMonitorVMStatusToPausedReason(const char *status) { diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 035c9a7e3c..483aa016ba 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1063,6 +1063,20 @@ int qemuMonitorGetBlockIoThrottle(qemuMonitor *mon, const char *qdevid, virDomainBlockIoTuneInfo *reply); +int +qemuMonitorThrottleGroupLimits(virJSONValue *limits, + const virDomainThrottleGroupDef *group); + +int +qemuMonitorUpdateThrottleGroup(qemuMonitor *mon, + const char *qomid, + virDomainBlockIoTuneInfo *info); + +int +qemuMonitorGetThrottleGroup(qemuMonitor *mon, + const char *groupname, + virDomainBlockIoTuneInfo *reply); + int qemuMonitorSystemWakeup(qemuMonitor *mon); int qemuMonitorGetVersion(qemuMonitor *mon, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index c594b33106..887ce8e7f5 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -4666,6 +4666,140 @@ int qemuMonitorJSONGetBlockIoThrottle(qemuMonitor *mon, return qemuMonitorJSONBlockIoThrottleInfo(devices, qdevid, reply); } + +int +qemuMonitorMakeThrottleGroupLimits(virJSONValue *limits, + const virDomainThrottleGroupDef *group) +{ + if (virJSONValueObjectAdd(&limits, + "P:bps-total", group->total_bytes_sec, + "P:bps-read", group->read_bytes_sec, + "P:bps-write", group->write_bytes_sec, + "P:iops-total", group->total_iops_sec, + "P:iops-read", group->read_iops_sec, + "P:iops-write", group->write_iops_sec, + "P:bps-total-max", group->total_bytes_sec_max, + "P:bps-read-max", group->read_bytes_sec_max, + "P:bps-write-max", group->write_bytes_sec_max, + "P:iops-total-max", group->total_iops_sec_max, + "P:iops-read-max", group->read_iops_sec_max, + "P:iops-write-max", group->write_iops_sec_max, + "P:iops-size", group->size_iops_sec, + /* avoid error from QEMU: "the burst length cannot be 0" for throttlelimits + * when setting max-length + */ + "P:bps-total-max-length", group->total_bytes_sec_max_length, + "P:bps-read-max-length", group->read_bytes_sec_max_length, + "P:bps-write-max-length", group->write_bytes_sec_max_length, + "P:iops-total-max-length", group->total_iops_sec_max_length, + "P:iops-read-max-length", group->read_iops_sec_max_length, + "P:iops-write-max-length", group->write_iops_sec_max_length, + NULL) < 0) + return -1; + + return 0; +} + + +int +qemuMonitorJSONUpdateThrottleGroup(qemuMonitor *mon, + const char *qomid, + virDomainBlockIoTuneInfo *info) +{ + g_autoptr(virJSONValue) cmd = NULL; + g_autoptr(virJSONValue) result = NULL; + g_autoptr(virJSONValue) limits = virJSONValueNewObject(); + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", qomid); + + if (qemuMonitorMakeThrottleGroupLimits(limits, info) < 0) + return -1; + + if (!(cmd = qemuMonitorJSONMakeCommand("qom-set", + "s:property", "limits", + "s:path", prefixed_group_name, + "a:value", &limits, + NULL))) + return -1; + + if (qemuMonitorJSONCommand(mon, cmd, &result) < 0) + return -1; + + if (qemuMonitorJSONCheckError(cmd, result) < 0) + return -1; + + return 0; +} + + +#define GET_THROTTLE_GROUP_VALUE(FIELD, STORE) \ + if (virJSONValueObjectHasKey(ret, FIELD)) { \ + if (virJSONValueObjectGetNumberUlong(ret, FIELD, &reply->STORE) < 0) { \ + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, \ + _("value of throttle group field '%1$s' is malformed"), \ + #STORE); \ + return -1; \ + } \ + } + + +int +qemuMonitorJSONGetThrottleGroup(qemuMonitor *mon, + const char *gname, + virDomainBlockIoTuneInfo *reply) +{ + g_autoptr(virJSONValue) cmd = NULL; + g_autoptr(virJSONValue) result = NULL; + g_autofree char *groupCopy = NULL; + virJSONValue *ret; + + /* prefix group name with "throttle-" in QOM */ + g_autofree char *path = g_strdup_printf("/objects/throttle-%s", gname); + if (!(cmd = qemuMonitorJSONMakeCommand("qom-get", + "s:path", path, + "s:property", "limits", + NULL))) + return -1; + + if (qemuMonitorJSONCommand(mon, cmd, &result) < 0) + return -1; + + if (qemuMonitorJSONCheckError(cmd, result) < 0) + return -1; + + if (!(ret = qemuMonitorJSONGetReply(cmd, result, VIR_JSON_TYPE_OBJECT))) + return -1; + + GET_THROTTLE_GROUP_VALUE("bps-total", total_bytes_sec); + GET_THROTTLE_GROUP_VALUE("bps-read", read_bytes_sec); + GET_THROTTLE_GROUP_VALUE("bps-write", write_bytes_sec); + GET_THROTTLE_GROUP_VALUE("iops-total", total_iops_sec); + GET_THROTTLE_GROUP_VALUE("iops-read", read_iops_sec); + GET_THROTTLE_GROUP_VALUE("iops-write", write_iops_sec); + + GET_THROTTLE_GROUP_VALUE("bps-total-max", total_bytes_sec_max); + GET_THROTTLE_GROUP_VALUE("bps-read-max", read_bytes_sec_max); + GET_THROTTLE_GROUP_VALUE("bps-write-max", write_bytes_sec_max); + GET_THROTTLE_GROUP_VALUE("iops-total-max", total_iops_sec_max); + GET_THROTTLE_GROUP_VALUE("iops-read-max", read_iops_sec_max); + GET_THROTTLE_GROUP_VALUE("iops-write-max", write_iops_sec_max); + GET_THROTTLE_GROUP_VALUE("iops-size", size_iops_sec); + + GET_THROTTLE_GROUP_VALUE("bps-total-max-length", total_bytes_sec_max_length); + GET_THROTTLE_GROUP_VALUE("bps-read-max-length", read_bytes_sec_max_length); + GET_THROTTLE_GROUP_VALUE("bps-write-max-length", write_bytes_sec_max_length); + GET_THROTTLE_GROUP_VALUE("iops-total-max-length", total_iops_sec_max_length); + GET_THROTTLE_GROUP_VALUE("iops-read-max-length", read_iops_sec_max_length); + GET_THROTTLE_GROUP_VALUE("iops-write-max-length", write_iops_sec_max_length); + + groupCopy = g_strdup(gname); + reply->group_name = g_steal_pointer(&groupCopy); + + return 0; +} +#undef GET_THROTTLE_GROUP_VALUE + + int qemuMonitorJSONSystemWakeup(qemuMonitor *mon) { g_autoptr(virJSONValue) cmd = NULL; diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index ab3c2cb7c8..57b6a2dafa 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -375,6 +375,20 @@ qemuMonitorJSONSetBlockIoThrottle(qemuMonitor *mon, const char *qomid, virDomainBlockIoTuneInfo *info); +int +qemuMonitorMakeThrottleGroupLimits(virJSONValue *limits, + const virDomainThrottleGroupDef *group); + +int +qemuMonitorJSONUpdateThrottleGroup(qemuMonitor *mon, + const char *qomid, + virDomainBlockIoTuneInfo *info); + +int +qemuMonitorJSONGetThrottleGroup(qemuMonitor *mon, + const char *gname, + virDomainBlockIoTuneInfo *reply); + int qemuMonitorJSONGetBlockIoThrottle(qemuMonitor *mon, const char *qdevid, -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:13 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
This change contains QMP requests for ThrottleGroup
* ThrottleGroup is updated through "qemuMonitorJSONUpdateThrottleGroup" * ThrottleGroup is retrieved through "qemuMonitorJSONGetThrottleGroup" * ThrottleGroup is deleted by reusing "qemuMonitorDelObject" * ThrottleGroup is added by reusing "qemuMonitorAddObject" * "qemuMonitorMakeThrottleGroupLimits" will be used by building qemu cmd as well
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_monitor.c | 34 +++++++++ src/qemu/qemu_monitor.h | 14 ++++ src/qemu/qemu_monitor_json.c | 134 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 14 ++++ 4 files changed, 196 insertions(+)
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index c594b33106..887ce8e7f5 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -4666,6 +4666,140 @@ int qemuMonitorJSONGetBlockIoThrottle(qemuMonitor *mon, return qemuMonitorJSONBlockIoThrottleInfo(devices, qdevid, reply); }
+ +int +qemuMonitorMakeThrottleGroupLimits(virJSONValue *limits, + const virDomainThrottleGroupDef *group) +{ + if (virJSONValueObjectAdd(&limits, + "P:bps-total", group->total_bytes_sec, + "P:bps-read", group->read_bytes_sec, + "P:bps-write", group->write_bytes_sec, + "P:iops-total", group->total_iops_sec, + "P:iops-read", group->read_iops_sec, + "P:iops-write", group->write_iops_sec, + "P:bps-total-max", group->total_bytes_sec_max, + "P:bps-read-max", group->read_bytes_sec_max, + "P:bps-write-max", group->write_bytes_sec_max, + "P:iops-total-max", group->total_iops_sec_max, + "P:iops-read-max", group->read_iops_sec_max, + "P:iops-write-max", group->write_iops_sec_max, + "P:iops-size", group->size_iops_sec, + /* avoid error from QEMU: "the burst length cannot be 0" for throttlelimits + * when setting max-length + */
What is the point of this comment? All the conversions use "P:" conversion which skips the value if it's 0.
+ "P:bps-total-max-length", group->total_bytes_sec_max_length, + "P:bps-read-max-length", group->read_bytes_sec_max_length, + "P:bps-write-max-length", group->write_bytes_sec_max_length, + "P:iops-total-max-length", group->total_iops_sec_max_length, + "P:iops-read-max-length", group->read_iops_sec_max_length, + "P:iops-write-max-length", group->write_iops_sec_max_length, + NULL) < 0) + return -1; + + return 0; +} + + +int +qemuMonitorJSONUpdateThrottleGroup(qemuMonitor *mon, + const char *qomid, + virDomainBlockIoTuneInfo *info) +{ + g_autoptr(virJSONValue) cmd = NULL; + g_autoptr(virJSONValue) result = NULL; + g_autoptr(virJSONValue) limits = virJSONValueNewObject(); + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", qomid);a
I'd suggest storing the name with the prefix in the main struct itself but no need to change that now.
+ + if (qemuMonitorMakeThrottleGroupLimits(limits, info) < 0) + return -1; + + if (!(cmd = qemuMonitorJSONMakeCommand("qom-set", + "s:property", "limits", + "s:path", prefixed_group_name, + "a:value", &limits, + NULL)))
Since tests are added in another patch this one can use: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <danielwuwy@163.com> Within "testQemuMonitorJSONqemuMonitorJSONUpdateThrottleGroup" * Test qemuMonitorJSONGetThrottleGroup * Test qemuMonitorJSONUpdateThrottleGroup, which updates limits through "qom-set" Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- tests/qemumonitorjsontest.c | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index f7fe0fb6f4..cf6fce3d77 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -1884,6 +1884,91 @@ testQemuMonitorJSONqemuMonitorJSONSetBlockIoThrottle(const void *opaque) return ret; } + +static int +testQemuMonitorJSONqemuMonitorJSONUpdateThrottleGroup(const void *opaque) +{ + const testGenericData *data = opaque; + virDomainXMLOption *xmlopt = data->xmlopt; + virDomainBlockIoTuneInfo info, expectedInfo; + g_autoptr(qemuMonitorTest) test = NULL; + + if (!(test = qemuMonitorTestNewSchema(xmlopt, data->schema))) + return -1; + + expectedInfo = (virDomainBlockIoTuneInfo) {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, NULL, 15, 16, 17, 18, 19, 20}; + expectedInfo.group_name = g_strdup("limit0"); + + if (qemuMonitorTestAddItem(test, "qom-get", + "{" + " \"return\": {" + " \"bps-total\": 1," + " \"bps-read\": 2," + " \"bps-write\": 3," + " \"iops-total\": 4," + " \"iops-read\": 5," + " \"iops-write\": 6," + " \"bps-total-max\": 7," + " \"bps-read-max\": 8," + " \"bps-write-max\": 9," + " \"iops-total-max\": 10," + " \"iops-read-max\": 11," + " \"iops-write-max\": 12," + " \"iops-size\": 13," + " \"bps-total-max-length\": 15," + " \"bps-read-max-length\": 16," + " \"bps-write-max-length\": 17," + " \"iops-total-max-length\": 18," + " \"iops-read-max-length\": 19," + " \"iops-write-max-length\": 20" + " }," + " \"id\": \"libvirt-12\"" + "}") < 0) + return -1; + + if (qemuMonitorTestAddItemVerbatim(test, + "{\"execute\":\"qom-set\"," + " \"arguments\":{\"property\": \"limits\"," + " \"path\": \"throttle-limit1\"," + " \"value\":{\"bps-total\": 1," + " \"bps-read\": 2," + " \"bps-write\": 3," + " \"iops-total\": 4," + " \"iops-read\": 5," + " \"iops-write\": 6," + " \"bps-total-max\": 7," + " \"bps-read-max\": 8," + " \"bps-write-max\": 9," + " \"iops-total-max\": 10," + " \"iops-read-max\": 11," + " \"iops-write-max\": 12," + " \"iops-size\": 13," + " \"bps-total-max-length\": 15," + " \"bps-read-max-length\": 16," + " \"bps-write-max-length\": 17," + " \"iops-total-max-length\": 18," + " \"iops-read-max-length\": 19," + " \"iops-write-max-length\": 20}}," + " \"id\":\"libvirt-2\"}", + NULL, + "{ \"return\" : {}}") < 0) + return -1; + + if (qemuMonitorJSONGetThrottleGroup(qemuMonitorTestGetMonitor(test), + "limit0", &info) < 0) + return -1; + + if (testValidateGetBlockIoThrottle(&info, &expectedInfo) < 0) + return -1; + + if (qemuMonitorJSONUpdateThrottleGroup(qemuMonitorTestGetMonitor(test), + "limit1", &info) < 0) + return -1; + + return 0; +} + + static int testQemuMonitorJSONqemuMonitorJSONGetTargetArch(const void *opaque) { @@ -2981,6 +3066,7 @@ mymain(void) DO_TEST(qemuMonitorJSONGetMigrationStats); DO_TEST(qemuMonitorJSONGetChardevInfo); DO_TEST(qemuMonitorJSONSetBlockIoThrottle); + DO_TEST(qemuMonitorJSONUpdateThrottleGroup); DO_TEST(qemuMonitorJSONGetTargetArch); DO_TEST(qemuMonitorJSONGetMigrationCapabilities); DO_TEST(qemuMonitorJSONQueryCPUsFast); -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:14 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Within "testQemuMonitorJSONqemuMonitorJSONUpdateThrottleGroup" * Test qemuMonitorJSONGetThrottleGroup * Test qemuMonitorJSONUpdateThrottleGroup, which updates limits through "qom-set"
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- tests/qemumonitorjsontest.c | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <danielwuwy@163.com> Defined new public APIs: * virDomainSetThrottleGroup to add or update throttlegroup within specific domain, it will be referenced by throttlefilter later in disk to do limits * virDomainGetThrottleGroup to get throttlegroup info, old-style is discarded (APIs to query first for the number of parameters and then give it a reasonably-sized pointer), instead, the new approach is adopted that API returns allocated array of fields and number of fileds that are in it. * virDomainDelThrottleGroup to delete throttlegroup, it fails if this throttlegroup is still referenced by some throttlefilter Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- include/libvirt/libvirt-domain.h | 21 ++++ src/driver-hypervisor.h | 22 ++++ src/libvirt-domain.c | 174 ++++++++++++++++++++++++++++ src/libvirt_private.syms | 8 ++ src/libvirt_public.syms | 7 ++ src/remote/remote_daemon_dispatch.c | 44 +++++++ src/remote/remote_driver.c | 40 +++++++ src/remote/remote_protocol.x | 48 +++++++- src/remote_protocol-structs | 28 +++++ 9 files changed, 391 insertions(+), 1 deletion(-) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 9232ce2e6b..7ff1e50ef5 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -6560,4 +6560,25 @@ virDomainGraphicsReload(virDomainPtr domain, unsigned int type, unsigned int flags); + +int +virDomainSetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr params, + int nparams, + unsigned int flags); + +int +virDomainGetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags); + +int +virDomainDelThrottleGroup(virDomainPtr dom, + const char *group, + unsigned int flags); + + #endif /* LIBVIRT_DOMAIN_H */ diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index 4ce8da078d..b3b1912666 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1453,6 +1453,25 @@ typedef int unsigned int type, unsigned int flags); +typedef int +(*virDrvDomainSetThrottleGroup)(virDomainPtr dom, + const char *groupname, + virTypedParameterPtr params, + int nparams, + unsigned int flags); + +typedef int +(*virDrvDomainGetThrottleGroup)(virDomainPtr dom, + const char *groupname, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags); + +typedef int +(*virDrvDomainDelThrottleGroup)(virDomainPtr dom, + const char *groupname, + unsigned int flags); + typedef struct _virHypervisorDriver virHypervisorDriver; /** @@ -1726,4 +1745,7 @@ struct _virHypervisorDriver { virDrvDomainStartDirtyRateCalc domainStartDirtyRateCalc; virDrvDomainFDAssociate domainFDAssociate; virDrvDomainGraphicsReload domainGraphicsReload; + virDrvDomainSetThrottleGroup domainSetThrottleGroup; + virDrvDomainGetThrottleGroup domainGetThrottleGroup; + virDrvDomainDelThrottleGroup domainDelThrottleGroup; }; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 7c6b93963c..ce18d18854 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -14162,3 +14162,177 @@ virDomainGraphicsReload(virDomainPtr domain, virDispatchError(domain->conn); return -1; } + + +/** + * virDomainSetThrottleGroup: + * @dom: pointer to domain object + * @group: throttle group name + * @params: Pointer to blkio parameter objects + * @nparams: Number of blkio parameters (this value can be the same or + * less than the number of parameters supported) + * @flags: bitwise-OR of virDomainModificationImpact + * + * Add throttlegroup or change all or a subset of the throttlegroup options + * within specific domain + * + * The @group parameter is the name for new or existing throttlegroup, + * it cannot be NULL, detailed throttlegroup info is included in @params, + * it either creates new throttlegroup with @params or updates existing + * throttlegroup with @params, throttlegroup can be referenced by throttle + * filter in attached disk to do limits, the difference from iotune is that + * multiple throttlegroups can be referenced within attached disk + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.7.0 + */ +int +virDomainSetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "params=%p, nparams=%d, flags=0x%x", + params, nparams, flags); + VIR_TYPED_PARAMS_DEBUG(params, nparams); + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + conn = dom->conn; + + virCheckReadOnlyGoto(conn->flags, error); + virCheckNonNullArgGoto(group, error); + virCheckPositiveArgGoto(nparams, error); + virCheckNonNullArgGoto(params, error); + + if (virTypedParameterValidateSet(dom->conn, params, nparams) < 0) + goto error; + + if (conn->driver->domainSetThrottleGroup) { + int ret; + ret = conn->driver->domainSetThrottleGroup(dom, group, params, nparams, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dom->conn); + return -1; +} + + +/** + * virDomainGetThrottleGroup: + * @dom: pointer to domain object + * @group: throttle group name + * @params: pointer that will be filled with an array of typed parameters + * @nparams: pointer filled with number of elements in @params + * @flags: bitwise-OR of virDomainModificationImpact + * + * Get all block IO tunable parameters for specific throttle group. @group cannot be NULL. + * @nparams gives how many slots were filled with parameter information + * @flags may include VIR_DOMAIN_AFFECT_LIVE or VIR_DOMAIN_AFFECT_CONFIG. + * Both flags may be set. + * + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.7.0 + */ +int +virDomainGetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "params=%p, nparams=%d, flags=0x%x", + params, (nparams) ? *nparams : -1, flags); + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(group, error); + virCheckNonNullArgGoto(nparams, error); + virCheckNonNullArgGoto(params, error); + + conn = dom->conn; + + if (conn->driver->domainGetThrottleGroup) { + int ret; + ret = conn->driver->domainGetThrottleGroup(dom, group, params, nparams, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dom->conn); + return -1; +} + + +/** + * virDomainDelThrottleGroup: + * @dom: pointer to domain object + * @group: throttle group name + * @flags: bitwise-OR of virDomainModificationImpact + * + * Delete an throttlegroup from the domain. @group cannot be NULL, + * and the @group to be deleted must not have a throttlefilter associated with + * it and can be any of the current valid group. + * + * @flags may include VIR_DOMAIN_AFFECT_LIVE or VIR_DOMAIN_AFFECT_CONFIG. + * Both flags may be set. + * If VIR_DOMAIN_AFFECT_LIVE is set, the change affects a running domain + * and may fail if domain is not alive. + * If VIR_DOMAIN_AFFECT_CONFIG is set, the change affects persistent state, + * and will fail for transient domains. If neither flag is specified (that is, + * @flags is VIR_DOMAIN_AFFECT_CURRENT), then an inactive domain modifies + * persistent setup, while an active domain is hypervisor-dependent on whether + * just live or both live and persistent state is changed. + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.7.0 + */ +int +virDomainDelThrottleGroup(virDomainPtr dom, + const char *group, + unsigned int flags) +{ + virConnectPtr conn; + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(group, error); + + conn = dom->conn; + + if (conn->driver->domainDelThrottleGroup) { + int ret; + ret = conn->driver->domainDelThrottleGroup(dom, group, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dom->conn); + return -1; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 5b9b44ef96..9727495f3d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -677,6 +677,14 @@ virDomainTaintMessageTypeFromString; virDomainTaintMessageTypeToString; virDomainTaintTypeFromString; virDomainTaintTypeToString; +virDomainThrottleFilterDefFree; +virDomainThrottleFilterFind; +virDomainThrottleGroupAdd; +virDomainThrottleGroupByName; +virDomainThrottleGroupDefCopy; +virDomainThrottleGroupDefFree; +virDomainThrottleGroupDel; +virDomainThrottleGroupUpdate; virDomainTimerModeTypeFromString; virDomainTimerModeTypeToString; virDomainTimerNameTypeFromString; diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 7a3492d9d7..f17b6feaef 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -948,4 +948,11 @@ LIBVIRT_10.2.0 { virDomainGraphicsReload; } LIBVIRT_10.1.0; +LIBVIRT_10.7.0 { + global: + virDomainSetThrottleGroup; + virDomainGetThrottleGroup; + virDomainDelThrottleGroup; +} LIBVIRT_10.2.0; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c index e812f5c3e9..5d5f286203 100644 --- a/src/remote/remote_daemon_dispatch.c +++ b/src/remote/remote_daemon_dispatch.c @@ -3618,6 +3618,50 @@ remoteDispatchDomainGetBlockIoTune(virNetServer *server G_GNUC_UNUSED, return rv; } + +static int +remoteDispatchDomainGetThrottleGroup(virNetServer *server G_GNUC_UNUSED, + virNetServerClient *client, + virNetMessage *hdr G_GNUC_UNUSED, + struct virNetMessageError *rerr, + remote_domain_get_throttle_group_args *args, + remote_domain_get_throttle_group_ret *ret) +{ + virDomainPtr dom = NULL; + int rv = -1; + virTypedParameterPtr params = NULL; + int nparams = 0; + virConnectPtr conn = remoteGetHypervisorConn(client); + + if (!conn) + goto cleanup; + + if (!(dom = get_nonnull_domain(conn, args->dom))) + goto cleanup; + + if (virDomainGetThrottleGroup(dom, args->group ? *args->group : NULL, + ¶ms, &nparams, args->flags) < 0) + goto cleanup; + + /* Serialize the ThrottleGroup parameters. */ + if (virTypedParamsSerialize(params, nparams, + REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX, + (struct _virTypedParameterRemote **) &ret->params.params_val, + &ret->params.params_len, + args->flags) < 0) + goto cleanup; + + rv = 0; + + cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virTypedParamsFree(params, nparams); + virObjectUnref(dom); + return rv; +} + + /*-------------------------------------------------------------*/ static int diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index e76d9e9ba4..c757156e24 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -2583,6 +2583,43 @@ static int remoteDomainGetBlockIoTune(virDomainPtr domain, return 0; } + +static int +remoteDomainGetThrottleGroup(virDomainPtr domain, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + remote_domain_get_throttle_group_args args = {0}; + g_auto(remote_domain_get_throttle_group_ret) ret = {0}; + struct private_data *priv = domain->conn->privateData; + VIR_LOCK_GUARD lock = remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, domain); + args.group = group ? (char **)&group : NULL; + args.flags = flags; + + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_THROTTLE_GROUP, + (xdrproc_t) xdr_remote_domain_get_throttle_group_args, + (char *) &args, + (xdrproc_t) xdr_remote_domain_get_throttle_group_ret, + (char *) &ret) == -1) { + return -1; + } + + if (virTypedParamsDeserialize((struct _virTypedParameterRemote *) ret.params.params_val, + ret.params.params_len, + REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX, + params, + nparams) < 0) + return -1; + + return 0; + +} + + static int remoteDomainGetCPUStats(virDomainPtr domain, virTypedParameterPtr params, unsigned int nparams, @@ -7842,6 +7879,9 @@ static virHypervisorDriver hypervisor_driver = { .domainSetLaunchSecurityState = remoteDomainSetLaunchSecurityState, /* 8.0.0 */ .domainFDAssociate = remoteDomainFDAssociate, /* 9.0.0 */ .domainGraphicsReload = remoteDomainGraphicsReload, /* 10.2.0 */ + .domainSetThrottleGroup = remoteDomainSetThrottleGroup, /* 10.7.0 */ + .domainGetThrottleGroup = remoteDomainGetThrottleGroup, /* 10.7.0 */ + .domainDelThrottleGroup = remoteDomainDelThrottleGroup, /* 10.7.0 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 41c045ff78..02338b9f02 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -113,6 +113,9 @@ const REMOTE_DOMAIN_MEMORY_PARAMETERS_MAX = 16; /* Upper limit on list of blockio tuning parameters. */ const REMOTE_DOMAIN_BLOCK_IO_TUNE_PARAMETERS_MAX = 32; +/* Upper limit on list of throttle group parameters. */ +const REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX = 32; + /* Upper limit on list of numa parameters. */ const REMOTE_DOMAIN_NUMA_PARAMETERS_MAX = 16; @@ -1475,6 +1478,29 @@ struct remote_domain_get_block_io_tune_ret { int nparams; }; +struct remote_domain_set_throttle_group_args { + remote_nonnull_domain dom; + remote_nonnull_string group; + remote_typed_param params<REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX>; + unsigned int flags; +}; + +struct remote_domain_get_throttle_group_args { + remote_nonnull_domain dom; + remote_string group; + unsigned int flags; +}; + +struct remote_domain_get_throttle_group_ret { + remote_typed_param params<REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX>; +}; + +struct remote_domain_del_throttle_group_args { + remote_nonnull_domain dom; + remote_string group; + unsigned int flags; +}; + struct remote_domain_get_cpu_stats_args { remote_nonnull_domain dom; unsigned int nparams; @@ -7048,5 +7074,25 @@ enum remote_procedure { * @generate: both * @acl: domain:write */ - REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448 + REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448, + + /** + * @generate: both + * @acl: domain:write + * @acl: domain:save:!VIR_DOMAIN_AFFECT_CONFIG|VIR_DOMAIN_AFFECT_LIVE + * @acl: domain:save:VIR_DOMAIN_AFFECT_CONFIG + */ + REMOTE_PROC_DOMAIN_SET_THROTTLE_GROUP = 449, + + /** + * @generate: none + * @acl: domain:read + */ + REMOTE_PROC_DOMAIN_GET_THROTTLE_GROUP = 450, + + /** + * @generate: both + * @acl: domain:write + */ + REMOTE_PROC_DOMAIN_DEL_THROTTLE_GROUP = 451 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 4d3dc2d249..a94e29f9c9 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -1050,6 +1050,31 @@ struct remote_domain_get_block_io_tune_ret { } params; int nparams; }; +struct remote_domain_set_throttle_group_args { + remote_nonnull_domain dom; + remote_nonnull_string group; + struct { + u_int params_len; + remote_typed_param * params_val; + } params; + u_int flags; +}; +struct remote_domain_get_throttle_group_args { + remote_nonnull_domain dom; + remote_string group; + u_int flags; +}; +struct remote_domain_get_throttle_group_ret { + struct { + u_int params_len; + remote_typed_param * params_val; + } params; +}; +struct remote_domain_del_throttle_group_args { + remote_nonnull_domain dom; + remote_string group; + u_int flags; +}; struct remote_domain_get_cpu_stats_args { remote_nonnull_domain dom; u_int nparams; @@ -3755,4 +3780,7 @@ enum remote_procedure { REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, REMOTE_PROC_NODE_DEVICE_UPDATE = 447, REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448, + REMOTE_PROC_DOMAIN_SET_THROTTLE_GROUP = 449, + REMOTE_PROC_DOMAIN_GET_THROTTLE_GROUP = 450, + REMOTE_PROC_DOMAIN_DEL_THROTTLE_GROUP = 451, }; -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:15 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Defined new public APIs: * virDomainSetThrottleGroup to add or update throttlegroup within specific domain, it will be referenced by throttlefilter later in disk to do limits * virDomainGetThrottleGroup to get throttlegroup info, old-style is discarded (APIs to query first for the number of parameters and then give it a reasonably-sized pointer), instead, the new approach is adopted that API returns allocated array of fields and number of fileds that are in it. * virDomainDelThrottleGroup to delete throttlegroup, it fails if this throttlegroup is still referenced by some throttlefilter
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- include/libvirt/libvirt-domain.h | 21 ++++ src/driver-hypervisor.h | 22 ++++ src/libvirt-domain.c | 174 ++++++++++++++++++++++++++++ src/libvirt_private.syms | 8 ++ src/libvirt_public.syms | 7 ++ src/remote/remote_daemon_dispatch.c | 44 +++++++ src/remote/remote_driver.c | 40 +++++++ src/remote/remote_protocol.x | 48 +++++++- src/remote_protocol-structs | 28 +++++ 9 files changed, 391 insertions(+), 1 deletion(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 9232ce2e6b..7ff1e50ef5 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -6560,4 +6560,25 @@ virDomainGraphicsReload(virDomainPtr domain, unsigned int type, unsigned int flags);
+ +int +virDomainSetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr params, + int nparams, + unsigned int flags); + +int +virDomainGetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags);
I'm not exactly a fan of 'getter' functions for configuration which is also present in the XML, but since we did these historically I'm okay with this. [...]
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 7c6b93963c..ce18d18854 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -14162,3 +14162,177 @@ virDomainGraphicsReload(virDomainPtr domain, virDispatchError(domain->conn); return -1; } + + +/** + * virDomainSetThrottleGroup: + * @dom: pointer to domain object + * @group: throttle group name + * @params: Pointer to blkio parameter objects + * @nparams: Number of blkio parameters (this value can be the same or + * less than the number of parameters supported) + * @flags: bitwise-OR of virDomainModificationImpact + * + * Add throttlegroup or change all or a subset of the throttlegroup options + * within specific domain.
I don't think we should do the 'change a subset of' semantics any more. It was proven that it doesn't make sense. Specifically it's not possible to distinguish between "I don't want to change this field" and "I want to delete/revert to default for this field". The users must be instructed to always post the full required config.
+ * + * The @group parameter is the name for new or existing throttlegroup, + * it cannot be NULL, detailed throttlegroup info is included in @params, + * it either creates new throttlegroup with @params or updates existing + * throttlegroup with @params, throttlegroup can be referenced by throttle + * filter in attached disk to do limits, the difference from iotune is that + * multiple throttlegroups can be referenced within attached disk + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.7.0
11.1.0
+ */ +int +virDomainSetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "params=%p, nparams=%d, flags=0x%x", + params, nparams, flags); + VIR_TYPED_PARAMS_DEBUG(params, nparams); + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + conn = dom->conn; + + virCheckReadOnlyGoto(conn->flags, error); + virCheckNonNullArgGoto(group, error); + virCheckPositiveArgGoto(nparams, error); + virCheckNonNullArgGoto(params, error); + + if (virTypedParameterValidateSet(dom->conn, params, nparams) < 0) + goto error; + + if (conn->driver->domainSetThrottleGroup) { + int ret; + ret = conn->driver->domainSetThrottleGroup(dom, group, params, nparams, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dom->conn); + return -1; +} + + +/** + * virDomainGetThrottleGroup: + * @dom: pointer to domain object + * @group: throttle group name + * @params: pointer that will be filled with an array of typed parameters + * @nparams: pointer filled with number of elements in @params + * @flags: bitwise-OR of virDomainModificationImpact + * + * Get all block IO tunable parameters for specific throttle group. @group cannot be NULL. + * @nparams gives how many slots were filled with parameter information + * @flags may include VIR_DOMAIN_AFFECT_LIVE or VIR_DOMAIN_AFFECT_CONFIG. + * Both flags may be set.
For getter APIs you can't set both because there's no way the API can return both configs at once.
+ * + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.7.0 + */ +int +virDomainGetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "params=%p, nparams=%d, flags=0x%x", + params, (nparams) ? *nparams : -1, flags);
The actual value of 'nparams' at call doesn't matter, thus makes no sense to be logged. At the same time, 'group' which is a much more useful info is not logged.
+ + virResetLastError(); + + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(group, error); + virCheckNonNullArgGoto(nparams, error); + virCheckNonNullArgGoto(params, error); + + conn = dom->conn; + + if (conn->driver->domainGetThrottleGroup) { + int ret; + ret = conn->driver->domainGetThrottleGroup(dom, group, params, nparams, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(dom->conn); + return -1; +} + + +/** + * virDomainDelThrottleGroup: + * @dom: pointer to domain object + * @group: throttle group name + * @flags: bitwise-OR of virDomainModificationImpact + * + * Delete an throttlegroup from the domain. @group cannot be NULL, + * and the @group to be deleted must not have a throttlefilter associated with + * it and can be any of the current valid group. + * + * @flags may include VIR_DOMAIN_AFFECT_LIVE or VIR_DOMAIN_AFFECT_CONFIG. + * Both flags may be set. + * If VIR_DOMAIN_AFFECT_LIVE is set, the change affects a running domain + * and may fail if domain is not alive. + * If VIR_DOMAIN_AFFECT_CONFIG is set, the change affects persistent state, + * and will fail for transient domains. If neither flag is specified (that is, + * @flags is VIR_DOMAIN_AFFECT_CURRENT), then an inactive domain modifies + * persistent setup, while an active domain is hypervisor-dependent on whether + * just live or both live and persistent state is changed. + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.7.0 + */ +int +virDomainDelThrottleGroup(virDomainPtr dom, + const char *group, + unsigned int flags) +{ + virConnectPtr conn; + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(group, error); +
[...]
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 7a3492d9d7..f17b6feaef 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -948,4 +948,11 @@ LIBVIRT_10.2.0 { virDomainGraphicsReload; } LIBVIRT_10.1.0;
+LIBVIRT_10.7.0 {
This also must correspond to the proper version and this one can't be fixed later.
+ global: + virDomainSetThrottleGroup; + virDomainGetThrottleGroup; + virDomainDelThrottleGroup; +} LIBVIRT_10.2.0; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c index e812f5c3e9..5d5f286203 100644 --- a/src/remote/remote_daemon_dispatch.c +++ b/src/remote/remote_daemon_dispatch.c @@ -3618,6 +3618,50 @@ remoteDispatchDomainGetBlockIoTune(virNetServer *server G_GNUC_UNUSED, return rv; }
+ +static int +remoteDispatchDomainGetThrottleGroup(virNetServer *server G_GNUC_UNUSED, + virNetServerClient *client, + virNetMessage *hdr G_GNUC_UNUSED, + struct virNetMessageError *rerr, + remote_domain_get_throttle_group_args *args, + remote_domain_get_throttle_group_ret *ret) +{ + virDomainPtr dom = NULL; + int rv = -1; + virTypedParameterPtr params = NULL; + int nparams = 0; + virConnectPtr conn = remoteGetHypervisorConn(client); + + if (!conn) + goto cleanup; + + if (!(dom = get_nonnull_domain(conn, args->dom))) + goto cleanup; + + if (virDomainGetThrottleGroup(dom, args->group ? *args->group : NULL,
This is a weird construction, 'group' is supposed to be a nonnull string, so why should it ever be NULL?
+ ¶ms, &nparams, args->flags) < 0) + goto cleanup; + + /* Serialize the ThrottleGroup parameters. */ + if (virTypedParamsSerialize(params, nparams, + REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX, + (struct _virTypedParameterRemote **) &ret->params.params_val, + &ret->params.params_len, + args->flags) < 0) + goto cleanup; + + rv = 0; + + cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virTypedParamsFree(params, nparams); + virObjectUnref(dom); + return rv; +} + + /*-------------------------------------------------------------*/
static int diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index e76d9e9ba4..c757156e24 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -2583,6 +2583,43 @@ static int remoteDomainGetBlockIoTune(virDomainPtr domain, return 0; }
+ +static int +remoteDomainGetThrottleGroup(virDomainPtr domain, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + remote_domain_get_throttle_group_args args = {0}; + g_auto(remote_domain_get_throttle_group_ret) ret = {0}; + struct private_data *priv = domain->conn->privateData; + VIR_LOCK_GUARD lock = remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, domain); + args.group = group ? (char **)&group : NULL;
Spacing broken. Also 'group' is a mandatory argument, so why it's being checked? This line makes no sense to me.
+ args.flags = flags; + + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_THROTTLE_GROUP, + (xdrproc_t) xdr_remote_domain_get_throttle_group_args, + (char *) &args, + (xdrproc_t) xdr_remote_domain_get_throttle_group_ret, + (char *) &ret) == -1) {
Alignment of args seems to be broken.
+ return -1; + } + + if (virTypedParamsDeserialize((struct _virTypedParameterRemote *) ret.params.params_val, + ret.params.params_len, + REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX, + params, + nparams) < 0) + return -1; + + return 0; + +} + + static int remoteDomainGetCPUStats(virDomainPtr domain, virTypedParameterPtr params, unsigned int nparams, @@ -7842,6 +7879,9 @@ static virHypervisorDriver hypervisor_driver = { .domainSetLaunchSecurityState = remoteDomainSetLaunchSecurityState, /* 8.0.0 */ .domainFDAssociate = remoteDomainFDAssociate, /* 9.0.0 */ .domainGraphicsReload = remoteDomainGraphicsReload, /* 10.2.0 */ + .domainSetThrottleGroup = remoteDomainSetThrottleGroup, /* 10.7.0 */ + .domainGetThrottleGroup = remoteDomainGetThrottleGroup, /* 10.7.0 */ + .domainDelThrottleGroup = remoteDomainDelThrottleGroup, /* 10.7.0 */
11.1.0
};
static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 41c045ff78..02338b9f02 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -113,6 +113,9 @@ const REMOTE_DOMAIN_MEMORY_PARAMETERS_MAX = 16; /* Upper limit on list of blockio tuning parameters. */ const REMOTE_DOMAIN_BLOCK_IO_TUNE_PARAMETERS_MAX = 32;
+/* Upper limit on list of throttle group parameters. */ +const REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX = 32; + /* Upper limit on list of numa parameters. */ const REMOTE_DOMAIN_NUMA_PARAMETERS_MAX = 16;
@@ -1475,6 +1478,29 @@ struct remote_domain_get_block_io_tune_ret { int nparams; };
+struct remote_domain_set_throttle_group_args { + remote_nonnull_domain dom; + remote_nonnull_string group; + remote_typed_param params<REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX>; + unsigned int flags; +}; + +struct remote_domain_get_throttle_group_args { + remote_nonnull_domain dom; + remote_string group; + unsigned int flags; +}; + +struct remote_domain_get_throttle_group_ret { + remote_typed_param params<REMOTE_DOMAIN_THROTTLE_GROUP_PARAMETERS_MAX>; +}; + +struct remote_domain_del_throttle_group_args { + remote_nonnull_domain dom; + remote_string group; + unsigned int flags; +}; + struct remote_domain_get_cpu_stats_args { remote_nonnull_domain dom; unsigned int nparams; @@ -7048,5 +7074,25 @@ enum remote_procedure { * @generate: both * @acl: domain:write */ - REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448 + REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448, + + /** + * @generate: both + * @acl: domain:write + * @acl: domain:save:!VIR_DOMAIN_AFFECT_CONFIG|VIR_DOMAIN_AFFECT_LIVE + * @acl: domain:save:VIR_DOMAIN_AFFECT_CONFIG + */ + REMOTE_PROC_DOMAIN_SET_THROTTLE_GROUP = 449, + + /** + * @generate: none + * @acl: domain:read + */ + REMOTE_PROC_DOMAIN_GET_THROTTLE_GROUP = 450, + + /** + * @generate: both + * @acl: domain:write
Deletion of the throttle group needs to have same ACLs as the SET, thus also the 'save' perms need to be copied.
+ */ + REMOTE_PROC_DOMAIN_DEL_THROTTLE_GROUP = 451 };

On Mon, Nov 18, 2024 at 19:24:15 +0530, Harikumar R wrote: I don't think we should do the 'change a subset of' semantics any more. It was proven that it doesn't make sense. Specifically it's not possible to distinguish between "I don't want to change this field" and "I want to delete/revert to default for this field".
The users must be instructed to always post the full required config.
If the customer provides only a subset of the throttle group, the other fields will remain unchanged. Deleting/reverting a field may cause data mismatches between the XML and QEMU. We will instruct users to always provide the full required configuration. Can we have a validation to check the config?

On Fri, Feb 07, 2025 at 04:59:49 -0000, Harikumar Rajkumar wrote:
On Mon, Nov 18, 2024 at 19:24:15 +0530, Harikumar R wrote: I don't think we should do the 'change a subset of' semantics any more. It was proven that it doesn't make sense. Specifically it's not possible to distinguish between "I don't want to change this field" and "I want to delete/revert to default for this field".
The users must be instructed to always post the full required config.
If the customer provides only a subset of the throttle group, the other fields will remain unchanged.
First let's focus on the libvirt API design: (deliberately let's not compare this to the old design) You have the XML configuration, in which if a filed is omitted the default value will be assumed. Now if the API is designed so that omitted fields are not touched you don't have a way to actually return the configuration to the "default value" via the API. The users then have to edit the XML and restart the VM to achieve that config. Thus the only solution where the setter API allows full range of setting is the one which fully replaces the configuration with the new fields. This is also something we started to gravitate towards in the API backing 'virsh update-device'. Originally it tried to not change settings that were omitted in the input XML but that simply didn't work for certain thigns and especially for returning to defualt settings.
Deleting/reverting a field may cause data mismatches between the XML and QEMU.
This must not happen. Libvirt must always update everything so that the QEMU config is identical to what is requested in the XML. It is an implementation detail how that is achieved, based on what qemu provides: - explicitly request qemu to revert to default - query the default and set the same value as default - refuse to do something that is not possible
We will instruct users to always provide the full required configuration.
The API needs to allow a way that overwrites the full config including a way to return to the default. That said you can have a flag which switches the behaviour to "update fields which were provided to the provided value and leave everything else untouched" if that is practical to achieve
Can we have a validation to check the config?
I don't follow which you mean here. As said, if something is not possible to set in qemu if the API allows it, it needs to be rejected, so that the state doesn't desync between qemu and libvirt.

On Fri, Feb 07, 2025 at 04:59:49 -0000, Harikumar Rajkumar wrote: It is an implementation detail how that is achieved, based on what qemu provides: - explicitly request qemu to revert to default - query the default and set the same value as default - refuse to do something that is not possible
The API needs to allow a way that overwrites the full config including a way to return to the default.
We have changed the implementation to allowing full config SET. Below six fields are set to default 1 by QEMU if omitted by user, are you suggesting to store this default value in XML by query the default in QEMU while SET? total_bytes_sec_max_length: 1 read_bytes_sec_max_length: 1 write_bytes_sec_max_length: 1 total_iops_sec_max_length: 1 read_iops_sec_max_length: 1 write_iops_sec_max_length: 1

On Mon, Feb 10, 2025 at 11:16:54 -0000, Harikumar Rajkumar wrote:
On Fri, Feb 07, 2025 at 04:59:49 -0000, Harikumar Rajkumar wrote: It is an implementation detail how that is achieved, based on what qemu provides: - explicitly request qemu to revert to default - query the default and set the same value as default - refuse to do something that is not possible
The API needs to allow a way that overwrites the full config including a way to return to the default.
We have changed the implementation to allowing full config SET.
Below six fields are set to default 1 by QEMU if omitted by user, are you suggesting to store this default value in XML by query the default in QEMU while SET?
Not necessarily. The issue is that if you set a non-default value you'll most likely won't be able to query the original state. This'd require us to instantiate the object with defaults and then set it via monitor during startup. While possible that'll overcomplicate things most likely.
total_bytes_sec_max_length: 1 read_bytes_sec_max_length: 1 write_bytes_sec_max_length: 1 total_iops_sec_max_length: 1 read_iops_sec_max_length: 1 write_iops_sec_max_length: 1
Ideally; if you want to express the full power of the API you need a way to re-set this to default in qemu. I didn't have a look if it's possible to do without querying the default first though. Either way, for the initial impl you can also implement a check that will require the users to provide a value if it already was non-default. I just want to leave the possibility open for the API to do full reset semantics if somebody will be willing to implement them.

From: Chun Feng Wu <danielwuwy@163.com> extract common methods from "qemuDomainSetBlockIoTune" to be reused by throttle handling later, common methods include: * "qemuDomainValidateBlockIoTune", which is to validate that PARAMS contains only recognized parameter names with correct types * "qemuDomainSetBlockIoTuneFields", which is to load parameters into internal object virDomainBlockIoTuneInfo * "qemuDomainCheckBlockIoTuneMutualExclusion", which is to check rules like "total and read/write of bytes_sec cannot be set at the same time" * "qemuDomainCheckBlockIoTuneMax", which is to check "max" rules within iotune Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_driver.c | 225 ++++++++++++++++++++++++++--------------- 1 file changed, 142 insertions(+), 83 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index aa8a78da69..e80e5fc511 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14989,35 +14989,8 @@ qemuDomainCheckBlockIoTuneReset(virDomainDiskDef *disk, static int -qemuDomainSetBlockIoTune(virDomainPtr dom, - const char *path, - virTypedParameterPtr params, - int nparams, - unsigned int flags) +qemuDomainValidateBlockIoTune(virTypedParameterPtr params, int nparams) { - virQEMUDriver *driver = dom->conn->privateData; - virDomainObj *vm = NULL; - qemuDomainObjPrivate *priv; - virDomainDef *def = NULL; - virDomainDef *persistentDef = NULL; - virDomainBlockIoTuneInfo info = { 0 }; - virDomainBlockIoTuneInfo conf_info = { 0 }; - int ret = -1; - size_t i; - virDomainDiskDef *conf_disk = NULL; - virDomainDiskDef *disk; - qemuBlockIoTuneSetFlags set_fields = 0; - g_autoptr(virQEMUDriverConfig) cfg = NULL; - virObjectEvent *event = NULL; - virTypedParameterPtr eventParams = NULL; - int eventNparams = 0; - int eventMaxparams = 0; - virDomainBlockIoTuneInfo *cur_info; - virDomainBlockIoTuneInfo *conf_cur_info; - - - virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | - VIR_DOMAIN_AFFECT_CONFIG, -1); if (virTypedParamsValidate(params, nparams, VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC, VIR_TYPED_PARAM_ULLONG, @@ -15062,32 +15035,28 @@ qemuDomainSetBlockIoTune(virDomainPtr dom, NULL) < 0) return -1; - if (!(vm = qemuDomainObjFromDomain(dom))) - return -1; - - if (virDomainSetBlockIoTuneEnsureACL(dom->conn, vm->def, flags) < 0) - goto cleanup; - - cfg = virQEMUDriverGetConfig(driver); - - if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) - goto cleanup; - - priv = vm->privateData; + return 0; +} - if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) - goto endjob; - if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, - VIR_DOMAIN_TUNABLE_BLKDEV_DISK, path) < 0) - goto endjob; +static int +qemuDomainSetBlockIoTuneFields(virDomainBlockIoTuneInfo *info, + virTypedParameterPtr params, + int nparams, + qemuBlockIoTuneSetFlags *set_fields, + virTypedParameterPtr *eventParams, + int *eventNparams, + int *eventMaxparams) +{ + size_t i; + int ret = -1; #define SET_IOTUNE_FIELD(FIELD, BOOL, CONST) \ if (STREQ(param->field, VIR_DOMAIN_BLOCK_IOTUNE_##CONST)) { \ - info.FIELD = param->value.ul; \ - set_fields |= QEMU_BLOCK_IOTUNE_SET_##BOOL; \ - if (virTypedParamsAddULLong(&eventParams, &eventNparams, \ - &eventMaxparams, \ + info->FIELD = param->value.ul; \ + *set_fields |= QEMU_BLOCK_IOTUNE_SET_##BOOL; \ + if (virTypedParamsAddULLong(eventParams, eventNparams, \ + eventMaxparams, \ VIR_DOMAIN_TUNABLE_BLKDEV_##CONST, \ param->value.ul) < 0) \ goto endjob; \ @@ -15127,10 +15096,10 @@ qemuDomainSetBlockIoTune(virDomainPtr dom, /* NB: Cannot use macro since this is a value.s not a value.ul */ if (STREQ(param->field, VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME)) { - info.group_name = g_strdup(param->value.s); - set_fields |= QEMU_BLOCK_IOTUNE_SET_GROUP_NAME; - if (virTypedParamsAddString(&eventParams, &eventNparams, - &eventMaxparams, + info->group_name = g_strdup(param->value.s); + *set_fields |= QEMU_BLOCK_IOTUNE_SET_GROUP_NAME; + if (virTypedParamsAddString(eventParams, eventNparams, + eventMaxparams, VIR_DOMAIN_TUNABLE_BLKDEV_GROUP_NAME, param->value.s) < 0) goto endjob; @@ -15153,56 +15122,56 @@ qemuDomainSetBlockIoTune(virDomainPtr dom, #undef SET_IOTUNE_FIELD - if ((info.total_bytes_sec && info.read_bytes_sec) || - (info.total_bytes_sec && info.write_bytes_sec)) { + ret = 0; + endjob: + return ret; +} + + +static int +qemuDomainCheckBlockIoTuneMutualExclusion(virDomainBlockIoTuneInfo *info) +{ + if ((info->total_bytes_sec && info->read_bytes_sec) || + (info->total_bytes_sec && info->write_bytes_sec)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of bytes_sec cannot be set at the same time")); - goto endjob; + return -1; } - if ((info.total_iops_sec && info.read_iops_sec) || - (info.total_iops_sec && info.write_iops_sec)) { + if ((info->total_iops_sec && info->read_iops_sec) || + (info->total_iops_sec && info->write_iops_sec)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of iops_sec cannot be set at the same time")); - goto endjob; + return -1; } - if ((info.total_bytes_sec_max && info.read_bytes_sec_max) || - (info.total_bytes_sec_max && info.write_bytes_sec_max)) { + if ((info->total_bytes_sec_max && info->read_bytes_sec_max) || + (info->total_bytes_sec_max && info->write_bytes_sec_max)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of bytes_sec_max cannot be set at the same time")); - goto endjob; + return -1; } - if ((info.total_iops_sec_max && info.read_iops_sec_max) || - (info.total_iops_sec_max && info.write_iops_sec_max)) { + if ((info->total_iops_sec_max && info->read_iops_sec_max) || + (info->total_iops_sec_max && info->write_iops_sec_max)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of iops_sec_max cannot be set at the same time")); - goto endjob; + return -1; } - virDomainBlockIoTuneInfoCopy(&info, &conf_info); - - if (def) { - if (!(disk = qemuDomainDiskByName(def, path))) - goto endjob; - - if (!qemuDomainDiskBlockIoTuneIsSupported(disk)) - goto endjob; - - cur_info = qemuDomainFindGroupBlockIoTune(def, disk, &info); + return 0; +} - if (qemuDomainSetBlockIoTuneDefaults(&info, cur_info, - set_fields) < 0) - goto endjob; - if (qemuDomainCheckBlockIoTuneReset(disk, &info) < 0) - goto endjob; +static int +qemuDomainCheckBlockIoTuneMax(virDomainBlockIoTuneInfo *info) +{ + int ret = -1; #define CHECK_MAX(val, _bool) \ do { \ - if (info.val##_max) { \ - if (!info.val) { \ + if (info->val##_max) { \ + if (!info->val) { \ if (QEMU_BLOCK_IOTUNE_SET_##_bool) { \ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \ _("cannot reset '%1$s' when '%2$s' is set"), \ @@ -15214,7 +15183,7 @@ qemuDomainSetBlockIoTune(virDomainPtr dom, } \ goto endjob; \ } \ - if (info.val##_max < info.val) { \ + if (info->val##_max < info->val) { \ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \ _("value '%1$s' cannot be smaller than '%2$s'"), \ #val "_max", #val); \ @@ -15232,6 +15201,96 @@ qemuDomainSetBlockIoTune(virDomainPtr dom, #undef CHECK_MAX + ret = 0; + endjob: + return ret; +} + + +static int +qemuDomainSetBlockIoTune(virDomainPtr dom, + const char *path, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virQEMUDriver *driver = dom->conn->privateData; + virDomainObj *vm = NULL; + qemuDomainObjPrivate *priv; + virDomainDef *def = NULL; + virDomainDef *persistentDef = NULL; + virDomainBlockIoTuneInfo info = { 0 }; + virDomainBlockIoTuneInfo conf_info = { 0 }; + int ret = -1; + virDomainDiskDef *conf_disk = NULL; + virDomainDiskDef *disk; + qemuBlockIoTuneSetFlags set_fields = 0; + g_autoptr(virQEMUDriverConfig) cfg = NULL; + virObjectEvent *event = NULL; + virTypedParameterPtr eventParams = NULL; + int eventNparams = 0; + int eventMaxparams = 0; + virDomainBlockIoTuneInfo *cur_info; + virDomainBlockIoTuneInfo *conf_cur_info; + + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + if (qemuDomainValidateBlockIoTune(params, nparams) < 0) + return -1; + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainSetBlockIoTuneEnsureACL(dom->conn, vm->def, flags) < 0) + goto cleanup; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + priv = vm->privateData; + + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, + VIR_DOMAIN_TUNABLE_BLKDEV_DISK, path) < 0) + goto endjob; + + if (qemuDomainSetBlockIoTuneFields(&info, + params, + nparams, + &set_fields, + &eventParams, + &eventNparams, + &eventMaxparams) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMutualExclusion(&info) < 0) + goto endjob; + + virDomainBlockIoTuneInfoCopy(&info, &conf_info); + + if (def) { + if (!(disk = qemuDomainDiskByName(def, path))) + goto endjob; + + if (!qemuDomainDiskBlockIoTuneIsSupported(disk)) + goto endjob; + + cur_info = qemuDomainFindGroupBlockIoTune(def, disk, &info); + + if (qemuDomainSetBlockIoTuneDefaults(&info, cur_info, set_fields) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneReset(disk, &info) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMax(&info) < 0) + goto endjob; + /* blockdev-based qemu doesn't want to set the throttling when a cdrom * is empty. Skip the monitor call here since we will set the throttling * once new media is inserted */ -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:16 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
extract common methods from "qemuDomainSetBlockIoTune" to be reused by throttle handling later, common methods include: * "qemuDomainValidateBlockIoTune", which is to validate that PARAMS contains only recognized parameter names with correct types * "qemuDomainSetBlockIoTuneFields", which is to load parameters into internal object virDomainBlockIoTuneInfo * "qemuDomainCheckBlockIoTuneMutualExclusion", which is to check rules like "total and read/write of bytes_sec cannot be set at the same time" * "qemuDomainCheckBlockIoTuneMax", which is to check "max" rules within iotune
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_driver.c | 225 ++++++++++++++++++++++++++--------------- 1 file changed, 142 insertions(+), 83 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index aa8a78da69..e80e5fc511 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14989,35 +14989,8 @@ qemuDomainCheckBlockIoTuneReset(virDomainDiskDef *disk,
static int -qemuDomainSetBlockIoTune(virDomainPtr dom, - const char *path, - virTypedParameterPtr params, - int nparams, - unsigned int flags) +qemuDomainValidateBlockIoTune(virTypedParameterPtr params, int nparams) { - virQEMUDriver *driver = dom->conn->privateData; - virDomainObj *vm = NULL; - qemuDomainObjPrivate *priv; - virDomainDef *def = NULL; - virDomainDef *persistentDef = NULL; - virDomainBlockIoTuneInfo info = { 0 }; - virDomainBlockIoTuneInfo conf_info = { 0 }; - int ret = -1; - size_t i; - virDomainDiskDef *conf_disk = NULL; - virDomainDiskDef *disk; - qemuBlockIoTuneSetFlags set_fields = 0; - g_autoptr(virQEMUDriverConfig) cfg = NULL; - virObjectEvent *event = NULL; - virTypedParameterPtr eventParams = NULL; - int eventNparams = 0; - int eventMaxparams = 0; - virDomainBlockIoTuneInfo *cur_info; - virDomainBlockIoTuneInfo *conf_cur_info; - - - virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | - VIR_DOMAIN_AFFECT_CONFIG, -1); if (virTypedParamsValidate(params, nparams, VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC, VIR_TYPED_PARAM_ULLONG, @@ -15062,32 +15035,28 @@ qemuDomainSetBlockIoTune(virDomainPtr dom, NULL) < 0) return -1;
- if (!(vm = qemuDomainObjFromDomain(dom))) - return -1; - - if (virDomainSetBlockIoTuneEnsureACL(dom->conn, vm->def, flags) < 0) - goto cleanup; - - cfg = virQEMUDriverGetConfig(driver); - - if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) - goto cleanup; - - priv = vm->privateData; + return 0; +}
- if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) - goto endjob;
- if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, - VIR_DOMAIN_TUNABLE_BLKDEV_DISK, path) < 0) - goto endjob; +static int +qemuDomainSetBlockIoTuneFields(virDomainBlockIoTuneInfo *info, + virTypedParameterPtr params, + int nparams, + qemuBlockIoTuneSetFlags *set_fields, + virTypedParameterPtr *eventParams, + int *eventNparams, + int *eventMaxparams) +{ + size_t i; + int ret = -1;
#define SET_IOTUNE_FIELD(FIELD, BOOL, CONST) \ if (STREQ(param->field, VIR_DOMAIN_BLOCK_IOTUNE_##CONST)) { \ - info.FIELD = param->value.ul; \ - set_fields |= QEMU_BLOCK_IOTUNE_SET_##BOOL; \ - if (virTypedParamsAddULLong(&eventParams, &eventNparams, \ - &eventMaxparams, \ + info->FIELD = param->value.ul; \ + *set_fields |= QEMU_BLOCK_IOTUNE_SET_##BOOL; \ + if (virTypedParamsAddULLong(eventParams, eventNparams, \ + eventMaxparams, \ VIR_DOMAIN_TUNABLE_BLKDEV_##CONST, \ param->value.ul) < 0) \ goto endjob; \ @@ -15127,10 +15096,10 @@ qemuDomainSetBlockIoTune(virDomainPtr dom,
/* NB: Cannot use macro since this is a value.s not a value.ul */ if (STREQ(param->field, VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME)) { - info.group_name = g_strdup(param->value.s); - set_fields |= QEMU_BLOCK_IOTUNE_SET_GROUP_NAME; - if (virTypedParamsAddString(&eventParams, &eventNparams, - &eventMaxparams, + info->group_name = g_strdup(param->value.s); + *set_fields |= QEMU_BLOCK_IOTUNE_SET_GROUP_NAME; + if (virTypedParamsAddString(eventParams, eventNparams, + eventMaxparams, VIR_DOMAIN_TUNABLE_BLKDEV_GROUP_NAME, param->value.s) < 0) goto endjob; @@ -15153,56 +15122,56 @@ qemuDomainSetBlockIoTune(virDomainPtr dom,
#undef SET_IOTUNE_FIELD
- if ((info.total_bytes_sec && info.read_bytes_sec) || - (info.total_bytes_sec && info.write_bytes_sec)) { + ret = 0; + endjob: + return ret;
The 'endjob' label is reserved exclusively for jumping out of a code block which has the VM object job taken. This would be either 'cleanup' as there is no job in use here; but reallistically you don't need 'ret' and can return directly from the helper.
+} + + +static int +qemuDomainCheckBlockIoTuneMutualExclusion(virDomainBlockIoTuneInfo *info) +{ + if ((info->total_bytes_sec && info->read_bytes_sec) || + (info->total_bytes_sec && info->write_bytes_sec)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of bytes_sec cannot be set at the same time")); - goto endjob; + return -1;
Like here.
}
- if ((info.total_iops_sec && info.read_iops_sec) || - (info.total_iops_sec && info.write_iops_sec)) { + if ((info->total_iops_sec && info->read_iops_sec) || + (info->total_iops_sec && info->write_iops_sec)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of iops_sec cannot be set at the same time")); - goto endjob; + return -1; }
- if ((info.total_bytes_sec_max && info.read_bytes_sec_max) || - (info.total_bytes_sec_max && info.write_bytes_sec_max)) { + if ((info->total_bytes_sec_max && info->read_bytes_sec_max) || + (info->total_bytes_sec_max && info->write_bytes_sec_max)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of bytes_sec_max cannot be set at the same time")); - goto endjob; + return -1; }
- if ((info.total_iops_sec_max && info.read_iops_sec_max) || - (info.total_iops_sec_max && info.write_iops_sec_max)) { + if ((info->total_iops_sec_max && info->read_iops_sec_max) || + (info->total_iops_sec_max && info->write_iops_sec_max)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of iops_sec_max cannot be set at the same time")); - goto endjob; + return -1; }
- virDomainBlockIoTuneInfoCopy(&info, &conf_info); - - if (def) { - if (!(disk = qemuDomainDiskByName(def, path))) - goto endjob; - - if (!qemuDomainDiskBlockIoTuneIsSupported(disk)) - goto endjob; - - cur_info = qemuDomainFindGroupBlockIoTune(def, disk, &info); + return 0; +}
- if (qemuDomainSetBlockIoTuneDefaults(&info, cur_info, - set_fields) < 0) - goto endjob;
- if (qemuDomainCheckBlockIoTuneReset(disk, &info) < 0) - goto endjob; +static int +qemuDomainCheckBlockIoTuneMax(virDomainBlockIoTuneInfo *info) +{ + int ret = -1;
#define CHECK_MAX(val, _bool) \ do { \ - if (info.val##_max) { \ - if (!info.val) { \ + if (info->val##_max) { \ + if (!info->val) { \ if (QEMU_BLOCK_IOTUNE_SET_##_bool) { \ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \ _("cannot reset '%1$s' when '%2$s' is set"), \ @@ -15214,7 +15183,7 @@ qemuDomainSetBlockIoTune(virDomainPtr dom, } \ goto endjob; \ } \ - if (info.val##_max < info.val) { \ + if (info->val##_max < info->val) { \ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \ _("value '%1$s' cannot be smaller than '%2$s'"), \ #val "_max", #val); \ @@ -15232,6 +15201,96 @@ qemuDomainSetBlockIoTune(virDomainPtr dom,
#undef CHECK_MAX
+ ret = 0; + endjob: + return ret;
Same as above.
+} + + +static int +qemuDomainSetBlockIoTune(virDomainPtr dom, + const char *path, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virQEMUDriver *driver = dom->conn->privateData; + virDomainObj *vm = NULL; + qemuDomainObjPrivate *priv; + virDomainDef *def = NULL; + virDomainDef *persistentDef = NULL; + virDomainBlockIoTuneInfo info = { 0 }; + virDomainBlockIoTuneInfo conf_info = { 0 }; + int ret = -1; + virDomainDiskDef *conf_disk = NULL; + virDomainDiskDef *disk; + qemuBlockIoTuneSetFlags set_fields = 0; + g_autoptr(virQEMUDriverConfig) cfg = NULL; + virObjectEvent *event = NULL; + virTypedParameterPtr eventParams = NULL; + int eventNparams = 0; + int eventMaxparams = 0; + virDomainBlockIoTuneInfo *cur_info; + virDomainBlockIoTuneInfo *conf_cur_info; + + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + if (qemuDomainValidateBlockIoTune(params, nparams) < 0) + return -1; + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainSetBlockIoTuneEnsureACL(dom->conn, vm->def, flags) < 0) + goto cleanup; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + priv = vm->privateData; + + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, + VIR_DOMAIN_TUNABLE_BLKDEV_DISK, path) < 0) + goto endjob; + + if (qemuDomainSetBlockIoTuneFields(&info, + params, + nparams, + &set_fields, + &eventParams, + &eventNparams, + &eventMaxparams) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMutualExclusion(&info) < 0) + goto endjob; + + virDomainBlockIoTuneInfoCopy(&info, &conf_info); + + if (def) { + if (!(disk = qemuDomainDiskByName(def, path))) + goto endjob; + + if (!qemuDomainDiskBlockIoTuneIsSupported(disk)) + goto endjob; + + cur_info = qemuDomainFindGroupBlockIoTune(def, disk, &info); + + if (qemuDomainSetBlockIoTuneDefaults(&info, cur_info, set_fields) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneReset(disk, &info) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMax(&info) < 0) + goto endjob; + /* blockdev-based qemu doesn't want to set the throttling when a cdrom * is empty. Skip the monitor call here since we will set the throttling * once new media is inserted */ -- 2.39.5 (Apple Git-154)

From: Chun Feng Wu <danielwuwy@163.com> ThrottleGroup lifecycle implementation, note, in QOM, throttlegroup name is prefixed with "throttle-" to clearly separate throttle group objects into their own namespace. * "qemuDomainSetThrottleGroup", this method is to add("object-add") or update("qom-set") throttlegroup in QOM and update corresponding objects in DOM * "qemuDomainGetThrottleGroup", this method queries throttlegroup info by groupname * "qemuDomainDelThrottleGroup", this method checks if group is referenced by any throttle in disks and delete it if it's not used anymore * Check flag "QEMU_CAPS_OBJECT_JSON" during qemuDomainSetThrottleGroup when vm is active, throttle group feature requries such flag * "objectAddNoWrap"("props") check is done by reusing qemuMonitorAddObject in qemuDomainSetThrottleGroup Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_driver.c | 388 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e80e5fc511..d65d5fd2ff 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20111,6 +20111,390 @@ qemuDomainGraphicsReload(virDomainPtr domain, return ret; } + +/** + * qemuDomainCheckThrottleGroupAllZero: + * @newiotune: Pointer to iotune, which contains detailed items + * + * Check if params within @newiotune contain all zero values, if yes, + * return failure since it's meaningless to set all zero values in @newiotune + * + * Returns -1 on failure, or 0 on success. + */ +static int +qemuDomainCheckThrottleGroupAllZero(virDomainBlockIoTuneInfo *newiotune) +{ + if (virDomainBlockIoTuneInfoHasAny(newiotune)) + return 0; + + VIR_FREE(newiotune->group_name); + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("creating a new group/updating existing with all parameters zero is not supported")); + return -1; +} + + +static int +qemuDomainSetThrottleGroup(virDomainPtr dom, + const char *groupname, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virQEMUDriver *driver = dom->conn->privateData; + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainDef *persistentDef = NULL; + virDomainThrottleGroupDef info = { 0 }; + virDomainThrottleGroupDef conf_info = { 0 }; + int ret = -1; + qemuBlockIoTuneSetFlags set_fields = 0; + g_autoptr(virQEMUDriverConfig) cfg = NULL; + virObjectEvent *event = NULL; + virTypedParameterPtr eventParams = NULL; + int eventNparams = 0; + int eventMaxparams = 0; + virDomainThrottleGroupDef *cur_info; + virDomainThrottleGroupDef *conf_cur_info; + int rc = 0; + g_autoptr(virJSONValue) props = NULL; + g_autoptr(virJSONValue) limits = virJSONValueNewObject(); + qemuDomainObjPrivate *priv = NULL; + virQEMUCaps *qemuCaps = NULL; + + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + + if (qemuDomainValidateBlockIoTune(params, nparams) < 0) + return -1; + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainSetThrottleGroupEnsureACL(dom->conn, vm->def, flags) < 0) + goto cleanup; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, + VIR_DOMAIN_TUNABLE_BLKDEV_GROUP_NAME, groupname) < 0) + goto endjob; + + if (qemuDomainSetBlockIoTuneFields(&info, + params, + nparams, + &set_fields, + &eventParams, + &eventNparams, + &eventMaxparams) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMutualExclusion(&info) < 0) + goto endjob; + + virDomainThrottleGroupDefCopy(&info, &conf_info); + + priv = vm->privateData; + qemuCaps = priv->qemuCaps; + /* this throttle group feature requires "QEMU_CAPS_OBJECT_JSON" + * when starting domain later, so check such flag here as well */ + if (virDomainObjIsActive(vm)) { + if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("This QEMU doesn't support throttle group creation")); + return -1; + } + } + + if (def) { + if (qemuDomainCheckThrottleGroupAllZero(&info) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMax(&info) < 0) + goto endjob; + + cur_info = virDomainThrottleGroupByName(def, groupname); + /* Update existing group. */ + if (cur_info != NULL) { + if (qemuDomainSetBlockIoTuneDefaults(&info, cur_info, set_fields) < 0) + goto endjob; + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorUpdateThrottleGroup(qemuDomainGetMonitor(vm), + groupname, + &info); + qemuDomainObjExitMonitor(vm); + if (rc < 0) + goto endjob; + virDomainThrottleGroupUpdate(def, &info); + } else { + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", groupname); + + if (qemuMonitorThrottleGroupLimits(limits, &info)<0) + goto endjob; + if (qemuMonitorCreateObjectProps(&props, + "throttle-group", prefixed_group_name, + "a:limits", &limits, + NULL) < 0) + goto endjob; + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorAddObject(qemuDomainGetMonitor(vm), &props, NULL); + qemuDomainObjExitMonitor(vm); + if (rc < 0) + goto endjob; + virDomainThrottleGroupAdd(def, &info); + } + + qemuDomainSaveStatus(vm); + + if (eventNparams) { + event = virDomainEventTunableNewFromDom(dom, &eventParams, eventNparams); + virObjectEventStateQueue(driver->domainEventState, event); + } + } + + if (persistentDef) { + conf_cur_info = virDomainThrottleGroupByName(persistentDef, groupname); + + if (qemuDomainCheckThrottleGroupAllZero(&conf_info) < 0) + goto endjob; + + if (conf_cur_info != NULL) { + if (qemuDomainSetBlockIoTuneDefaults(&conf_info, conf_cur_info, + set_fields) < 0) + goto endjob; + virDomainThrottleGroupUpdate(persistentDef, &conf_info); + } else { + virDomainThrottleGroupAdd(persistentDef, &conf_info); + } + + + if (virDomainDefSave(persistentDef, driver->xmlopt, + cfg->configDir) < 0) + goto endjob; + } + + ret = 0; + endjob: + virDomainObjEndJob(vm); + + cleanup: + virDomainObjEndAPI(&vm); + virTypedParamsFree(eventParams, eventNparams); + return ret; +} + + +static int +qemuDomainGetThrottleGroup(virDomainPtr dom, + const char *groupname, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainDef *persistentDef = NULL; + virDomainThrottleGroupDef groupDef = { 0 }; + virDomainThrottleGroupDef *reply = &groupDef; + int ret = -1; + int maxparams = 0; + int rc = 0; + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainGetThrottleGroupEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (virDomainObjBeginJob(vm, VIR_JOB_QUERY) < 0) + goto cleanup; + + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + if (def) { + if (virDomainThrottleGroupByName(def, groupname) == NULL) { + virReportError(VIR_ERR_INVALID_ARG, + _("throttle group '%1$s' was not found in the domain config"), + groupname); + goto endjob; + } + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorGetThrottleGroup(qemuDomainGetMonitor(vm), groupname, reply); + qemuDomainObjExitMonitor(vm); + + if (rc < 0) + goto endjob; + } + + if (persistentDef) { + reply = virDomainThrottleGroupByName(persistentDef, groupname); + if (reply == NULL) { + virReportError(VIR_ERR_INVALID_ARG, + _("throttle group '%1$s' was not found in the domain config"), + groupname); + goto endjob; + } + reply->group_name = g_strdup(groupname); + } + + *nparams = 0; + +#define THROTTLE_GROUP_ASSIGN(name, var) \ + if (virTypedParamsAddULLong(params, \ + nparams, \ + &maxparams, \ + VIR_DOMAIN_BLOCK_IOTUNE_ ## name, \ + reply->var) < 0) \ + goto endjob; + + + if (virTypedParamsAddString(params, nparams, &maxparams, + VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME, + reply->group_name) < 0) + goto endjob; + + THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC, total_bytes_sec); + THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC, read_bytes_sec); + THROTTLE_GROUP_ASSIGN(WRITE_BYTES_SEC, write_bytes_sec); + + THROTTLE_GROUP_ASSIGN(TOTAL_IOPS_SEC, total_iops_sec); + THROTTLE_GROUP_ASSIGN(READ_IOPS_SEC, read_iops_sec); + THROTTLE_GROUP_ASSIGN(WRITE_IOPS_SEC, write_iops_sec); + + THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC_MAX, total_bytes_sec_max); + THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC_MAX, read_bytes_sec_max); + THROTTLE_GROUP_ASSIGN(WRITE_BYTES_SEC_MAX, write_bytes_sec_max); + + THROTTLE_GROUP_ASSIGN(TOTAL_IOPS_SEC_MAX, total_iops_sec_max); + THROTTLE_GROUP_ASSIGN(READ_IOPS_SEC_MAX, read_iops_sec_max); + THROTTLE_GROUP_ASSIGN(WRITE_IOPS_SEC_MAX, write_iops_sec_max); + + THROTTLE_GROUP_ASSIGN(SIZE_IOPS_SEC, size_iops_sec); + + THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC_MAX_LENGTH, total_bytes_sec_max_length); + THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC_MAX_LENGTH, read_bytes_sec_max_length); + THROTTLE_GROUP_ASSIGN(WRITE_BYTES_SEC_MAX_LENGTH, write_bytes_sec_max_length); + + THROTTLE_GROUP_ASSIGN(TOTAL_IOPS_SEC_MAX_LENGTH, total_iops_sec_max_length); + THROTTLE_GROUP_ASSIGN(READ_IOPS_SEC_MAX_LENGTH, read_iops_sec_max_length); + THROTTLE_GROUP_ASSIGN(WRITE_IOPS_SEC_MAX_LENGTH, write_iops_sec_max_length); +#undef THROTTLE_GROUP_ASSIGN + + ret = 0; + + endjob: + virDomainObjEndJob(vm); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static int +qemuDomainCheckThrottleGroupRef(virDomainDef *def, + const char *group_name) +{ + size_t i; + for (i = 0; i < def->ndisks; i++) { + virDomainDiskDef *disk = def->disks[i]; + if (virDomainThrottleFilterFind(disk, group_name)) { + virReportError(VIR_ERR_INVALID_ARG, + _("throttle group '%1$s' is still being used by disk %2$s"), + group_name, disk->dst); + return -1; + } + } + return 0; +} + + +static int +qemuDomainDelThrottleGroup(virDomainPtr dom, + const char *groupname, + unsigned int flags) +{ + virQEMUDriver *driver = dom->conn->privateData; + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainDef *persistentDef = NULL; + g_autoptr(virQEMUDriverConfig) cfg = NULL; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainDelThrottleGroupEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + /* check if this group is still being used by disks, catch errors upfront and + * avoid a partial success with error reported */ + if (def) { + if (qemuDomainCheckThrottleGroupRef(def, groupname) < 0) + goto endjob; + } + if (persistentDef) { + if (qemuDomainCheckThrottleGroupRef(persistentDef, groupname) < 0) + goto endjob; + } + + if (def) { + int rc = 0; + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", groupname); + + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorDelObject(qemuDomainGetMonitor(vm), prefixed_group_name, true); + qemuDomainObjExitMonitor(vm); + + if (rc < 0) + goto endjob; + + virDomainThrottleGroupDel(def, groupname); + qemuDomainSaveStatus(vm); + } + + if (persistentDef) { + cfg = virQEMUDriverGetConfig(driver); + virDomainThrottleGroupDel(persistentDef, groupname); + if (virDomainDefSave(persistentDef, driver->xmlopt, + cfg->configDir) < 0) + goto endjob; + } + + ret = 0; + + endjob: + virDomainObjEndJob(vm); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + static virHypervisorDriver qemuHypervisorDriver = { .name = QEMU_DRIVER_NAME, .connectURIProbe = qemuConnectURIProbe, @@ -20361,6 +20745,10 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainSetLaunchSecurityState = qemuDomainSetLaunchSecurityState, /* 8.0.0 */ .domainFDAssociate = qemuDomainFDAssociate, /* 9.0.0 */ .domainGraphicsReload = qemuDomainGraphicsReload, /* 10.2.0 */ + .domainSetThrottleGroup = qemuDomainSetThrottleGroup, /* 10.7.0 */ + .domainGetThrottleGroup = qemuDomainGetThrottleGroup, /* 10.7.0 */ + .domainDelThrottleGroup = qemuDomainDelThrottleGroup, /* 10.7.0 */ + }; -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:17 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
ThrottleGroup lifecycle implementation, note, in QOM, throttlegroup name is prefixed with "throttle-" to clearly separate throttle group objects into their own namespace. * "qemuDomainSetThrottleGroup", this method is to add("object-add") or update("qom-set") throttlegroup in QOM and update corresponding objects in DOM * "qemuDomainGetThrottleGroup", this method queries throttlegroup info by groupname * "qemuDomainDelThrottleGroup", this method checks if group is referenced by any throttle in disks and delete it if it's not used anymore * Check flag "QEMU_CAPS_OBJECT_JSON" during qemuDomainSetThrottleGroup when vm is active, throttle group feature requries such flag * "objectAddNoWrap"("props") check is done by reusing qemuMonitorAddObject in qemuDomainSetThrottleGroup
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_driver.c | 388 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e80e5fc511..d65d5fd2ff 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20111,6 +20111,390 @@ qemuDomainGraphicsReload(virDomainPtr domain, return ret; }
+ +/** + * qemuDomainCheckThrottleGroupAllZero: + * @newiotune: Pointer to iotune, which contains detailed items + * + * Check if params within @newiotune contain all zero values, if yes, + * return failure since it's meaningless to set all zero values in @newiotune + * + * Returns -1 on failure, or 0 on success. + */ +static int +qemuDomainCheckThrottleGroupAllZero(virDomainBlockIoTuneInfo *newiotune) +{ + if (virDomainBlockIoTuneInfoHasAny(newiotune)) + return 0; + + VIR_FREE(newiotune->group_name); + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("creating a new group/updating existing with all parameters zero is not supported")); + return -1; +} + + +static int +qemuDomainSetThrottleGroup(virDomainPtr dom, + const char *groupname, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virQEMUDriver *driver = dom->conn->privateData; + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainDef *persistentDef = NULL; + virDomainThrottleGroupDef info = { 0 }; + virDomainThrottleGroupDef conf_info = { 0 }; + int ret = -1; + qemuBlockIoTuneSetFlags set_fields = 0; + g_autoptr(virQEMUDriverConfig) cfg = NULL; + virObjectEvent *event = NULL; + virTypedParameterPtr eventParams = NULL; + int eventNparams = 0; + int eventMaxparams = 0; + virDomainThrottleGroupDef *cur_info; + virDomainThrottleGroupDef *conf_cur_info; + int rc = 0; + g_autoptr(virJSONValue) props = NULL; + g_autoptr(virJSONValue) limits = virJSONValueNewObject(); + qemuDomainObjPrivate *priv = NULL; + virQEMUCaps *qemuCaps = NULL; + + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + + if (qemuDomainValidateBlockIoTune(params, nparams) < 0) + return -1; + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainSetThrottleGroupEnsureACL(dom->conn, vm->def, flags) < 0) + goto cleanup; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, + VIR_DOMAIN_TUNABLE_BLKDEV_GROUP_NAME, groupname) < 0) + goto endjob; + + if (qemuDomainSetBlockIoTuneFields(&info, + params, + nparams, + &set_fields, + &eventParams, + &eventNparams, + &eventMaxparams) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMutualExclusion(&info) < 0) + goto endjob; + + virDomainThrottleGroupDefCopy(&info, &conf_info);
The code from qemuDomainCheckThrottleGroupAllZero should be inlined here as it doesn't make to check the data twice.
+ + priv = vm->privateData; + qemuCaps = priv->qemuCaps; + /* this throttle group feature requires "QEMU_CAPS_OBJECT_JSON" + * when starting domain later, so check such flag here as well */ + if (virDomainObjIsActive(vm)) { + if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("This QEMU doesn't support throttle group creation")); + return -1; + } + } + + if (def) { + if (qemuDomainCheckThrottleGroupAllZero(&info) < 0) + goto endjob; + + if (qemuDomainCheckBlockIoTuneMax(&info) < 0) + goto endjob; + + cur_info = virDomainThrottleGroupByName(def, groupname); + /* Update existing group. */ + if (cur_info != NULL) { + if (qemuDomainSetBlockIoTuneDefaults(&info, cur_info, set_fields) < 0) + goto endjob; + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorUpdateThrottleGroup(qemuDomainGetMonitor(vm), + groupname, + &info); + qemuDomainObjExitMonitor(vm); + if (rc < 0) + goto endjob; + virDomainThrottleGroupUpdate(def, &info); + } else { + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", groupname); + + if (qemuMonitorThrottleGroupLimits(limits, &info)<0) + goto endjob; + if (qemuMonitorCreateObjectProps(&props, + "throttle-group", prefixed_group_name, + "a:limits", &limits, + NULL) < 0) + goto endjob; + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorAddObject(qemuDomainGetMonitor(vm), &props, NULL); + qemuDomainObjExitMonitor(vm); + if (rc < 0) + goto endjob; + virDomainThrottleGroupAdd(def, &info); + } + + qemuDomainSaveStatus(vm); + + if (eventNparams) { + event = virDomainEventTunableNewFromDom(dom, &eventParams, eventNparams); + virObjectEventStateQueue(driver->domainEventState, event); + } + } + + if (persistentDef) { + conf_cur_info = virDomainThrottleGroupByName(persistentDef, groupname); + + if (qemuDomainCheckThrottleGroupAllZero(&conf_info) < 0) + goto endjob; + + if (conf_cur_info != NULL) { + if (qemuDomainSetBlockIoTuneDefaults(&conf_info, conf_cur_info, + set_fields) < 0) + goto endjob; + virDomainThrottleGroupUpdate(persistentDef, &conf_info); + } else { + virDomainThrottleGroupAdd(persistentDef, &conf_info); + } + + + if (virDomainDefSave(persistentDef, driver->xmlopt, + cfg->configDir) < 0) + goto endjob; + } + + ret = 0; + endjob: + virDomainObjEndJob(vm); + + cleanup: + virDomainObjEndAPI(&vm); + virTypedParamsFree(eventParams, eventNparams); + return ret; +} + + +static int +qemuDomainGetThrottleGroup(virDomainPtr dom, + const char *groupname, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags)
As noted, this getter is mostly pointless as it just returns what should have been recorded in the XML in the first place so I don't think this makes too much sense to have.
+{ + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainDef *persistentDef = NULL; + virDomainThrottleGroupDef groupDef = { 0 }; + virDomainThrottleGroupDef *reply = &groupDef; + int ret = -1; + int maxparams = 0; + int rc = 0; + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainGetThrottleGroupEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (virDomainObjBeginJob(vm, VIR_JOB_QUERY) < 0) + goto cleanup; + + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + if (def) { + if (virDomainThrottleGroupByName(def, groupname) == NULL) { + virReportError(VIR_ERR_INVALID_ARG, + _("throttle group '%1$s' was not found in the domain config"), + groupname); + goto endjob; + } + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorGetThrottleGroup(qemuDomainGetMonitor(vm), groupname, reply); + qemuDomainObjExitMonitor(vm); + + if (rc < 0) + goto endjob; + } + + if (persistentDef) { + reply = virDomainThrottleGroupByName(persistentDef, groupname); + if (reply == NULL) { + virReportError(VIR_ERR_INVALID_ARG, + _("throttle group '%1$s' was not found in the domain config"), + groupname); + goto endjob; + } + reply->group_name = g_strdup(groupname); + }
As noted you can't really combide VIR_DOMAIN_AFFECT_LIVE VIR_DOMAIN_AFFECT_CONFIG as it'll return garbage.
+ + *nparams = 0; + +#define THROTTLE_GROUP_ASSIGN(name, var) \ + if (virTypedParamsAddULLong(params, \ + nparams, \ + &maxparams, \ + VIR_DOMAIN_BLOCK_IOTUNE_ ## name, \ + reply->var) < 0) \ + goto endjob; + + + if (virTypedParamsAddString(params, nparams, &maxparams, + VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME, + reply->group_name) < 0) + goto endjob; + + THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC, total_bytes_sec); + THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC, read_bytes_sec); + THROTTLE_GROUP_ASSIGN(WRITE_BYTES_SEC, write_bytes_sec); + + THROTTLE_GROUP_ASSIGN(TOTAL_IOPS_SEC, total_iops_sec); + THROTTLE_GROUP_ASSIGN(READ_IOPS_SEC, read_iops_sec); + THROTTLE_GROUP_ASSIGN(WRITE_IOPS_SEC, write_iops_sec); + + THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC_MAX, total_bytes_sec_max); + THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC_MAX, read_bytes_sec_max);
The rest looks reasonable.

From: Chun Feng Wu <danielwuwy@163.com> It contains throttle filter nodename processing(new nodename, topnodename, parse and format nodename), throttle filter attaching/detaching preparation and implementation. * Updated "qemuDomainDiskGetTopNodename", so if throttlefilter is used together with copyOnRead, top node is throttle filter node, e.g. device -> throttle -> copyOnRead Layer-> image chain * In qemuBuildThrottleFiltersAttachPrepareBlockdev, if copy_on_read is on, build throttle nodename chain on top of copy_on_read nodename * In status xml, throttle filter nodename(virDomainDiskDef.nodename) is saved at disk/privateData/nodenames/nodename(type='throttle-filter'), corresponding parse/format sits in qemuDomainDiskPrivateParse and qemuDomainDiskPrivateFormat * If filter nodename hasn't been set by qemuDomainDiskPrivateParse, in qemuDomainPrepareThrottleFilterBlockdev, filter nodename index can be generated by reusing qemuDomainStorageIDNew and current global sequence number is persistented in virDomainObj- >privateData(qemuDomainObjPrivate)->nodenameindex. qemuDomainPrepareThrottleFilterBlockdev is called by qemuDomainPrepareDiskSourceBlockdev, which in turn used by both hotplug and qemuProcessStart to prepare throttle filter node name * Define method qemuBlockThrottleFilterGetProps, which is used by both hotplug and command to build throttle object for QEMU * Define methods for throttle filter attach/detach/rollback Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_block.c | 136 ++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_block.h | 49 +++++++++++++++ src/qemu/qemu_command.c | 81 ++++++++++++++++++++++++ src/qemu/qemu_command.h | 6 ++ src/qemu/qemu_domain.c | 73 +++++++++++++++++++-- 5 files changed, 341 insertions(+), 4 deletions(-) diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 692b4d350e..5ff7319ceb 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -2755,6 +2755,142 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, } +void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename) +{ + g_free(filter->nodename); + filter->nodename = nodename; +} + + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter) +{ + return filter->nodename; +} + + +/** + * qemuBlockThrottleFilterGetProps: + * @filter: throttle filter + * @parentNodeName: parent nodename of @filter + * + * Build "arguments" part of "blockdev-add" QMP cmd. + */ +static virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName) +{ + g_autoptr(virJSONValue) props = NULL; + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", filter->group_name); + if (virJSONValueObjectAdd(&props, + "s:driver", "throttle", + "s:node-name", qemuBlockThrottleFilterGetNodename(filter), + "s:throttle-group", prefixed_group_name, + "s:file", parentNodeName, + NULL) < 0) + return NULL; + + return g_steal_pointer(&props); +} + + +void +qemuBlockThrottleFilterAttachDataFree(qemuBlockThrottleFilterAttachData *data) +{ + if (!data) + return; + + virJSONValueFree(data->filterProps); + g_free(data); +} + + +qemuBlockThrottleFilterAttachData * +qemuBlockThrottleFilterAttachPrepareBlockdev(virDomainThrottleFilterDef *filter, + const char *parentNodeName) +{ + g_autoptr(qemuBlockThrottleFilterAttachData) data = NULL; + + data = g_new0(qemuBlockThrottleFilterAttachData, 1); + + if (!(data->filterProps = qemuBlockThrottleFilterGetProps(filter, parentNodeName))) + return NULL; + + data->filterNodeName = qemuBlockThrottleFilterGetNodename(filter); + + return g_steal_pointer(&data); +} + + +void +qemuBlockThrottleFilterAttachRollback(qemuMonitor *mon, + qemuBlockThrottleFilterAttachData *data) +{ + virErrorPtr orig_err; + + virErrorPreserveLast(&orig_err); + + if (data->filterAttached) + ignore_value(qemuMonitorBlockdevDel(mon, data->filterNodeName)); + + virErrorRestore(&orig_err); +} + + +void +qemuBlockThrottleFiltersDataFree(qemuBlockThrottleFiltersData *data) +{ + size_t i; + + if (!data) + return; + + for (i = 0; i < data->nfilterdata; i++) + qemuBlockThrottleFilterAttachDataFree(data->filterdata[i]); + + g_free(data->filterdata); + g_free(data); +} + + +/** + * qemuBlockThrottleFiltersAttach: + * @mon: monitor object + * @data: filter chain data + * + * Attach throttle filters. + * Caller must enter @mon prior calling this function. + */ +int +qemuBlockThrottleFiltersAttach(qemuMonitor *mon, + qemuBlockThrottleFiltersData *data) +{ + size_t i; + + for (i = 0; i < data->nfilterdata; i++) { + if (qemuMonitorBlockdevAdd(mon, &data->filterdata[i]->filterProps) < 0) + return -1; + data->filterdata[i]->filterAttached = true; + } + + return 0; +} + + +void +qemuBlockThrottleFiltersDetach(qemuMonitor *mon, + qemuBlockThrottleFiltersData *data) +{ + size_t i; + + for (i = data->nfilterdata; i > 0; i--) + qemuBlockThrottleFilterAttachRollback(mon, data->filterdata[i-1]); +} + + int qemuBlockRemoveImageMetadata(virQEMUDriver *driver, virDomainObj *vm, diff --git a/src/qemu/qemu_block.h b/src/qemu/qemu_block.h index f13a4a4a9a..b9e950e494 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -215,6 +215,55 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, virStorageSource *src, virStorageSource *templ); +void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename); + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter); + +typedef struct qemuBlockThrottleFilterAttachData qemuBlockThrottleFilterAttachData; +struct qemuBlockThrottleFilterAttachData { + virJSONValue *filterProps; + const char *filterNodeName; + bool filterAttached; +}; + +qemuBlockThrottleFilterAttachData * +qemuBlockThrottleFilterAttachPrepareBlockdev(virDomainThrottleFilterDef *throttlefilter, + const char *parentNodeName); + +void +qemuBlockThrottleFilterAttachDataFree(qemuBlockThrottleFilterAttachData *data); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockThrottleFilterAttachData, + qemuBlockThrottleFilterAttachDataFree); + +void +qemuBlockThrottleFilterAttachRollback(qemuMonitor *mon, + qemuBlockThrottleFilterAttachData *data); + +struct _qemuBlockThrottleFiltersData { + qemuBlockThrottleFilterAttachData **filterdata; + size_t nfilterdata; +}; + +typedef struct _qemuBlockThrottleFiltersData qemuBlockThrottleFiltersData; + +void +qemuBlockThrottleFiltersDataFree(qemuBlockThrottleFiltersData *data); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockThrottleFiltersData, + qemuBlockThrottleFiltersDataFree); + +int +qemuBlockThrottleFiltersAttach(qemuMonitor *mon, + qemuBlockThrottleFiltersData *data); + +void +qemuBlockThrottleFiltersDetach(qemuMonitor *mon, + qemuBlockThrottleFiltersData *data); + int qemuBlockRemoveImageMetadata(virQEMUDriver *driver, virDomainObj *vm, diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 696f891b47..0c119c8827 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -10996,6 +10996,87 @@ qemuBuildStorageSourceChainAttachPrepareBlockdevOne(qemuBlockStorageSourceChainD } +/** + * qemuBuildThrottleFiltersAttachPrepareBlockdevOne: + * @data: filter chain data, which consists of array of filters and size of such array + * @throttlefilter: new filter to be added into filter array + * @parentNodeName: parent nodename for this new throttlefilter + * + * Build filter node chain to provide more flexibility for block disk I/O limits + */ +static int +qemuBuildThrottleFiltersAttachPrepareBlockdevOne(qemuBlockThrottleFiltersData *data, + virDomainThrottleFilterDef *throttlefilter, + const char *parentNodeName) +{ + g_autoptr(qemuBlockThrottleFilterAttachData) elem = NULL; + + if (!(elem = qemuBlockThrottleFilterAttachPrepareBlockdev(throttlefilter, parentNodeName))) + return -1; + + VIR_APPEND_ELEMENT(data->filterdata, data->nfilterdata, elem); + return 0; +} + + +/** + * qemuBuildThrottleFiltersAttachPrepareBlockdev: + * @disk: domain disk + * + * Build filter node chain to provide more flexibility for block disk I/O limits + */ +qemuBlockThrottleFiltersData * +qemuBuildThrottleFiltersAttachPrepareBlockdev(virDomainDiskDef *disk) +{ + g_autoptr(qemuBlockThrottleFiltersData) data = NULL; + size_t i; + const char * parentNodeName = NULL; + qemuDomainDiskPrivate *priv = QEMU_DOMAIN_DISK_PRIVATE(disk); + + data = g_new0(qemuBlockThrottleFiltersData, 1); + /* if copy_on_read is enabled, put throttle chain on top of it */ + if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) { + parentNodeName = priv->nodeCopyOnRead; + } else { + /* get starting parentNodename, e.g. libvirt-1-format */ + parentNodeName = qemuBlockStorageSourceGetEffectiveNodename(disk->src); + } + /* build filterdata, which contains all filters info and sequence info through parentNodeName */ + for (i = 0; i < disk->nthrottlefilters; i++) { + if (qemuBuildThrottleFiltersAttachPrepareBlockdevOne(data, disk->throttlefilters[i], parentNodeName) < 0) + return NULL; + parentNodeName = disk->throttlefilters[i]->nodename; + } + + return g_steal_pointer(&data); +} + + +/** + * qemuBuildThrottleFiltersDetachPrepareBlockdev: + * @disk: domain disk + * + * Build filters data for later "blockdev-del" + */ +qemuBlockThrottleFiltersData * +qemuBuildThrottleFiltersDetachPrepareBlockdev(virDomainDiskDef *disk) +{ + g_autoptr(qemuBlockThrottleFiltersData) data = g_new0(qemuBlockThrottleFiltersData, 1); + size_t i; + + /* build filterdata, which contains filters info and sequence info */ + for (i = 0; i < disk->nthrottlefilters; i++) { + g_autoptr(qemuBlockThrottleFilterAttachData) elem = g_new0(qemuBlockThrottleFilterAttachData, 1); + /* ignore other fields since the following info are enough for "blockdev-del" */ + elem->filterNodeName = qemuBlockThrottleFilterGetNodename(disk->throttlefilters[i]); + elem->filterAttached = true; + + VIR_APPEND_ELEMENT(data->filterdata, data->nfilterdata, elem); + } + return g_steal_pointer(&data); +} + + /** * qemuBuildStorageSourceChainAttachPrepareBlockdev: * @top: storage source chain diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h index 76c514b5f7..1b0296ea42 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -116,6 +116,12 @@ qemuBlockStorageSourceChainData * qemuBuildStorageSourceChainAttachPrepareBlockdevTop(virStorageSource *top, virStorageSource *backingStore); +qemuBlockThrottleFiltersData * +qemuBuildThrottleFiltersAttachPrepareBlockdev(virDomainDiskDef *disk); + +qemuBlockThrottleFiltersData * +qemuBuildThrottleFiltersDetachPrepareBlockdev(virDomainDiskDef *disk); + virJSONValue * qemuBuildDiskDeviceProps(const virDomainDef *def, virDomainDiskDef *disk, diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index f3b810a564..b6acf895c3 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2235,6 +2235,34 @@ qemuStorageSourcePrivateDataFormat(virStorageSource *src, } +static int +virDomainDiskThrottleFilterNodeNamesParse(xmlXPathContextPtr ctxt, + virDomainDiskDef *def) +{ + size_t i; + int n = 0; + g_autofree xmlNodePtr *nodes = NULL; + g_autoptr(GHashTable) throttleFiltersMap = virHashNew(g_free); + + if ((n = virXPathNodeSet("./nodenames/nodename[@type='throttle-filter']", ctxt, &nodes)) < 0) + return -1; + + for (i = 0; i < n; i++) { + g_hash_table_insert(throttleFiltersMap, virXMLPropString(nodes[i], "group"), virXMLPropString(nodes[i], "name")); + } + + for (i = 0; i < def->nthrottlefilters; i++) { + char* nodename = g_hash_table_lookup(throttleFiltersMap, def->throttlefilters[i]->group_name); + if (nodename) { + /* first time to set nodename in filter */ + def->throttlefilters[i]->nodename = g_strdup(nodename); + } + } + + return 0; +} + + static int qemuDomainDiskPrivateParse(xmlXPathContextPtr ctxt, virDomainDiskDef *disk) @@ -2244,6 +2272,9 @@ qemuDomainDiskPrivateParse(xmlXPathContextPtr ctxt, priv->qomName = virXPathString("string(./qom/@name)", ctxt); priv->nodeCopyOnRead = virXPathString("string(./nodenames/nodename[@type='copyOnRead']/@name)", ctxt); + if (virDomainDiskThrottleFilterNodeNamesParse(ctxt, disk) < 0) + return -1; + return 0; } @@ -2253,14 +2284,22 @@ qemuDomainDiskPrivateFormat(virDomainDiskDef *disk, virBuffer *buf) { qemuDomainDiskPrivate *priv = QEMU_DOMAIN_DISK_PRIVATE(disk); + size_t i; virBufferEscapeString(buf, "<qom name='%s'/>\n", priv->qomName); - if (priv->nodeCopyOnRead) { + if (priv->nodeCopyOnRead || disk->nthrottlefilters > 0) { virBufferAddLit(buf, "<nodenames>\n"); virBufferAdjustIndent(buf, 2); - virBufferEscapeString(buf, "<nodename type='copyOnRead' name='%s'/>\n", - priv->nodeCopyOnRead); + if (priv->nodeCopyOnRead) + virBufferEscapeString(buf, "<nodename type='copyOnRead' name='%s'/>\n", + priv->nodeCopyOnRead); + if (disk->nthrottlefilters > 0) { + for (i = 0; i < disk->nthrottlefilters; i++) { + virBufferEscapeString(buf, "<nodename type='throttle-filter' name='%s' ", disk->throttlefilters[i]->nodename); + virBufferEscapeString(buf, "group='%s'/>\n", disk->throttlefilters[i]->group_name); + } + } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "</nodenames>\n"); } @@ -6348,7 +6387,8 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, * @disk: disk definition object * * Returns the pointer to the node-name of the topmost layer used by @disk as - * backend. Currently returns the nodename of the copy-on-read filter if enabled + * backend. Currently returns the nodename of top throttle filter if enabled + * or the nodename of the copy-on-read filter if enabled * or the nodename of the top image's format driver. Empty disks return NULL. * This must be used only with disks instantiated via -blockdev (thus not * for SD cards). @@ -6361,6 +6401,10 @@ qemuDomainDiskGetTopNodename(virDomainDiskDef *disk) if (virStorageSourceIsEmpty(disk->src)) return NULL; + /* If disk has throttles, take top throttle node name */ + if (disk->nthrottlefilters > 0) + return disk->throttlefilters[disk->nthrottlefilters - 1]->nodename; + if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) return priv->nodeCopyOnRead; @@ -9724,6 +9768,22 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, } +static void +qemuDomainPrepareThrottleFilterBlockdev(virDomainThrottleFilterDef *filter, + qemuDomainObjPrivate *priv) +{ + g_autofree char *nodenameprefix = NULL; + + /* skip setting throttle filter nodename if it's set by parsing statusxml */ + if (filter->nodename) { + return; + } + nodenameprefix = g_strdup_printf("libvirt-%u", qemuDomainStorageIDNew(priv)); + /* generate and set nodename into filter for later QEMU cmd preparation */ + qemuBlockThrottleFilterSetNodename(filter, g_strdup_printf("%s-filter", nodenameprefix)); +} + + int qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDef *disk, virStorageSource *src, @@ -9747,6 +9807,7 @@ qemuDomainPrepareDiskSourceBlockdev(virDomainDiskDef *disk, { qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); virStorageSource *n; + size_t i; if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON && !diskPriv->nodeCopyOnRead) @@ -9757,6 +9818,10 @@ qemuDomainPrepareDiskSourceBlockdev(virDomainDiskDef *disk, return -1; } + for (i = 0; i < disk->nthrottlefilters; i++) { + qemuDomainPrepareThrottleFilterBlockdev(disk->throttlefilters[i], priv); + } + return 0; } -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:18 +0530, Harikumar R wrote: [...]
--- src/qemu/qemu_block.c | 136 ++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_block.h | 49 +++++++++++++++ src/qemu/qemu_command.c | 81 ++++++++++++++++++++++++ src/qemu/qemu_command.h | 6 ++ src/qemu/qemu_domain.c | 73 +++++++++++++++++++-- 5 files changed, 341 insertions(+), 4 deletions(-)
Note this is a preliminary comment; please don't send an updated version just because of this: Fails to compile: ../../../libvirt/src/qemu/qemu_domain.c: In function ‘qemuDomainPrepareDiskSourceBlockdev’: ../../../libvirt/src/qemu/qemu_domain.c:9708:12: error: unused variable ‘i’ [-Werror=unused-variable] 9708 | size_t i; | ^ ../../../libvirt/src/qemu/qemu_domain.c: In function ‘qemuDomainPrepareDiskSource’: ../../../libvirt/src/qemu/qemu_domain.c:9754:10: error: ‘i’ undeclared (first use in this function); did you mean ‘ip’? 9754 | for (i = 0; i < disk->nthrottlefilters; i++) { | ^ | ip "If you're going to submit multiple patches, the automated tests must pass after each patch, not just after the last one." https://www.libvirt.org/hacking.html#preparing-patches

On Mon, Nov 18, 2024 at 19:24:18 +0530, Harikumar R wrote: > From: Chun Feng Wu <danielwuwy@163.com> > > It contains throttle filter nodename processing(new nodename, > topnodename, parse and format nodename), throttle filter > attaching/detaching preparation and implementation. > > * Updated "qemuDomainDiskGetTopNodename", so if throttlefilter is used > together with copyOnRead, top node is throttle filter node, e.g. > device -> throttle -> copyOnRead Layer-> image chain > * In qemuBuildThrottleFiltersAttachPrepareBlockdev, if copy_on_read > is on, build throttle nodename chain on top of copy_on_read nodename > * In status xml, throttle filter nodename(virDomainDiskDef.nodename) is > saved at disk/privateData/nodenames/nodename(type='throttle-filter'), > corresponding parse/format sits in qemuDomainDiskPrivateParse and > qemuDomainDiskPrivateFormat > * If filter nodename hasn't been set by qemuDomainDiskPrivateParse, > in qemuDomainPrepareThrottleFilterBlockdev, filter nodename index > can be generated by reusing qemuDomainStorageIDNew and current > global sequence number is persistented in virDomainObj- > >privateData(qemuDomainObjPrivate)->nodenameindex. > qemuDomainPrepareThrottleFilterBlockdev is called by > qemuDomainPrepareDiskSourceBlockdev, which in turn used by both > hotplug and qemuProcessStart to prepare throttle filter node name > * Define method qemuBlockThrottleFilterGetProps, which is used by > both hotplug and command to build throttle object for QEMU > * Define methods for throttle filter attach/detach/rollback > > Signed-off-by: Chun Feng Wu <danielwuwy@163.com> > --- > src/qemu/qemu_block.c | 136 ++++++++++++++++++++++++++++++++++++++++ > src/qemu/qemu_block.h | 49 +++++++++++++++ > src/qemu/qemu_command.c | 81 ++++++++++++++++++++++++ > src/qemu/qemu_command.h | 6 ++ > src/qemu/qemu_domain.c | 73 +++++++++++++++++++-- > 5 files changed, 341 insertions(+), 4 deletions(-) > > diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c > index 692b4d350e..5ff7319ceb 100644 > --- a/src/qemu/qemu_block.c > +++ b/src/qemu/qemu_block.c > @@ -2755,6 +2755,142 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, > } > > > +void > +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, > + char *nodename) > +{ > + g_free(filter->nodename); > + filter->nodename = nodename; > +} > + > + > +const char * > +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter) > +{ > + return filter->nodename; > +} > + > + > +/** > + * qemuBlockThrottleFilterGetProps: > + * @filter: throttle filter > + * @parentNodeName: parent nodename of @filter > + * > + * Build "arguments" part of "blockdev-add" QMP cmd. > + */ > +static virJSONValue * > +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, > + const char *parentNodeName) > +{ > + g_autoptr(virJSONValue) props = NULL; > + /* prefix group name with "throttle-" in QOM */ > + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", filter->group_name); > + if (virJSONValueObjectAdd(&props, > + "s:driver", "throttle", > + "s:node-name", qemuBlockThrottleFilterGetNodename(filter), > + "s:throttle-group", prefixed_group_name, > + "s:file", parentNodeName, > + NULL) < 0) > + return NULL; > + > + return g_steal_pointer(&props); > +} > + > + > +void > +qemuBlockThrottleFilterAttachDataFree(qemuBlockThrottleFilterAttachData *data) > +{ > + if (!data) > + return; > + > + virJSONValueFree(data->filterProps); > + g_free(data); > +} > + > + > +qemuBlockThrottleFilterAttachData * > +qemuBlockThrottleFilterAttachPrepareBlockdev(virDomainThrottleFilterDef *filter, > + const char *parentNodeName) > +{ > + g_autoptr(qemuBlockThrottleFilterAttachData) data = NULL; > + > + data = g_new0(qemuBlockThrottleFilterAttachData, 1); > + > + if (!(data->filterProps = qemuBlockThrottleFilterGetProps(filter, parentNodeName))) > + return NULL; The above call can be inlined, there's a bit too much indirection going on here. > + > + data->filterNodeName = qemuBlockThrottleFilterGetNodename(filter); > + > + return g_steal_pointer(&data); > +} > + > + > +void > +qemuBlockThrottleFilterAttachRollback(qemuMonitor *mon, > + qemuBlockThrottleFilterAttachData *data) > +{ > + virErrorPtr orig_err; > + > + virErrorPreserveLast(&orig_err); > + > + if (data->filterAttached) > + ignore_value(qemuMonitorBlockdevDel(mon, data->filterNodeName)); > + > + virErrorRestore(&orig_err); > +} > + > + > +void > +qemuBlockThrottleFiltersDataFree(qemuBlockThrottleFiltersData *data) > +{ > + size_t i; > + > + if (!data) > + return; > + > + for (i = 0; i < data->nfilterdata; i++) > + qemuBlockThrottleFilterAttachDataFree(data->filterdata[i]); > + > + g_free(data->filterdata); > + g_free(data); > +} > + > + > +/** > + * qemuBlockThrottleFiltersAttach: > + * @mon: monitor object > + * @data: filter chain data > + * > + * Attach throttle filters. > + * Caller must enter @mon prior calling this function. > + */ > +int > +qemuBlockThrottleFiltersAttach(qemuMonitor *mon, > + qemuBlockThrottleFiltersData *data) > +{ > + size_t i; > + > + for (i = 0; i < data->nfilterdata; i++) { > + if (qemuMonitorBlockdevAdd(mon, &data->filterdata[i]->filterProps) < 0) > + return -1; > + data->filterdata[i]->filterAttached = true; > + } > + > + return 0; > +} > + > + > +void > +qemuBlockThrottleFiltersDetach(qemuMonitor *mon, > + qemuBlockThrottleFiltersData *data) > +{ > + size_t i; > + > + for (i = data->nfilterdata; i > 0; i--) > + qemuBlockThrottleFilterAttachRollback(mon, data->filterdata[i-1]); > +} > + > + > int > qemuBlockRemoveImageMetadata(virQEMUDriver *driver, > virDomainObj *vm, > diff --git a/src/qemu/qemu_block.h b/src/qemu/qemu_block.h > index f13a4a4a9a..b9e950e494 100644 > --- a/src/qemu/qemu_block.h > +++ b/src/qemu/qemu_block.h > @@ -215,6 +215,55 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, > virStorageSource *src, > virStorageSource *templ); > > +void > +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, > + char *nodename); > + > +const char * > +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter); > + > +typedef struct qemuBlockThrottleFilterAttachData qemuBlockThrottleFilterAttachData; > +struct qemuBlockThrottleFilterAttachData { > + virJSONValue *filterProps; > + const char *filterNodeName; > + bool filterAttached; > +}; > + > +qemuBlockThrottleFilterAttachData * > +qemuBlockThrottleFilterAttachPrepareBlockdev(virDomainThrottleFilterDef *throttlefilter, > + const char *parentNodeName); > + > +void > +qemuBlockThrottleFilterAttachDataFree(qemuBlockThrottleFilterAttachData *data); > + > +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockThrottleFilterAttachData, > + qemuBlockThrottleFilterAttachDataFree); > + > +void > +qemuBlockThrottleFilterAttachRollback(qemuMonitor *mon, > + qemuBlockThrottleFilterAttachData *data); > + > +struct _qemuBlockThrottleFiltersData { > + qemuBlockThrottleFilterAttachData **filterdata; > + size_t nfilterdata; > +}; > + > +typedef struct _qemuBlockThrottleFiltersData qemuBlockThrottleFiltersData; > + > +void > +qemuBlockThrottleFiltersDataFree(qemuBlockThrottleFiltersData *data); > + > +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockThrottleFiltersData, > + qemuBlockThrottleFiltersDataFree); > + > +int > +qemuBlockThrottleFiltersAttach(qemuMonitor *mon, > + qemuBlockThrottleFiltersData *data); > + > +void > +qemuBlockThrottleFiltersDetach(qemuMonitor *mon, > + qemuBlockThrottleFiltersData *data); > + > int > qemuBlockRemoveImageMetadata(virQEMUDriver *driver, > virDomainObj *vm, > diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c > index 696f891b47..0c119c8827 100644 > --- a/src/qemu/qemu_command.c > +++ b/src/qemu/qemu_command.c > @@ -10996,6 +10996,87 @@ qemuBuildStorageSourceChainAttachPrepareBlockdevOne(qemuBlockStorageSourceChainD > } > > > +/** > + * qemuBuildThrottleFiltersAttachPrepareBlockdevOne: > + * @data: filter chain data, which consists of array of filters and size of such array > + * @throttlefilter: new filter to be added into filter array > + * @parentNodeName: parent nodename for this new throttlefilter > + * > + * Build filter node chain to provide more flexibility for block disk I/O limits > + */ > +static int > +qemuBuildThrottleFiltersAttachPrepareBlockdevOne(qemuBlockThrottleFiltersData *data, > + virDomainThrottleFilterDef *throttlefilter, > + const char *parentNodeName) > +{ > + g_autoptr(qemuBlockThrottleFilterAttachData) elem = NULL; > + > + if (!(elem = qemuBlockThrottleFilterAttachPrepareBlockdev(throttlefilter, parentNodeName))) > + return -1; > + > + VIR_APPEND_ELEMENT(data->filterdata, data->nfilterdata, elem); > + return 0; > +} > + > + > +/** > + * qemuBuildThrottleFiltersAttachPrepareBlockdev: > + * @disk: domain disk > + * > + * Build filter node chain to provide more flexibility for block disk I/O limits > + */ > +qemuBlockThrottleFiltersData * > +qemuBuildThrottleFiltersAttachPrepareBlockdev(virDomainDiskDef *disk) > +{ > + g_autoptr(qemuBlockThrottleFiltersData) data = NULL; > + size_t i; > + const char * parentNodeName = NULL; coding style: const char *parent... > + qemuDomainDiskPrivate *priv = QEMU_DOMAIN_DISK_PRIVATE(disk); > + > + data = g_new0(qemuBlockThrottleFiltersData, 1); > + /* if copy_on_read is enabled, put throttle chain on top of it */ > + if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) { > + parentNodeName = priv->nodeCopyOnRead; > + } else { > + /* get starting parentNodename, e.g. libvirt-1-format */ pointless comment > + parentNodeName = qemuBlockStorageSourceGetEffectiveNodename(disk->src); > + } Add empty line. > + /* build filterdata, which contains all filters info and sequence info through parentNodeName */ > + for (i = 0; i < disk->nthrottlefilters; i++) { > + if (qemuBuildThrottleFiltersAttachPrepareBlockdevOne(data, disk->throttlefilters[i], parentNodeName) < 0) > + return NULL; > + parentNodeName = disk->throttlefilters[i]->nodename; > + } > + > + return g_steal_pointer(&data); > +} > + > + > +/** > + * qemuBuildThrottleFiltersDetachPrepareBlockdev: > + * @disk: domain disk > + * > + * Build filters data for later "blockdev-del" > + */ > +qemuBlockThrottleFiltersData * > +qemuBuildThrottleFiltersDetachPrepareBlockdev(virDomainDiskDef *disk) > +{ > + g_autoptr(qemuBlockThrottleFiltersData) data = g_new0(qemuBlockThrottleFiltersData, 1); > + size_t i; > + > + /* build filterdata, which contains filters info and sequence info */ > + for (i = 0; i < disk->nthrottlefilters; i++) { > + g_autoptr(qemuBlockThrottleFilterAttachData) elem = g_new0(qemuBlockThrottleFilterAttachData, 1); > + /* ignore other fields since the following info are enough for "blockdev-del" */ > + elem->filterNodeName = qemuBlockThrottleFilterGetNodename(disk->throttlefilters[i]); > + elem->filterAttached = true; > + > + VIR_APPEND_ELEMENT(data->filterdata, data->nfilterdata, elem); > + } > + return g_steal_pointer(&data); > +} > + > + > /** > * qemuBuildStorageSourceChainAttachPrepareBlockdev: > * @top: storage source chain > diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h > index 76c514b5f7..1b0296ea42 100644 > --- a/src/qemu/qemu_command.h > +++ b/src/qemu/qemu_command.h > @@ -116,6 +116,12 @@ qemuBlockStorageSourceChainData * > qemuBuildStorageSourceChainAttachPrepareBlockdevTop(virStorageSource *top, > virStorageSource *backingStore); > > +qemuBlockThrottleFiltersData * > +qemuBuildThrottleFiltersAttachPrepareBlockdev(virDomainDiskDef *disk); > + > +qemuBlockThrottleFiltersData * > +qemuBuildThrottleFiltersDetachPrepareBlockdev(virDomainDiskDef *disk); > + > virJSONValue * > qemuBuildDiskDeviceProps(const virDomainDef *def, > virDomainDiskDef *disk, > diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c > index f3b810a564..b6acf895c3 100644 > --- a/src/qemu/qemu_domain.c > +++ b/src/qemu/qemu_domain.c > @@ -2235,6 +2235,34 @@ qemuStorageSourcePrivateDataFormat(virStorageSource *src, > } > > > +static int > +virDomainDiskThrottleFilterNodeNamesParse(xmlXPathContextPtr ctxt, > + virDomainDiskDef *def) > +{ > + size_t i; > + int n = 0; > + g_autofree xmlNodePtr *nodes = NULL; > + g_autoptr(GHashTable) throttleFiltersMap = virHashNew(g_free); > + > + if ((n = virXPathNodeSet("./nodenames/nodename[@type='throttle-filter']", ctxt, &nodes)) < 0) > + return -1; > + > + for (i = 0; i < n; i++) { > + g_hash_table_insert(throttleFiltersMap, virXMLPropString(nodes[i], "group"), virXMLPropString(nodes[i], "name")); > + } > + > + for (i = 0; i < def->nthrottlefilters; i++) { > + char* nodename = g_hash_table_lookup(throttleFiltersMap, def->throttlefilters[i]->group_name); Coding style dictates: 'char *nodename'; > + if (nodename) { > + /* first time to set nodename in filter */ pointless comment > + def->throttlefilters[i]->nodename = g_strdup(nodename); use the accessor defined before. > + } > + } > + > + return 0; > +} > + > + > static int > qemuDomainDiskPrivateParse(xmlXPathContextPtr ctxt, > virDomainDiskDef *disk) > @@ -2244,6 +2272,9 @@ qemuDomainDiskPrivateParse(xmlXPathContextPtr ctxt, > priv->qomName = virXPathString("string(./qom/@name)", ctxt); > priv->nodeCopyOnRead = virXPathString("string(./nodenames/nodename[@type='copyOnRead']/@name)", ctxt); > > + if (virDomainDiskThrottleFilterNodeNamesParse(ctxt, disk) < 0) > + return -1; > + > return 0; > } > > @@ -2253,14 +2284,22 @@ qemuDomainDiskPrivateFormat(virDomainDiskDef *disk, > virBuffer *buf) > { > qemuDomainDiskPrivate *priv = QEMU_DOMAIN_DISK_PRIVATE(disk); > + size_t i; > > virBufferEscapeString(buf, "<qom name='%s'/>\n", priv->qomName); > > - if (priv->nodeCopyOnRead) { > + if (priv->nodeCopyOnRead || disk->nthrottlefilters > 0) { > virBufferAddLit(buf, "<nodenames>\n"); > virBufferAdjustIndent(buf, 2); > - virBufferEscapeString(buf, "<nodename type='copyOnRead' name='%s'/>\n", > - priv->nodeCopyOnRead); > + if (priv->nodeCopyOnRead) > + virBufferEscapeString(buf, "<nodename type='copyOnRead' name='%s'/>\n", > + priv->nodeCopyOnRead); > + if (disk->nthrottlefilters > 0) { > + for (i = 0; i < disk->nthrottlefilters; i++) { > + virBufferEscapeString(buf, "<nodename type='throttle-filter' name='%s' ", disk->throttlefilters[i]->nodename); > + virBufferEscapeString(buf, "group='%s'/>\n", disk->throttlefilters[i]->group_name); virBufferEscapeString skips the formatting of the complete format string in case when the 3rd argument is NULL. This has the potential of breaking the XML if the nodename or group_name are missing. Please check them, as it's better to have one broken feature than lose the whole VM when restartingf the daemon/. > + } > + } > virBufferAdjustIndent(buf, -2); > virBufferAddLit(buf, "</nodenames>\n"); > } > @@ -6348,7 +6387,8 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, > * @disk: disk definition object > * > * Returns the pointer to the node-name of the topmost layer used by @disk as > - * backend. Currently returns the nodename of the copy-on-read filter if enabled > + * backend. Currently returns the nodename of top throttle filter if enabled > + * or the nodename of the copy-on-read filter if enabled > * or the nodename of the top image's format driver. Empty disks return NULL. > * This must be used only with disks instantiated via -blockdev (thus not > * for SD cards). > @@ -6361,6 +6401,10 @@ qemuDomainDiskGetTopNodename(virDomainDiskDef *disk) > if (virStorageSourceIsEmpty(disk->src)) > return NULL; > > + /* If disk has throttles, take top throttle node name */ > + if (disk->nthrottlefilters > 0) > + return disk->throttlefilters[disk->nthrottlefilters - 1]->nodename; > + > if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) > return priv->nodeCopyOnRead; > > @@ -9724,6 +9768,22 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, > } > > > +static void > +qemuDomainPrepareThrottleFilterBlockdev(virDomainThrottleFilterDef *filter, > + qemuDomainObjPrivate *priv) > +{ > + g_autofree char *nodenameprefix = NULL; > + > + /* skip setting throttle filter nodename if it's set by parsing statusxml */ > + if (filter->nodename) { > + return; > + } > + nodenameprefix = g_strdup_printf("libvirt-%u", qemuDomainStorageIDNew(priv)); > + /* generate and set nodename into filter for later QEMU cmd preparation */ pointless comment > + qemuBlockThrottleFilterSetNodename(filter, g_strdup_printf("%s-filter", nodenameprefix)); > +} > + > + > int > qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDef *disk, > virStorageSource *src, > @@ -9747,6 +9807,7 @@ qemuDomainPrepareDiskSourceBlockdev(virDomainDiskDef *disk, > { > qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); > virStorageSource *n; > + size_t i; > > if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON && > !diskPriv->nodeCopyOnRead) > @@ -9757,6 +9818,10 @@ qemuDomainPrepareDiskSourceBlockdev(virDomainDiskDef *disk, > return -1; > } > > + for (i = 0; i < disk->nthrottlefilters; i++) { > + qemuDomainPrepareThrottleFilterBlockdev(disk->throttlefilters[i], priv); > + } > + > return 0; > } The rest looks reasonable so if you do all the changes: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <danielwuwy@163.com> For hot attaching/detaching * Leverage qemuBlockThrottleFiltersData to prepare attaching/detaching throttle filter data for qemuMonitorBlockdevAdd and qemuMonitorBlockdevDel * For hot attaching, within qemuDomainAttachDiskGeneric,prepare throttle filters json data, and create corresponding blockdev for QMP request ("blockdev-add" with "driver":"throttle") * Each filter has a nodename, and those filters are chained up, create them in sequence, and delete them reversely * Delete filters by "qemuBlockThrottleFiltersDetach"("blockdev-del") when detaching device For throttle group commandline * Add qemuBuildThrottleGroupCommandLine in qemuBuildCommandLine to add "object" of throttle-group * Verify throttle group definition when lauching vm * Check QEMU_CAPS_OBJECT_JSON before "qemuBuildObjectCommandlineFromJSON", which is to build "-object" option For throttle filter commandline * Add qemuBuildDiskThrottleFiltersCommandLine in qemuBuildDiskCommandLine to add "blockdev" Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_command.c | 99 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_hotplug.c | 25 +++++++++++ 2 files changed, 124 insertions(+) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 0c119c8827..19c8ab1b7f 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -2022,6 +2022,99 @@ qemuBuildBlockStorageSourceAttachDataCommandline(virCommand *cmd, } +static bool +qemuDiskConfigThrottleGroupEnabled(const virDomainThrottleGroupDef *group) +{ + return !!group->group_name && + virDomainBlockIoTuneInfoHasAny(group); +} + + +/** + * qemuBuildThrottleGroupCommandLine: + * @cmd: the command to modify + * @def: domain definition + * @qemuCaps: qemu capabilities object + * + * build throttle group object in json format + */ +static int +qemuBuildThrottleGroupCommandLine(virCommand *cmd, + const virDomainDef *def, + virQEMUCaps *qemuCaps) +{ + size_t i; + + for (i = 0; i < def->nthrottlegroups; i++) { + g_autoptr(virJSONValue) props = NULL; + g_autoptr(virJSONValue) limits = virJSONValueNewObject(); + virDomainThrottleGroupDef *group = def->throttlegroups[i]; + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", group->group_name); + + if (!qemuDiskConfigThrottleGroupEnabled(group)) { + continue; + } + + if (qemuMonitorThrottleGroupLimits(limits, group) < 0) + return -1; + + if (qemuMonitorCreateObjectProps(&props, "throttle-group", prefixed_group_name, + "a:limits", &limits, + NULL) < 0) + return -1; + + /* this throttle group feature requires "QEMU_CAPS_OBJECT_JSON" */ + if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("This QEMU doesn't support throttle group creation")); + return -1; + } + if (qemuBuildObjectCommandlineFromJSON(cmd, props, qemuCaps) < 0) + return -1; + } + + return 0; +} + + +static int +qemuBuildBlockThrottleFilterCommandline(virCommand *cmd, + qemuBlockThrottleFilterAttachData *data) +{ + if (data->filterProps) { + g_autofree char *tmp = NULL; + if (!(tmp = virJSONValueToString(data->filterProps, false))) + return -1; + + virCommandAddArgList(cmd, "-blockdev", tmp, NULL); + } + + return 0; +} + + +static int +qemuBuildDiskThrottleFiltersCommandLine(virCommand *cmd, + virDomainDiskDef *disk) +{ + g_autoptr(qemuBlockThrottleFiltersData) data = NULL; + size_t i; + + data = qemuBuildThrottleFiltersAttachPrepareBlockdev(disk); + if (!data) + return -1; + + for (i = 0; i < data->nfilterdata; i++) { + if (qemuBuildBlockThrottleFilterCommandline(cmd, + data->filterdata[i]) < 0) + return -1; + } + + return 0; +} + + static int qemuBuildDiskSourceCommandLine(virCommand *cmd, virDomainDiskDef *disk, @@ -2079,6 +2172,9 @@ qemuBuildDiskCommandLine(virCommand *cmd, if (qemuBuildDiskSourceCommandLine(cmd, disk, qemuCaps) < 0) return -1; + if (qemuBuildDiskThrottleFiltersCommandLine(cmd, disk) < 0) + return -1; + /* SD cards are currently instantiated via -drive if=sd, so the -device * part must be skipped */ if (qemuDiskBusIsSD(disk->bus)) @@ -10435,6 +10531,9 @@ qemuBuildCommandLine(virDomainObj *vm, if (qemuBuildIOThreadCommandLine(cmd, def, qemuCaps) < 0) return NULL; + if (qemuBuildThrottleGroupCommandLine(cmd, def, qemuCaps) < 0) + return NULL; + if (virDomainNumaGetNodeCount(def->numa) && qemuBuildNumaCommandLine(cfg, def, cmd, priv) < 0) return NULL; diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index bddd553c88..bf001aa902 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -708,6 +708,7 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, virDomainAsyncJob asyncJob) { g_autoptr(qemuBlockStorageSourceChainData) data = NULL; + g_autoptr(qemuBlockThrottleFiltersData) filterData = NULL; qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(virJSONValue) devprops = NULL; bool extensionDeviceAttached = false; @@ -746,6 +747,19 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (rc < 0) goto rollback; + /* Setup throttling filters + * add additional "blockdev-add"(throttle filter) between "blockdev-add" (qemuBlockStorageSourceChainAttach) and "device_add" (qemuDomainAttachExtensionDevice) + */ + if ((filterData = qemuBuildThrottleFiltersAttachPrepareBlockdev(disk))) { + if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) + return -1; + /* QMP requests("blockdev-add" with "driver":"throttle") to QEMU */ + rc = qemuBlockThrottleFiltersAttach(priv->mon, filterData); + qemuDomainObjExitMonitor(vm); + if (rc < 0) + goto rollback; + } + if (disk->transient) { g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL; g_autoptr(GHashTable) blockNamedNodeData = NULL; @@ -817,6 +831,8 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (extensionDeviceAttached) ignore_value(qemuDomainDetachExtensionDevice(priv->mon, &disk->info)); + qemuBlockThrottleFiltersDetach(priv->mon, filterData); + qemuBlockStorageSourceChainDetach(priv->mon, data); qemuDomainObjExitMonitor(vm); @@ -4642,6 +4658,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriver *driver, { qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); g_autoptr(qemuBlockStorageSourceChainData) diskBackend = NULL; + g_autoptr(qemuBlockThrottleFiltersData) filterData = NULL; size_t i; qemuDomainObjPrivate *priv = vm->privateData; int ret = -1; @@ -4680,6 +4697,14 @@ qemuDomainRemoveDiskDevice(virQEMUDriver *driver, } } + qemuDomainObjEnterMonitor(vm); + /* QMP request("blockdev-del") to QEMU to delete throttle filter*/ + if ((filterData = qemuBuildThrottleFiltersDetachPrepareBlockdev(disk))) { + /* "qemuBlockThrottleFiltersDetach" is used in rollback within "qemuDomainAttachDiskGeneric" as well */ + qemuBlockThrottleFiltersDetach(priv->mon, filterData); + } + qemuDomainObjExitMonitor(vm); + qemuDomainObjEnterMonitor(vm); if (diskBackend) -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:19 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
For hot attaching/detaching * Leverage qemuBlockThrottleFiltersData to prepare attaching/detaching throttle filter data for qemuMonitorBlockdevAdd and qemuMonitorBlockdevDel * For hot attaching, within qemuDomainAttachDiskGeneric,prepare throttle filters json data, and create corresponding blockdev for QMP request ("blockdev-add" with "driver":"throttle") * Each filter has a nodename, and those filters are chained up, create them in sequence, and delete them reversely * Delete filters by "qemuBlockThrottleFiltersDetach"("blockdev-del") when detaching device
For throttle group commandline * Add qemuBuildThrottleGroupCommandLine in qemuBuildCommandLine to add "object" of throttle-group * Verify throttle group definition when lauching vm * Check QEMU_CAPS_OBJECT_JSON before "qemuBuildObjectCommandlineFromJSON", which is to build "-object" option
For throttle filter commandline * Add qemuBuildDiskThrottleFiltersCommandLine in qemuBuildDiskCommandLine to add "blockdev"
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/qemu/qemu_command.c | 99 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_hotplug.c | 25 +++++++++++ 2 files changed, 124 insertions(+)
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 0c119c8827..19c8ab1b7f 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -2022,6 +2022,99 @@ qemuBuildBlockStorageSourceAttachDataCommandline(virCommand *cmd, }
+static bool +qemuDiskConfigThrottleGroupEnabled(const virDomainThrottleGroupDef *group)
This helper doesn't seem to be used elsewhere; inline it.
+{ + return !!group->group_name && + virDomainBlockIoTuneInfoHasAny(group); +} + + +/** + * qemuBuildThrottleGroupCommandLine: + * @cmd: the command to modify + * @def: domain definition + * @qemuCaps: qemu capabilities object + * + * build throttle group object in json format + */ +static int +qemuBuildThrottleGroupCommandLine(virCommand *cmd, + const virDomainDef *def, + virQEMUCaps *qemuCaps) +{ + size_t i; + + for (i = 0; i < def->nthrottlegroups; i++) { + g_autoptr(virJSONValue) props = NULL; + g_autoptr(virJSONValue) limits = virJSONValueNewObject(); + virDomainThrottleGroupDef *group = def->throttlegroups[i]; + /* prefix group name with "throttle-" in QOM */ + g_autofree char *prefixed_group_name = g_strdup_printf("throttle-%s", group->group_name); + + if (!qemuDiskConfigThrottleGroupEnabled(group)) { + continue; + }
... here.
+ + if (qemuMonitorThrottleGroupLimits(limits, group) < 0) + return -1; + + if (qemuMonitorCreateObjectProps(&props, "throttle-group", prefixed_group_name, + "a:limits", &limits, + NULL) < 0) + return -1; + + /* this throttle group feature requires "QEMU_CAPS_OBJECT_JSON" */
So by itself this comment is useless. It doesn't say *why* this is in fact needed. Can you elaborate? Specifically the qemuBuildObjectCommandlineFromJSON helper should be able to conver the above to the dotted syntax, so I'm guessing the error is on qemu's side.
+ if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("This QEMU doesn't support throttle group creation")); + return -1; + }
Add empty line.
+ if (qemuBuildObjectCommandlineFromJSON(cmd, props, qemuCaps) < 0) + return -1; + } + + return 0; + + +static int +qemuBuildBlockThrottleFilterCommandline(virCommand *cmd, + qemuBlockThrottleFilterAttachData *data) +{ + if (data->filterProps) { + g_autofree char *tmp = NULL; + if (!(tmp = virJSONValueToString(data->filterProps, false))) + return -1; + + virCommandAddArgList(cmd, "-blockdev", tmp, NULL); + } + + return 0; +} + + +static int +qemuBuildDiskThrottleFiltersCommandLine(virCommand *cmd, + virDomainDiskDef *disk) +{ + g_autoptr(qemuBlockThrottleFiltersData) data = NULL; + size_t i; + + data = qemuBuildThrottleFiltersAttachPrepareBlockdev(disk); + if (!data) + return -1; + + for (i = 0; i < data->nfilterdata; i++) { + if (qemuBuildBlockThrottleFilterCommandline(cmd, + data->filterdata[i]) < 0) + return -1;
This patch seems to be split questionably. The previous patch added this infrastructure so it doesn't make too much sense to be here. For now I think we should leave it as-is to not create any additional churn in this series, but for the next time please split them more logically.
+ } + + return 0; +} + + static int qemuBuildDiskSourceCommandLine(virCommand *cmd, virDomainDiskDef *disk, @@ -2079,6 +2172,9 @@ qemuBuildDiskCommandLine(virCommand *cmd, if (qemuBuildDiskSourceCommandLine(cmd, disk, qemuCaps) < 0) return -1;
+ if (qemuBuildDiskThrottleFiltersCommandLine(cmd, disk) < 0) + return -1; + /* SD cards are currently instantiated via -drive if=sd, so the -device * part must be skipped */ if (qemuDiskBusIsSD(disk->bus)) @@ -10435,6 +10531,9 @@ qemuBuildCommandLine(virDomainObj *vm, if (qemuBuildIOThreadCommandLine(cmd, def, qemuCaps) < 0) return NULL;
+ if (qemuBuildThrottleGroupCommandLine(cmd, def, qemuCaps) < 0) + return NULL; + if (virDomainNumaGetNodeCount(def->numa) && qemuBuildNumaCommandLine(cfg, def, cmd, priv) < 0) return NULL; diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index bddd553c88..bf001aa902 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -708,6 +708,7 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, virDomainAsyncJob asyncJob) { g_autoptr(qemuBlockStorageSourceChainData) data = NULL; + g_autoptr(qemuBlockThrottleFiltersData) filterData = NULL; qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(virJSONValue) devprops = NULL; bool extensionDeviceAttached = false; @@ -746,6 +747,19 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (rc < 0) goto rollback;
+ /* Setup throttling filters + * add additional "blockdev-add"(throttle filter) between "blockdev-add" (qemuBlockStorageSourceChainAttach) and "device_add" (qemuDomainAttachExtensionDevice) + */
Drop the comment.
+ if ((filterData = qemuBuildThrottleFiltersAttachPrepareBlockdev(disk))) { + if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) + return -1; + /* QMP requests("blockdev-add" with "driver":"throttle") to QEMU */
pointless comment
+ rc = qemuBlockThrottleFiltersAttach(priv->mon, filterData); + qemuDomainObjExitMonitor(vm); + if (rc < 0) + goto rollback; + } + if (disk->transient) { g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL; g_autoptr(GHashTable) blockNamedNodeData = NULL; @@ -817,6 +831,8 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (extensionDeviceAttached) ignore_value(qemuDomainDetachExtensionDevice(priv->mon, &disk->info));
+ qemuBlockThrottleFiltersDetach(priv->mon, filterData); + qemuBlockStorageSourceChainDetach(priv->mon, data);
qemuDomainObjExitMonitor(vm); @@ -4642,6 +4658,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriver *driver, { qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); g_autoptr(qemuBlockStorageSourceChainData) diskBackend = NULL; + g_autoptr(qemuBlockThrottleFiltersData) filterData = NULL; size_t i; qemuDomainObjPrivate *priv = vm->privateData; int ret = -1; @@ -4680,6 +4697,14 @@ qemuDomainRemoveDiskDevice(virQEMUDriver *driver, } }
+ qemuDomainObjEnterMonitor(vm); + /* QMP request("blockdev-del") to QEMU to delete throttle filter*/
pointless comment
+ if ((filterData = qemuBuildThrottleFiltersDetachPrepareBlockdev(disk))) { + /* "qemuBlockThrottleFiltersDetach" is used in rollback within "qemuDomainAttachDiskGeneric" as well */
pointless comment
+ qemuBlockThrottleFiltersDetach(priv->mon, filterData); + } + qemuDomainObjExitMonitor(vm); + qemuDomainObjEnterMonitor(vm);
if (diskBackend) -- 2.39.5 (Apple Git-154) a
With the changes above: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <danielwuwy@163.com> Refactor iotune verification, and verify some rules * Disk iotune validation can be reused for throttle group validation, refactor it into common method "virDomainDiskIoTuneValidate" * Add "virDomainDefValidateThrottleGroups" to validate throttle groups, which in turn calls "virDomainDiskIoTuneValidate" * Make sure referenced throttle group exists * Use "iotune" and "throttlefilters" exclusively for specific disk * Throttle filters cannot be used together with CDROM Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/conf/domain_validate.c | 119 ++++++++++++++++++++++++++----------- src/qemu/qemu_driver.c | 6 ++ src/qemu/qemu_hotplug.c | 8 +++ 3 files changed, 99 insertions(+), 34 deletions(-) diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index b352cd874a..916d5c9986 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -659,11 +659,55 @@ virDomainDiskDefValidateStartupPolicy(const virDomainDiskDef *disk) } +static int +virDomainDiskIoTuneValidate(const virDomainBlockIoTuneInfo blkdeviotune) +{ + if ((blkdeviotune.total_bytes_sec && + blkdeviotune.read_bytes_sec) || + (blkdeviotune.total_bytes_sec && + blkdeviotune.write_bytes_sec)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write bytes_sec cannot be set at the same time")); + return -1; + } + + if ((blkdeviotune.total_iops_sec && + blkdeviotune.read_iops_sec) || + (blkdeviotune.total_iops_sec && + blkdeviotune.write_iops_sec)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write iops_sec cannot be set at the same time")); + return -1; + } + + if ((blkdeviotune.total_bytes_sec_max && + blkdeviotune.read_bytes_sec_max) || + (blkdeviotune.total_bytes_sec_max && + blkdeviotune.write_bytes_sec_max)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write bytes_sec_max cannot be set at the same time")); + return -1; + } + + if ((blkdeviotune.total_iops_sec_max && + blkdeviotune.read_iops_sec_max) || + (blkdeviotune.total_iops_sec_max && + blkdeviotune.write_iops_sec_max)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write iops_sec_max cannot be set at the same time")); + return -1; + } + + return 0; +} + + static int virDomainDiskDefValidate(const virDomainDef *def, const virDomainDiskDef *disk) { virStorageSource *next; + size_t i; /* disk target is used widely in other code so it must be validated first */ if (!disk->dst) { @@ -713,41 +757,8 @@ virDomainDiskDefValidate(const virDomainDef *def, } /* Validate IotuneParse */ - if ((disk->blkdeviotune.total_bytes_sec && - disk->blkdeviotune.read_bytes_sec) || - (disk->blkdeviotune.total_bytes_sec && - disk->blkdeviotune.write_bytes_sec)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write bytes_sec cannot be set at the same time")); + if (virDomainDiskIoTuneValidate(disk->blkdeviotune) < 0) return -1; - } - - if ((disk->blkdeviotune.total_iops_sec && - disk->blkdeviotune.read_iops_sec) || - (disk->blkdeviotune.total_iops_sec && - disk->blkdeviotune.write_iops_sec)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write iops_sec cannot be set at the same time")); - return -1; - } - - if ((disk->blkdeviotune.total_bytes_sec_max && - disk->blkdeviotune.read_bytes_sec_max) || - (disk->blkdeviotune.total_bytes_sec_max && - disk->blkdeviotune.write_bytes_sec_max)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write bytes_sec_max cannot be set at the same time")); - return -1; - } - - if ((disk->blkdeviotune.total_iops_sec_max && - disk->blkdeviotune.read_iops_sec_max) || - (disk->blkdeviotune.total_iops_sec_max && - disk->blkdeviotune.write_iops_sec_max)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write iops_sec_max cannot be set at the same time")); - return -1; - } /* Reject disks with a bus type that is not compatible with the * given address type. The function considers only buses that are @@ -943,6 +954,26 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; } + /* referenced group should be defined */ + for (i = 0; i < disk->nthrottlefilters; i++) { + virDomainThrottleFilterDef *filter = disk->throttlefilters[i]; + if (!virDomainThrottleGroupByName(def, filter->group_name)) { + virReportError(VIR_ERR_XML_ERROR, + _("throttle group '%1$s' not found"), + filter->group_name); + return -1; + } + } + + if (disk->throttlefilters && + (disk->blkdeviotune.group_name || + virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("block 'throttlefilters' can't be used together with 'iotune' for disk '%1$s'"), + disk->dst); + return -1; + } + return 0; } @@ -1843,6 +1874,23 @@ virDomainDefLaunchSecurityValidate(const virDomainDef *def) #undef CHECK_BASE64_LEN +static int +virDomainDefValidateThrottleGroups(const virDomainDef *def) +{ + size_t i; + + for (i = 0; i < def->nthrottlegroups; i++) { + virDomainThrottleGroupDef *throttleGroup = def->throttlegroups[i]; + + /* Validate Throttle Group */ + if (virDomainDiskIoTuneValidate(*throttleGroup) < 0) + return -1; + } + + return 0; +} + + static int virDomainDefValidateInternal(const virDomainDef *def, virDomainXMLOption *xmlopt) @@ -1901,6 +1949,9 @@ virDomainDefValidateInternal(const virDomainDef *def, if (virDomainDefLaunchSecurityValidate(def) < 0) return -1; + if (virDomainDefValidateThrottleGroups(def) < 0) + return -1; + return 0; } diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d65d5fd2ff..2a5f58635c 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14862,6 +14862,12 @@ qemuDomainDiskBlockIoTuneIsSupported(virDomainDiskDef *disk) return false; } + if (disk->throttlefilters) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("block 'iotune' can't be used together with 'throttlefilters' for disk '%1$s'"), disk->dst); + return false; + } + return true; } diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index bf001aa902..8c41d27704 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -988,6 +988,14 @@ qemuDomainAttachDeviceDiskLiveInternal(virQEMUDriver *driver, bool releaseSeclabel = false; int ret = -1; + if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) { + if (disk->nthrottlefilters > 0) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cdrom device with throttle filters isn't supported")); + return -1; + } + } + if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("floppy device hotplug isn't supported")); -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:20 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Refactor iotune verification, and verify some rules
* Disk iotune validation can be reused for throttle group validation, refactor it into common method "virDomainDiskIoTuneValidate" * Add "virDomainDefValidateThrottleGroups" to validate throttle groups, which in turn calls "virDomainDiskIoTuneValidate" * Make sure referenced throttle group exists * Use "iotune" and "throttlefilters" exclusively for specific disk * Throttle filters cannot be used together with CDROM
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> ---
Once again a bit too much going on in a single commit. The extraction of the code to +virDomainDiskIoTuneValidate would qualify for a individual commit. As said before; don't change it now as I'm forseeing more trouble if you do.
src/conf/domain_validate.c | 119 ++++++++++++++++++++++++++----------- src/qemu/qemu_driver.c | 6 ++ src/qemu/qemu_hotplug.c | 8 +++ 3 files changed, 99 insertions(+), 34 deletions(-)
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index b352cd874a..916d5c9986 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -659,11 +659,55 @@ virDomainDiskDefValidateStartupPolicy(const virDomainDiskDef *disk) }
+static int +virDomainDiskIoTuneValidate(const virDomainBlockIoTuneInfo blkdeviotune) +{ + if ((blkdeviotune.total_bytes_sec && + blkdeviotune.read_bytes_sec) || + (blkdeviotune.total_bytes_sec && + blkdeviotune.write_bytes_sec)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write bytes_sec cannot be set at the same time")); + return -1; + } + + if ((blkdeviotune.total_iops_sec && + blkdeviotune.read_iops_sec) || + (blkdeviotune.total_iops_sec && + blkdeviotune.write_iops_sec)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write iops_sec cannot be set at the same time")); + return -1; + } + + if ((blkdeviotune.total_bytes_sec_max && + blkdeviotune.read_bytes_sec_max) || + (blkdeviotune.total_bytes_sec_max && + blkdeviotune.write_bytes_sec_max)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write bytes_sec_max cannot be set at the same time")); + return -1; + } + + if ((blkdeviotune.total_iops_sec_max && + blkdeviotune.read_iops_sec_max) || + (blkdeviotune.total_iops_sec_max && + blkdeviotune.write_iops_sec_max)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("total and read/write iops_sec_max cannot be set at the same time")); + return -1; + } + + return 0; +} + + static int virDomainDiskDefValidate(const virDomainDef *def, const virDomainDiskDef *disk) { virStorageSource *next; + size_t i;
/* disk target is used widely in other code so it must be validated first */ if (!disk->dst) { @@ -713,41 +757,8 @@ virDomainDiskDefValidate(const virDomainDef *def, }
/* Validate IotuneParse */ - if ((disk->blkdeviotune.total_bytes_sec && - disk->blkdeviotune.read_bytes_sec) || - (disk->blkdeviotune.total_bytes_sec && - disk->blkdeviotune.write_bytes_sec)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write bytes_sec cannot be set at the same time")); + if (virDomainDiskIoTuneValidate(disk->blkdeviotune) < 0) return -1; - } - - if ((disk->blkdeviotune.total_iops_sec && - disk->blkdeviotune.read_iops_sec) || - (disk->blkdeviotune.total_iops_sec && - disk->blkdeviotune.write_iops_sec)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write iops_sec cannot be set at the same time")); - return -1; - } - - if ((disk->blkdeviotune.total_bytes_sec_max && - disk->blkdeviotune.read_bytes_sec_max) || - (disk->blkdeviotune.total_bytes_sec_max && - disk->blkdeviotune.write_bytes_sec_max)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write bytes_sec_max cannot be set at the same time")); - return -1; - } - - if ((disk->blkdeviotune.total_iops_sec_max && - disk->blkdeviotune.read_iops_sec_max) || - (disk->blkdeviotune.total_iops_sec_max && - disk->blkdeviotune.write_iops_sec_max)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("total and read/write iops_sec_max cannot be set at the same time")); - return -1; - }
/* Reject disks with a bus type that is not compatible with the * given address type. The function considers only buses that are @@ -943,6 +954,26 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; }
+ /* referenced group should be defined */ + for (i = 0; i < disk->nthrottlefilters; i++) { + virDomainThrottleFilterDef *filter = disk->throttlefilters[i]; + if (!virDomainThrottleGroupByName(def, filter->group_name)) { + virReportError(VIR_ERR_XML_ERROR, + _("throttle group '%1$s' not found"), + filter->group_name); + return -1; + } + } + + if (disk->throttlefilters && + (disk->blkdeviotune.group_name || + virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("block 'throttlefilters' can't be used together with 'iotune' for disk '%1$s'"), + disk->dst); + return -1; + } + return 0; }
@@ -1843,6 +1874,23 @@ virDomainDefLaunchSecurityValidate(const virDomainDef *def)
#undef CHECK_BASE64_LEN
+static int +virDomainDefValidateThrottleGroups(const virDomainDef *def) +{ + size_t i; + + for (i = 0; i < def->nthrottlegroups; i++) { + virDomainThrottleGroupDef *throttleGroup = def->throttlegroups[i]; + + /* Validate Throttle Group */
Pointless comment
+ if (virDomainDiskIoTuneValidate(*throttleGroup) < 0) + return -1; + } + + return 0; +} + + static int virDomainDefValidateInternal(const virDomainDef *def, virDomainXMLOption *xmlopt) @@ -1901,6 +1949,9 @@ virDomainDefValidateInternal(const virDomainDef *def, if (virDomainDefLaunchSecurityValidate(def) < 0) return -1;
+ if (virDomainDefValidateThrottleGroups(def) < 0) + return -1; + return 0; }
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d65d5fd2ff..2a5f58635c 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14862,6 +14862,12 @@ qemuDomainDiskBlockIoTuneIsSupported(virDomainDiskDef *disk) return false; }
+ if (disk->throttlefilters) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("block 'iotune' can't be used together with 'throttlefilters' for disk '%1$s'"), disk->dst); + return false; + } + return true; }
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index bf001aa902..8c41d27704 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -988,6 +988,14 @@ qemuDomainAttachDeviceDiskLiveInternal(virQEMUDriver *driver, bool releaseSeclabel = false; int ret = -1;
+ if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) {
IIRC this is so that there's no trouble when ejecting the media; but I'd expect to see the same for cold-plug as well.
+ if (disk->nthrottlefilters > 0) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cdrom device with throttle filters isn't supported")); + return -1; + } + } + if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("floppy device hotplug isn't supported")); -- 2.39.5 (Apple Git-154)

From: Chun Feng Wu <danielwuwy@163.com> Test domain xml and status xml * Add tests for "throttlegroup" domain xml processing, including groups referenced and not referenced by filters * Add tests for "throttlefilter" domain xml processing, including throttle group referenced by different disks * Add negative test case to report error when iotune is configured together with throttle filters * Add tests for throttlefilter nodename parse and format for statusxml (disk/privateData/nodenames/nodename with type='throttle-filter') * Add iotune limited disk tests to make sure iotune refactory works Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- .../throttlefilter-in.xml | 392 ++++++++++++++++++ .../throttlefilter-out.xml | 392 ++++++++++++++++++ tests/qemuxmlactivetest.c | 1 + .../throttlefilter-invalid.x86_64-latest.err | 1 + .../throttlefilter-invalid.xml | 89 ++++ .../throttlefilter.x86_64-latest.args | 55 +++ .../throttlefilter.x86_64-latest.xml | 105 +++++ tests/qemuxmlconfdata/throttlefilter.xml | 95 +++++ tests/qemuxmlconftest.c | 2 + 9 files changed, 1132 insertions(+) create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-in.xml create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-out.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.x86_64-latest.err create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.xml diff --git a/tests/qemustatusxml2xmldata/throttlefilter-in.xml b/tests/qemustatusxml2xmldata/throttlefilter-in.xml new file mode 100644 index 0000000000..2a98d6f5e2 --- /dev/null +++ b/tests/qemustatusxml2xmldata/throttlefilter-in.xml @@ -0,0 +1,392 @@ +<domstatus state='running' reason='booted' pid='7690'> + <taint flag='high-privileges'/> + <monitor path='/var/lib/libvirt/qemu/domain-4-copy/monitor.sock' type='unix'/> + <namespaces> + <mount/> + </namespaces> + <vcpus> + <vcpu id='0' pid='7696'/> + </vcpus> + <qemuCaps> + <flag name='kvm'/> + <flag name='no-hpet'/> + <flag name='spice'/> + <flag name='hda-duplex'/> + <flag name='ccid-emulated'/> + <flag name='ccid-passthru'/> + <flag name='virtio-tx-alg'/> + <flag name='virtio-blk-pci.ioeventfd'/> + <flag name='sga'/> + <flag name='virtio-blk-pci.event_idx'/> + <flag name='virtio-net-pci.event_idx'/> + <flag name='piix3-usb-uhci'/> + <flag name='piix4-usb-uhci'/> + <flag name='usb-ehci'/> + <flag name='ich9-usb-ehci1'/> + <flag name='vt82c686b-usb-uhci'/> + <flag name='pci-ohci'/> + <flag name='usb-redir'/> + <flag name='usb-hub'/> + <flag name='ich9-ahci'/> + <flag name='no-acpi'/> + <flag name='virtio-blk-pci.scsi'/> + <flag name='scsi-disk.channel'/> + <flag name='scsi-block'/> + <flag name='transaction'/> + <flag name='block-job-async'/> + <flag name='scsi-cd'/> + <flag name='ide-cd'/> + <flag name='hda-micro'/> + <flag name='dump-guest-memory'/> + <flag name='nec-usb-xhci'/> + <flag name='balloon-event'/> + <flag name='lsi'/> + <flag name='virtio-scsi-pci'/> + <flag name='blockio'/> + <flag name='disable-s3'/> + <flag name='disable-s4'/> + <flag name='usb-redir.filter'/> + <flag name='ide-drive.wwn'/> + <flag name='scsi-disk.wwn'/> + <flag name='seccomp-sandbox'/> + <flag name='reboot-timeout'/> + <flag name='seamless-migration'/> + <flag name='block-commit'/> + <flag name='vnc'/> + <flag name='drive-mirror'/> + <flag name='blockdev-snapshot-sync'/> + <flag name='qxl'/> + <flag name='VGA'/> + <flag name='cirrus-vga'/> + <flag name='vmware-svga'/> + <flag name='device-video-primary'/> + <flag name='usb-serial'/> + <flag name='nbd-server'/> + <flag name='virtio-rng'/> + <flag name='rng-random'/> + <flag name='rng-egd'/> + <flag name='megasas'/> + <flag name='tpm-passthrough'/> + <flag name='tpm-tis'/> + <flag name='pci-bridge'/> + <flag name='vfio-pci'/> + <flag name='mem-merge'/> + <flag name='drive-discard'/> + <flag name='mlock'/> + <flag name='device-del-event'/> + <flag name='dmi-to-pci-bridge'/> + <flag name='i440fx-pci-hole64-size'/> + <flag name='q35-pci-hole64-size'/> + <flag name='usb-storage'/> + <flag name='usb-storage.removable'/> + <flag name='ich9-intel-hda'/> + <flag name='kvm-pit-lost-tick-policy'/> + <flag name='boot-strict'/> + <flag name='pvpanic'/> + <flag name='spice-file-xfer-disable'/> + <flag name='usb-kbd'/> + <flag name='msg-timestamp'/> + <flag name='active-commit'/> + <flag name='change-backing-file'/> + <flag name='memory-backend-ram'/> + <flag name='numa'/> + <flag name='memory-backend-file'/> + <flag name='usb-audio'/> + <flag name='rtc-reset-reinjection'/> + <flag name='splash-timeout'/> + <flag name='iothread'/> + <flag name='migrate-rdma'/> + <flag name='ivshmem'/> + <flag name='drive-iotune-max'/> + <flag name='VGA.vgamem_mb'/> + <flag name='vmware-svga.vgamem_mb'/> + <flag name='qxl.vgamem_mb'/> + <flag name='pc-dimm'/> + <flag name='machine-vmport-opt'/> + <flag name='aes-key-wrap'/> + <flag name='dea-key-wrap'/> + <flag name='pci-serial'/> + <flag name='vhost-user-multiqueue'/> + <flag name='migration-event'/> + <flag name='ioh3420'/> + <flag name='x3130-upstream'/> + <flag name='xio3130-downstream'/> + <flag name='rtl8139'/> + <flag name='e1000'/> + <flag name='virtio-net'/> + <flag name='gic-version'/> + <flag name='incoming-defer'/> + <flag name='virtio-gpu'/> + <flag name='virtio-gpu.virgl'/> + <flag name='virtio-keyboard'/> + <flag name='virtio-mouse'/> + <flag name='virtio-tablet'/> + <flag name='virtio-input-host'/> + <flag name='chardev-file-append'/> + <flag name='ich9-disable-s3'/> + <flag name='ich9-disable-s4'/> + <flag name='vserport-change-event'/> + <flag name='virtio-balloon-pci.deflate-on-oom'/> + <flag name='mptsas1068'/> + <flag name='spice-gl'/> + <flag name='qxl.vram64_size_mb'/> + <flag name='chardev-logfile'/> + <flag name='debug-threads'/> + <flag name='secret'/> + <flag name='pxb'/> + <flag name='pxb-pcie'/> + <flag name='device-tray-moved-event'/> + <flag name='nec-usb-xhci-ports'/> + <flag name='virtio-scsi-pci.iothread'/> + <flag name='name-guest'/> + <flag name='qxl.max_outputs'/> + <flag name='spice-unix'/> + <flag name='drive-detect-zeroes'/> + <flag name='tls-creds-x509'/> + <flag name='intel-iommu'/> + <flag name='smm'/> + <flag name='virtio-pci-disable-legacy'/> + <flag name='query-hotpluggable-cpus'/> + <flag name='virtio-net.rx_queue_size'/> + <flag name='virtio-vga'/> + <flag name='drive-iotune-max-length'/> + <flag name='ivshmem-plain'/> + <flag name='ivshmem-doorbell'/> + <flag name='query-qmp-schema'/> + <flag name='gluster.debug_level'/> + <flag name='vhost-scsi'/> + <flag name='drive-iotune-group'/> + <flag name='query-cpu-model-expansion'/> + <flag name='virtio-net.host_mtu'/> + <flag name='spice-rendernode'/> + <flag name='nvdimm'/> + <flag name='pcie-root-port'/> + <flag name='query-cpu-definitions'/> + <flag name='block-write-threshold'/> + <flag name='query-named-block-nodes'/> + <flag name='cpu-cache'/> + <flag name='qemu-xhci'/> + <flag name='kernel-irqchip'/> + <flag name='kernel-irqchip.split'/> + <flag name='intel-iommu.intremap'/> + <flag name='intel-iommu.caching-mode'/> + <flag name='intel-iommu.eim'/> + <flag name='intel-iommu.device-iotlb'/> + <flag name='virtio.iommu_platform'/> + <flag name='virtio.ats'/> + <flag name='loadparm'/> + <flag name='vnc-multi-servers'/> + <flag name='virtio-net.tx_queue_size'/> + <flag name='chardev-reconnect'/> + <flag name='virtio-gpu.max_outputs'/> + <flag name='vxhs'/> + <flag name='virtio-blk.num-queues'/> + <flag name='vmcoreinfo'/> + <flag name='numa.dist'/> + <flag name='disk-share-rw'/> + <flag name='iscsi.password-secret'/> + <flag name='isa-serial'/> + <flag name='dump-completed'/> + <flag name='qcow2-luks'/> + <flag name='pcie-pci-bridge'/> + <flag name='seccomp-blacklist'/> + <flag name='query-cpus-fast'/> + <flag name='disk-write-cache'/> + <flag name='nbd-tls'/> + <flag name='tpm-crb'/> + <flag name='pr-manager-helper'/> + <flag name='qom-list-properties'/> + <flag name='memory-backend-file.discard-data'/> + <flag name='sdl-gl'/> + <flag name='screendump_device'/> + <flag name='hda-output'/> + <flag name='blockdev-del'/> + <flag name='vmgenid'/> + <flag name='vhost-vsock'/> + <flag name='chardev-fd-pass'/> + <flag name='tpm-emulator'/> + <flag name='mch'/> + <flag name='mch.extended-tseg-mbytes'/> + <flag name='usb-storage.werror'/> + <flag name='egl-headless'/> + <flag name='vfio-pci.display'/> + <flag name='blockdev'/> + <flag name='memory-backend-memfd'/> + <flag name='memory-backend-memfd.hugetlb'/> + <flag name='iothread.poll-max-ns'/> + <flag name='egl-headless.rendernode'/> + <flag name='incremental-backup'/> + </qemuCaps> + <devices> + <device alias='rng0'/> + <device alias='sound0-codec0'/> + <device alias='virtio-disk0'/> + <device alias='virtio-serial0'/> + <device alias='video0'/> + <device alias='serial0'/> + <device alias='sound0'/> + <device alias='channel1'/> + <device alias='channel0'/> + <device alias='usb'/> + </devices> + <libDir path='/var/lib/libvirt/qemu/domain-4-copy'/> + <channelTargetDir path='/var/lib/libvirt/qemu/channel/target/domain-4-copy'/> + <chardevStdioLogd/> + <allowReboot value='yes'/> + <nodename index='0'/> + <fdset index='0'/> + <blockjobs active='no'/> + <agentTimeout>-2</agentTimeout> + <domain type='kvm' id='4'> + <name>copy</name> + <uuid>0439a4a8-db56-4933-9183-d8681d7b0746</uuid> + <memory unit='KiB'>1024000</memory> + <currentMemory unit='KiB'>1024000</currentMemory> + <vcpu placement='static'>1</vcpu> + <throttlegroups> + <throttlegroup> + <total_iops_sec>200</total_iops_sec> + <total_iops_sec_max>200</total_iops_sec_max> + <group_name>limit0</group_name> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + </throttlegroup> + <throttlegroup> + <total_iops_sec>250</total_iops_sec> + <total_iops_sec_max>250</total_iops_sec_max> + <group_name>limit1</group_name> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + </throttlegroup> + <throttlegroup> + <total_iops_sec>300</total_iops_sec> + <total_iops_sec_max>300</total_iops_sec_max> + <group_name>limit2</group_name> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + </throttlegroup> + <throttlegroup> + <total_iops_sec>400</total_iops_sec> + <total_iops_sec_max>400</total_iops_sec_max> + <group_name>limit012</group_name> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + </throttlegroup> + </throttlegroups> + <resource> + <partition>/machine</partition> + </resource> + <os> + <type arch='x86_64' machine='pc-i440fx-2.9'>hvm</type> + <boot dev='hd'/> + <bootmenu enable='yes'/> + </os> + <features> + <acpi/> + <apic/> + <vmport state='off'/> + </features> + <clock offset='utc'> + <timer name='rtc' tickpolicy='catchup'/> + <timer name='pit' tickpolicy='delay'/> + <timer name='hpet' present='no'/> + </clock> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>restart</on_crash> + <pm> + <suspend-to-mem enabled='no'/> + <suspend-to-disk enabled='no'/> + </pm> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/pull4.qcow2' index='3'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-3-storage'/> + <nodename type='format' name='libvirt-3-format'/> + </nodenames> + </privateData> + </source> + <target dev='vda' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit0'/> + <throttlefilter group='limit012'/> + </throttlefilters> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0b' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk1/virtio-backend'/> + <nodenames> + <nodename type='throttle-filter' name='libvirt-4-filter' group='limit0'/> + <nodename type='throttle-filter' name='libvirt-5-filter' group='limit012'/> + </nodenames> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/pull5.qcow2' index='4'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-4-storage'/> + <nodename type='format' name='libvirt-4-format'/> + </nodenames> + </privateData> + </source> + <target dev='vda' bus='virtio'/> + <iotune> + <total_bytes_sec>10000000</total_bytes_sec> + <read_iops_sec>400000</read_iops_sec> + <write_iops_sec>100000</write_iops_sec> + </iotune> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0c' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk2/virtio-backend'/> + </privateData> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <alias name='usb'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'> + <alias name='pci.0'/> + </controller> + <serial type='pty'> + <source path='/dev/pts/34'/> + <target type='isa-serial' port='0'> + <model name='isa-serial'/> + </target> + <alias name='serial0'/> + </serial> + <console type='pty' tty='/dev/pts/34'> + <source path='/dev/pts/34'/> + <target type='serial' port='0'/> + <alias name='serial0'/> + </console> + <input type='mouse' bus='ps2'> + <alias name='input0'/> + </input> + <input type='keyboard' bus='ps2'> + <alias name='input1'/> + </input> + <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'> + <listen type='address' address='127.0.0.1' fromConfig='1' autoGenerated='no'/> + <image compression='off'/> + </graphics> + <audio id='1' type='spice'/> + <video> + <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/> + <alias name='video0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </video> + <memballoon model='none'/> + </devices> + <seclabel type='dynamic' model='selinux' relabel='yes'> + <label>unconfined_u:unconfined_r:svirt_t:s0:c550,c786</label> + <imagelabel>unconfined_u:object_r:svirt_image_t:s0:c550,c786</imagelabel> + </seclabel> + <seclabel type='dynamic' model='dac' relabel='yes'> + <label>+0:+0</label> + <imagelabel>+0:+0</imagelabel> + </seclabel> + </domain> +</domstatus> diff --git a/tests/qemustatusxml2xmldata/throttlefilter-out.xml b/tests/qemustatusxml2xmldata/throttlefilter-out.xml new file mode 100644 index 0000000000..2217ad929d --- /dev/null +++ b/tests/qemustatusxml2xmldata/throttlefilter-out.xml @@ -0,0 +1,392 @@ +<domstatus state='running' reason='booted' pid='7690'> + <taint flag='high-privileges'/> + <monitor path='/var/lib/libvirt/qemu/domain-4-copy/monitor.sock' type='unix'/> + <namespaces> + <mount/> + </namespaces> + <vcpus> + <vcpu id='0' pid='7696'/> + </vcpus> + <qemuCaps> + <flag name='kvm'/> + <flag name='no-hpet'/> + <flag name='spice'/> + <flag name='hda-duplex'/> + <flag name='ccid-emulated'/> + <flag name='ccid-passthru'/> + <flag name='virtio-tx-alg'/> + <flag name='virtio-blk-pci.ioeventfd'/> + <flag name='sga'/> + <flag name='virtio-blk-pci.event_idx'/> + <flag name='virtio-net-pci.event_idx'/> + <flag name='piix3-usb-uhci'/> + <flag name='piix4-usb-uhci'/> + <flag name='usb-ehci'/> + <flag name='ich9-usb-ehci1'/> + <flag name='vt82c686b-usb-uhci'/> + <flag name='pci-ohci'/> + <flag name='usb-redir'/> + <flag name='usb-hub'/> + <flag name='ich9-ahci'/> + <flag name='no-acpi'/> + <flag name='virtio-blk-pci.scsi'/> + <flag name='scsi-disk.channel'/> + <flag name='scsi-block'/> + <flag name='transaction'/> + <flag name='block-job-async'/> + <flag name='scsi-cd'/> + <flag name='ide-cd'/> + <flag name='hda-micro'/> + <flag name='dump-guest-memory'/> + <flag name='nec-usb-xhci'/> + <flag name='balloon-event'/> + <flag name='lsi'/> + <flag name='virtio-scsi-pci'/> + <flag name='blockio'/> + <flag name='disable-s3'/> + <flag name='disable-s4'/> + <flag name='usb-redir.filter'/> + <flag name='ide-drive.wwn'/> + <flag name='scsi-disk.wwn'/> + <flag name='seccomp-sandbox'/> + <flag name='reboot-timeout'/> + <flag name='seamless-migration'/> + <flag name='block-commit'/> + <flag name='vnc'/> + <flag name='drive-mirror'/> + <flag name='blockdev-snapshot-sync'/> + <flag name='qxl'/> + <flag name='VGA'/> + <flag name='cirrus-vga'/> + <flag name='vmware-svga'/> + <flag name='device-video-primary'/> + <flag name='usb-serial'/> + <flag name='nbd-server'/> + <flag name='virtio-rng'/> + <flag name='rng-random'/> + <flag name='rng-egd'/> + <flag name='megasas'/> + <flag name='tpm-passthrough'/> + <flag name='tpm-tis'/> + <flag name='pci-bridge'/> + <flag name='vfio-pci'/> + <flag name='mem-merge'/> + <flag name='drive-discard'/> + <flag name='mlock'/> + <flag name='device-del-event'/> + <flag name='dmi-to-pci-bridge'/> + <flag name='i440fx-pci-hole64-size'/> + <flag name='q35-pci-hole64-size'/> + <flag name='usb-storage'/> + <flag name='usb-storage.removable'/> + <flag name='ich9-intel-hda'/> + <flag name='kvm-pit-lost-tick-policy'/> + <flag name='boot-strict'/> + <flag name='pvpanic'/> + <flag name='spice-file-xfer-disable'/> + <flag name='usb-kbd'/> + <flag name='msg-timestamp'/> + <flag name='active-commit'/> + <flag name='change-backing-file'/> + <flag name='memory-backend-ram'/> + <flag name='numa'/> + <flag name='memory-backend-file'/> + <flag name='usb-audio'/> + <flag name='rtc-reset-reinjection'/> + <flag name='splash-timeout'/> + <flag name='iothread'/> + <flag name='migrate-rdma'/> + <flag name='ivshmem'/> + <flag name='drive-iotune-max'/> + <flag name='VGA.vgamem_mb'/> + <flag name='vmware-svga.vgamem_mb'/> + <flag name='qxl.vgamem_mb'/> + <flag name='pc-dimm'/> + <flag name='machine-vmport-opt'/> + <flag name='aes-key-wrap'/> + <flag name='dea-key-wrap'/> + <flag name='pci-serial'/> + <flag name='vhost-user-multiqueue'/> + <flag name='migration-event'/> + <flag name='ioh3420'/> + <flag name='x3130-upstream'/> + <flag name='xio3130-downstream'/> + <flag name='rtl8139'/> + <flag name='e1000'/> + <flag name='virtio-net'/> + <flag name='gic-version'/> + <flag name='incoming-defer'/> + <flag name='virtio-gpu'/> + <flag name='virtio-gpu.virgl'/> + <flag name='virtio-keyboard'/> + <flag name='virtio-mouse'/> + <flag name='virtio-tablet'/> + <flag name='virtio-input-host'/> + <flag name='chardev-file-append'/> + <flag name='ich9-disable-s3'/> + <flag name='ich9-disable-s4'/> + <flag name='vserport-change-event'/> + <flag name='virtio-balloon-pci.deflate-on-oom'/> + <flag name='mptsas1068'/> + <flag name='spice-gl'/> + <flag name='qxl.vram64_size_mb'/> + <flag name='chardev-logfile'/> + <flag name='debug-threads'/> + <flag name='secret'/> + <flag name='pxb'/> + <flag name='pxb-pcie'/> + <flag name='device-tray-moved-event'/> + <flag name='nec-usb-xhci-ports'/> + <flag name='virtio-scsi-pci.iothread'/> + <flag name='name-guest'/> + <flag name='qxl.max_outputs'/> + <flag name='spice-unix'/> + <flag name='drive-detect-zeroes'/> + <flag name='tls-creds-x509'/> + <flag name='intel-iommu'/> + <flag name='smm'/> + <flag name='virtio-pci-disable-legacy'/> + <flag name='query-hotpluggable-cpus'/> + <flag name='virtio-net.rx_queue_size'/> + <flag name='virtio-vga'/> + <flag name='drive-iotune-max-length'/> + <flag name='ivshmem-plain'/> + <flag name='ivshmem-doorbell'/> + <flag name='query-qmp-schema'/> + <flag name='gluster.debug_level'/> + <flag name='vhost-scsi'/> + <flag name='drive-iotune-group'/> + <flag name='query-cpu-model-expansion'/> + <flag name='virtio-net.host_mtu'/> + <flag name='spice-rendernode'/> + <flag name='nvdimm'/> + <flag name='pcie-root-port'/> + <flag name='query-cpu-definitions'/> + <flag name='block-write-threshold'/> + <flag name='query-named-block-nodes'/> + <flag name='cpu-cache'/> + <flag name='qemu-xhci'/> + <flag name='kernel-irqchip'/> + <flag name='kernel-irqchip.split'/> + <flag name='intel-iommu.intremap'/> + <flag name='intel-iommu.caching-mode'/> + <flag name='intel-iommu.eim'/> + <flag name='intel-iommu.device-iotlb'/> + <flag name='virtio.iommu_platform'/> + <flag name='virtio.ats'/> + <flag name='loadparm'/> + <flag name='vnc-multi-servers'/> + <flag name='virtio-net.tx_queue_size'/> + <flag name='chardev-reconnect'/> + <flag name='virtio-gpu.max_outputs'/> + <flag name='vxhs'/> + <flag name='virtio-blk.num-queues'/> + <flag name='vmcoreinfo'/> + <flag name='numa.dist'/> + <flag name='disk-share-rw'/> + <flag name='iscsi.password-secret'/> + <flag name='isa-serial'/> + <flag name='dump-completed'/> + <flag name='qcow2-luks'/> + <flag name='pcie-pci-bridge'/> + <flag name='seccomp-blacklist'/> + <flag name='query-cpus-fast'/> + <flag name='disk-write-cache'/> + <flag name='nbd-tls'/> + <flag name='tpm-crb'/> + <flag name='pr-manager-helper'/> + <flag name='qom-list-properties'/> + <flag name='memory-backend-file.discard-data'/> + <flag name='sdl-gl'/> + <flag name='screendump_device'/> + <flag name='hda-output'/> + <flag name='blockdev-del'/> + <flag name='vmgenid'/> + <flag name='vhost-vsock'/> + <flag name='chardev-fd-pass'/> + <flag name='tpm-emulator'/> + <flag name='mch'/> + <flag name='mch.extended-tseg-mbytes'/> + <flag name='usb-storage.werror'/> + <flag name='egl-headless'/> + <flag name='vfio-pci.display'/> + <flag name='blockdev'/> + <flag name='memory-backend-memfd'/> + <flag name='memory-backend-memfd.hugetlb'/> + <flag name='iothread.poll-max-ns'/> + <flag name='egl-headless.rendernode'/> + <flag name='incremental-backup'/> + </qemuCaps> + <devices> + <device alias='rng0'/> + <device alias='sound0-codec0'/> + <device alias='virtio-disk0'/> + <device alias='virtio-serial0'/> + <device alias='video0'/> + <device alias='serial0'/> + <device alias='sound0'/> + <device alias='channel1'/> + <device alias='channel0'/> + <device alias='usb'/> + </devices> + <libDir path='/var/lib/libvirt/qemu/domain-4-copy'/> + <channelTargetDir path='/var/lib/libvirt/qemu/channel/target/domain-4-copy'/> + <chardevStdioLogd/> + <allowReboot value='yes'/> + <nodename index='0'/> + <fdset index='0'/> + <blockjobs active='no'/> + <agentTimeout>-2</agentTimeout> + <domain type='kvm' id='4'> + <name>copy</name> + <uuid>0439a4a8-db56-4933-9183-d8681d7b0746</uuid> + <memory unit='KiB'>1024000</memory> + <currentMemory unit='KiB'>1024000</currentMemory> + <vcpu placement='static'>1</vcpu> + <throttlegroups> + <throttlegroup> + <total_iops_sec>200</total_iops_sec> + <total_iops_sec_max>200</total_iops_sec_max> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + <group_name>limit0</group_name> + </throttlegroup> + <throttlegroup> + <total_iops_sec>250</total_iops_sec> + <total_iops_sec_max>250</total_iops_sec_max> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + <group_name>limit1</group_name> + </throttlegroup> + <throttlegroup> + <total_iops_sec>300</total_iops_sec> + <total_iops_sec_max>300</total_iops_sec_max> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + <group_name>limit2</group_name> + </throttlegroup> + <throttlegroup> + <total_iops_sec>400</total_iops_sec> + <total_iops_sec_max>400</total_iops_sec_max> + <total_iops_sec_max_length>1</total_iops_sec_max_length> + <group_name>limit012</group_name> + </throttlegroup> + </throttlegroups> + <resource> + <partition>/machine</partition> + </resource> + <os> + <type arch='x86_64' machine='pc-i440fx-2.9'>hvm</type> + <boot dev='hd'/> + <bootmenu enable='yes'/> + </os> + <features> + <acpi/> + <apic/> + <vmport state='off'/> + </features> + <clock offset='utc'> + <timer name='rtc' tickpolicy='catchup'/> + <timer name='pit' tickpolicy='delay'/> + <timer name='hpet' present='no'/> + </clock> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>restart</on_crash> + <pm> + <suspend-to-mem enabled='no'/> + <suspend-to-disk enabled='no'/> + </pm> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/pull4.qcow2' index='3'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-3-storage'/> + <nodename type='format' name='libvirt-3-format'/> + </nodenames> + </privateData> + </source> + <target dev='vda' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit0'/> + <throttlefilter group='limit012'/> + </throttlefilters> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0b' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk1/virtio-backend'/> + <nodenames> + <nodename type='throttle-filter' name='libvirt-4-filter' group='limit0'/> + <nodename type='throttle-filter' name='libvirt-5-filter' group='limit012'/> + </nodenames> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/pull5.qcow2' index='4'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-4-storage'/> + <nodename type='format' name='libvirt-4-format'/> + </nodenames> + </privateData> + </source> + <target dev='vda' bus='virtio'/> + <iotune> + <total_bytes_sec>10000000</total_bytes_sec> + <read_iops_sec>400000</read_iops_sec> + <write_iops_sec>100000</write_iops_sec> + </iotune> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0c' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk2/virtio-backend'/> + </privateData> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <alias name='usb'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'> + <alias name='pci.0'/> + </controller> + <serial type='pty'> + <source path='/dev/pts/34'/> + <target type='isa-serial' port='0'> + <model name='isa-serial'/> + </target> + <alias name='serial0'/> + </serial> + <console type='pty' tty='/dev/pts/34'> + <source path='/dev/pts/34'/> + <target type='serial' port='0'/> + <alias name='serial0'/> + </console> + <input type='mouse' bus='ps2'> + <alias name='input0'/> + </input> + <input type='keyboard' bus='ps2'> + <alias name='input1'/> + </input> + <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'> + <listen type='address' address='127.0.0.1' fromConfig='1' autoGenerated='no'/> + <image compression='off'/> + </graphics> + <audio id='1' type='spice'/> + <video> + <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/> + <alias name='video0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </video> + <memballoon model='none'/> + </devices> + <seclabel type='dynamic' model='selinux' relabel='yes'> + <label>unconfined_u:unconfined_r:svirt_t:s0:c550,c786</label> + <imagelabel>unconfined_u:object_r:svirt_image_t:s0:c550,c786</imagelabel> + </seclabel> + <seclabel type='dynamic' model='dac' relabel='yes'> + <label>+0:+0</label> + <imagelabel>+0:+0</imagelabel> + </seclabel> + </domain> +</domstatus> diff --git a/tests/qemuxmlactivetest.c b/tests/qemuxmlactivetest.c index 23ac807c76..5fc2b00c5a 100644 --- a/tests/qemuxmlactivetest.c +++ b/tests/qemuxmlactivetest.c @@ -247,6 +247,7 @@ mymain(void) DO_TEST_STATUS("blockjob-blockdev"); DO_TEST_STATUS("backup-pull"); + DO_TEST_STATUS("throttlefilter"); DO_TEST_STATUS("memory-backing-dir"); diff --git a/tests/qemuxmlconfdata/throttlefilter-invalid.x86_64-latest.err b/tests/qemuxmlconfdata/throttlefilter-invalid.x86_64-latest.err new file mode 100644 index 0000000000..ddb9670617 --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter-invalid.x86_64-latest.err @@ -0,0 +1 @@ +Operation not supported: block 'throttlefilters' can't be used together with 'iotune' for disk 'vda' diff --git a/tests/qemuxmlconfdata/throttlefilter-invalid.xml b/tests/qemuxmlconfdata/throttlefilter-invalid.xml new file mode 100644 index 0000000000..ebd250acac --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter-invalid.xml @@ -0,0 +1,89 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <throttlegroups> + <throttlegroup> + <total_bytes_sec>4000</total_bytes_sec> + <total_iops_sec>4000</total_iops_sec> + <group_name>limit0</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>5000</read_bytes_sec> + <write_bytes_sec>5000</write_bytes_sec> + <total_iops_sec>5000</total_iops_sec> + <group_name>limit1</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>6000</read_bytes_sec> + <write_bytes_sec>6000</write_bytes_sec> + <total_iops_sec>6000</total_iops_sec> + <group_name>limit2</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>7000</read_bytes_sec> + <write_bytes_sec>7000</write_bytes_sec> + <total_iops_sec>7000</total_iops_sec> + <group_name>limit12</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>7000</read_bytes_sec> + <write_bytes_sec>7000</write_bytes_sec> + <total_iops_sec>7000</total_iops_sec> + <group_name>limit3</group_name> + </throttlegroup> + </throttlegroups> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='vda' bus='virtio'/> + <iotune> + <total_bytes_sec>10000000</total_bytes_sec> + <read_iops_sec>400000</read_iops_sec> + <write_iops_sec>100000</write_iops_sec> + </iotune> + <throttlefilters> + <throttlefilter group='limit0'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='vdb' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit1'/> + <throttlefilter group='limit12'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest3'/> + <target dev='vdc' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit2'/> + <throttlefilter group='limit12'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> + </disk> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args b/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args new file mode 100644 index 0000000000..da757f91f9 --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args @@ -0,0 +1,55 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \ +-accel tcg \ +-cpu qemu64 \ +-m size=219136k \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-object '{"qom-type":"throttle-group","id":"throttle-limit0","limits":{"bps-total":4000,"iops-total":4000}}' \ +-object '{"qom-type":"throttle-group","id":"throttle-limit1","limits":{"bps-read":5000,"bps-write":5000,"iops-total":5000}}' \ +-object '{"qom-type":"throttle-group","id":"throttle-limit2","limits":{"bps-read":6000,"bps-write":6000,"iops-total":6000}}' \ +-object '{"qom-type":"throttle-group","id":"throttle-limit12","limits":{"bps-read":7000,"bps-write":7000,"iops-total":7000}}' \ +-object '{"qom-type":"throttle-group","id":"throttle-limit3","limits":{"bps-read":8000,"bps-write":8000,"iops-total":8000}}' \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-8-storage","auto-read-only":true,"discard":"unmap","cache":{"direct":true,"no-flush":false}}' \ +-blockdev '{"node-name":"libvirt-8-format","read-only":false,"cache":{"direct":true,"no-flush":false},"driver":"qcow2","file":"libvirt-8-storage"}' \ +-blockdev '{"driver":"throttle","node-name":"libvirt-9-filter","throttle-group":"throttle-limit0","file":"libvirt-8-format"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-9-filter","id":"virtio-disk0","bootindex":1,"write-cache":"on"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest2","node-name":"libvirt-5-storage","auto-read-only":true,"discard":"unmap","cache":{"direct":true,"no-flush":false}}' \ +-blockdev '{"node-name":"libvirt-5-format","read-only":false,"cache":{"direct":true,"no-flush":false},"driver":"qcow2","file":"libvirt-5-storage"}' \ +-blockdev '{"driver":"throttle","node-name":"libvirt-6-filter","throttle-group":"throttle-limit1","file":"libvirt-5-format"}' \ +-blockdev '{"driver":"throttle","node-name":"libvirt-7-filter","throttle-group":"throttle-limit12","file":"libvirt-6-filter"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x5","drive":"libvirt-7-filter","id":"virtio-disk1","write-cache":"on"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest3","node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap","cache":{"direct":true,"no-flush":false}}' \ +-blockdev '{"node-name":"libvirt-2-format","read-only":false,"cache":{"direct":true,"no-flush":false},"driver":"qcow2","file":"libvirt-2-storage"}' \ +-blockdev '{"driver":"throttle","node-name":"libvirt-3-filter","throttle-group":"throttle-limit2","file":"libvirt-2-format"}' \ +-blockdev '{"driver":"throttle","node-name":"libvirt-4-filter","throttle-group":"throttle-limit12","file":"libvirt-3-filter"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x6","drive":"libvirt-4-filter","id":"virtio-disk2","write-cache":"on"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest4","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap","cache":{"direct":true,"no-flush":false}}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":false,"cache":{"direct":true,"no-flush":false},"driver":"qcow2","file":"libvirt-1-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x7","drive":"libvirt-1-format","id":"virtio-disk3","write-cache":"on"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml b/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml new file mode 100644 index 0000000000..32d57f1723 --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml @@ -0,0 +1,105 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <throttlegroups> + <throttlegroup> + <total_bytes_sec>4000</total_bytes_sec> + <total_iops_sec>4000</total_iops_sec> + <group_name>limit0</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>5000</read_bytes_sec> + <write_bytes_sec>5000</write_bytes_sec> + <total_iops_sec>5000</total_iops_sec> + <group_name>limit1</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>6000</read_bytes_sec> + <write_bytes_sec>6000</write_bytes_sec> + <total_iops_sec>6000</total_iops_sec> + <group_name>limit2</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>7000</read_bytes_sec> + <write_bytes_sec>7000</write_bytes_sec> + <total_iops_sec>7000</total_iops_sec> + <group_name>limit12</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>8000</read_bytes_sec> + <write_bytes_sec>8000</write_bytes_sec> + <total_iops_sec>8000</total_iops_sec> + <group_name>limit3</group_name> + </throttlegroup> + </throttlegroups> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='vda' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit0'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='vdb' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit1'/> + <throttlefilter group='limit12'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest3'/> + <target dev='vdc' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit2'/> + <throttlefilter group='limit12'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest4'/> + <target dev='vdd' bus='virtio'/> + <iotune> + <total_bytes_sec>10000000</total_bytes_sec> + <read_iops_sec>400000</read_iops_sec> + <write_iops_sec>100000</write_iops_sec> + </iotune> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/throttlefilter.xml b/tests/qemuxmlconfdata/throttlefilter.xml new file mode 100644 index 0000000000..d7db766755 --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter.xml @@ -0,0 +1,95 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <throttlegroups> + <throttlegroup> + <total_bytes_sec>4000</total_bytes_sec> + <total_iops_sec>4000</total_iops_sec> + <group_name>limit0</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>5000</read_bytes_sec> + <write_bytes_sec>5000</write_bytes_sec> + <total_iops_sec>5000</total_iops_sec> + <group_name>limit1</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>6000</read_bytes_sec> + <write_bytes_sec>6000</write_bytes_sec> + <total_iops_sec>6000</total_iops_sec> + <group_name>limit2</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>7000</read_bytes_sec> + <write_bytes_sec>7000</write_bytes_sec> + <total_iops_sec>7000</total_iops_sec> + <group_name>limit12</group_name> + </throttlegroup> + <throttlegroup> + <read_bytes_sec>8000</read_bytes_sec> + <write_bytes_sec>8000</write_bytes_sec> + <total_iops_sec>8000</total_iops_sec> + <group_name>limit3</group_name> + </throttlegroup> + </throttlegroups> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='vda' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit0'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='vdb' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit1'/> + <throttlefilter group='limit12'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest3'/> + <target dev='vdc' bus='virtio'/> + <throttlefilters> + <throttlefilter group='limit2'/> + <throttlefilter group='limit12'/> + </throttlefilters> + <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest4'/> + <target dev='vdd' bus='virtio'/> + <iotune> + <total_bytes_sec>10000000</total_bytes_sec> + <read_iops_sec>400000</read_iops_sec> + <write_iops_sec>100000</write_iops_sec> + </iotune> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> + </disk> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c index 194a4b8553..a5a6fd4421 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -2349,6 +2349,8 @@ mymain(void) DO_TEST_CAPS_LATEST("blkdeviotune-max"); DO_TEST_CAPS_LATEST("blkdeviotune-group-num"); DO_TEST_CAPS_LATEST("blkdeviotune-max-length"); + DO_TEST_CAPS_LATEST("throttlefilter"); + DO_TEST_CAPS_LATEST_PARSE_ERROR("throttlefilter-invalid"); DO_TEST_CAPS_LATEST("multifunction-pci-device"); -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:21 +0530, Harikumar R wrote: [...]
create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-in.xml create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-out.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.x86_64-latest.err create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.xml
Test suite fails: 139/309 libvirt:bin / qemuxmlactivetest FAIL 0.15s exit status 1 Requires trivial fix: diff --git a/tests/qemustatusxml2xmldata/throttlefilter-out.xml b/tests/qemustatusxml2xmldata/throttlefilter-out.xml index 2217ad929d..8751a42cce 100644 --- a/tests/qemustatusxml2xmldata/throttlefilter-out.xml +++ b/tests/qemustatusxml2xmldata/throttlefilter-out.xml @@ -231,6 +231,7 @@ </devices> <libDir path='/var/lib/libvirt/qemu/domain-4-copy'/> <channelTargetDir path='/var/lib/libvirt/qemu/channel/target/domain-4-copy'/> + <memoryBackingDir path='/var/lib/libvirt/qemu/ram/4-copy'/> <chardevStdioLogd/> <allowReboot value='yes'/> <nodename index='0'/> Looks like you didn't base this posting on current master.

On Mon, Nov 18, 2024 at 19:24:21 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Test domain xml and status xml
* Add tests for "throttlegroup" domain xml processing, including groups referenced and not referenced by filters * Add tests for "throttlefilter" domain xml processing, including throttle group referenced by different disks * Add negative test case to report error when iotune is configured together with throttle filters * Add tests for throttlefilter nodename parse and format for statusxml (disk/privateData/nodenames/nodename with type='throttle-filter') * Add iotune limited disk tests to make sure iotune refactory works
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> ---
So you'll need to split this commit. The 'status XML' test should really be in a separate commit.
.../throttlefilter-in.xml | 392 ++++++++++++++++++ .../throttlefilter-out.xml | 392 ++++++++++++++++++ tests/qemuxmlactivetest.c | 1 + .../throttlefilter-invalid.x86_64-latest.err | 1 + .../throttlefilter-invalid.xml | 89 ++++ .../throttlefilter.x86_64-latest.args | 55 +++ .../throttlefilter.x86_64-latest.xml | 105 +++++ tests/qemuxmlconfdata/throttlefilter.xml | 95 +++++ tests/qemuxmlconftest.c | 2 + 9 files changed, 1132 insertions(+) create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-in.xml create mode 100644 tests/qemustatusxml2xmldata/throttlefilter-out.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.x86_64-latest.err create mode 100644 tests/qemuxmlconfdata/throttlefilter-invalid.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/throttlefilter.xml
The tests themselves look good; so after you deal with properly adding the missing line which was breaking the test you can use: Reviewed-by: Peter Krempa <pkrempa@redhat.com> for both.

From: Chun Feng Wu <danielwuwy@163.com> Test throttle group APIs * Extract common methods for both "testDomainSetThrottleGroup" and "testDomainSetBlockIoTune": testDomainValidateBlockIoTune, testDomainSetBlockIoTuneFields, testDomainCheckBlockIoTuneMutualExclusion, testDomainCheckBlockIoTuneMax * Test "Set": testDomainSetThrottleGroup * Test "Get": testDomainGetThrottleGroup * Test "Del": testDomainDelThrottleGroup Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/test/test_driver.c | 452 ++++++++++++++++++++++++++++++----------- 1 file changed, 330 insertions(+), 122 deletions(-) diff --git a/src/test/test_driver.c b/src/test/test_driver.c index f1cefb5c50..e9635c7c13 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -3783,25 +3783,8 @@ testDomainGetInterfaceParameters(virDomainPtr dom, #define TEST_BLOCK_IOTUNE_MAX 1000000000000000LL static int -testDomainSetBlockIoTune(virDomainPtr dom, - const char *path, - virTypedParameterPtr params, - int nparams, - unsigned int flags) +testDomainValidateBlockIoTune(virTypedParameterPtr params, int nparams) { - virDomainObj *vm = NULL; - virDomainDef *def = NULL; - virDomainBlockIoTuneInfo info = {0}; - virDomainDiskDef *conf_disk = NULL; - virTypedParameterPtr eventParams = NULL; - int eventNparams = 0; - int eventMaxparams = 0; - size_t i; - int ret = -1; - - virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | - VIR_DOMAIN_AFFECT_CONFIG, -1); - if (virTypedParamsValidate(params, nparams, VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC, VIR_TYPED_PARAM_ULLONG, @@ -3846,34 +3829,29 @@ testDomainSetBlockIoTune(virDomainPtr dom, NULL) < 0) return -1; - if (!(vm = testDomObjFromDomain(dom))) - return -1; - - if (!(def = virDomainObjGetOneDef(vm, flags))) - goto cleanup; - - if (!(conf_disk = virDomainDiskByName(def, path, true))) { - virReportError(VIR_ERR_INVALID_ARG, - _("missing persistent configuration for disk '%1$s'"), - path); - goto cleanup; - } + return 0; +} - info = conf_disk->blkdeviotune; - info.group_name = g_strdup(conf_disk->blkdeviotune.group_name); - if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, - VIR_DOMAIN_TUNABLE_BLKDEV_DISK, path) < 0) - goto cleanup; +static int +testDomainSetBlockIoTuneFields(virDomainBlockIoTuneInfo *info, + virTypedParameterPtr params, + int nparams, + virTypedParameterPtr *eventParams, + int *eventNparams, + int *eventMaxparams) +{ + size_t i; + int ret = -1; -#define SET_IOTUNE_FIELD(FIELD, STR, TUNABLE_STR) \ - if (STREQ(param->field, STR)) { \ - info.FIELD = param->value.ul; \ - if (virTypedParamsAddULLong(&eventParams, &eventNparams, \ - &eventMaxparams, \ - TUNABLE_STR, \ +#define SET_IOTUNE_FIELD(FIELD, CONST) \ + if (STREQ(param->field, VIR_DOMAIN_BLOCK_IOTUNE_##CONST)) { \ + info->FIELD = param->value.ul; \ + if (virTypedParamsAddULLong(eventParams, eventNparams, \ + eventMaxparams, \ + VIR_DOMAIN_TUNABLE_BLKDEV_##CONST, \ param->value.ul) < 0) \ - goto cleanup; \ + goto endjob; \ continue; \ } @@ -3884,119 +3862,99 @@ testDomainSetBlockIoTune(virDomainPtr dom, virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, _("block I/O throttle limit value must be no more than %1$llu"), TEST_BLOCK_IOTUNE_MAX); - goto cleanup; + goto endjob; } - SET_IOTUNE_FIELD(total_bytes_sec, - VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC, - VIR_DOMAIN_TUNABLE_BLKDEV_TOTAL_BYTES_SEC); - SET_IOTUNE_FIELD(read_bytes_sec, - VIR_DOMAIN_BLOCK_IOTUNE_READ_BYTES_SEC, - VIR_DOMAIN_TUNABLE_BLKDEV_READ_BYTES_SEC); - SET_IOTUNE_FIELD(write_bytes_sec, - VIR_DOMAIN_BLOCK_IOTUNE_WRITE_BYTES_SEC, - VIR_DOMAIN_TUNABLE_BLKDEV_WRITE_BYTES_SEC); - SET_IOTUNE_FIELD(total_iops_sec, - VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_IOPS_SEC, - VIR_DOMAIN_TUNABLE_BLKDEV_TOTAL_IOPS_SEC); - SET_IOTUNE_FIELD(read_iops_sec, - VIR_DOMAIN_BLOCK_IOTUNE_READ_IOPS_SEC, - VIR_DOMAIN_TUNABLE_BLKDEV_READ_IOPS_SEC); - SET_IOTUNE_FIELD(write_iops_sec, - VIR_DOMAIN_BLOCK_IOTUNE_WRITE_IOPS_SEC, - VIR_DOMAIN_TUNABLE_BLKDEV_WRITE_IOPS_SEC); - - SET_IOTUNE_FIELD(total_bytes_sec_max, - VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC_MAX, - VIR_DOMAIN_TUNABLE_BLKDEV_TOTAL_BYTES_SEC_MAX); - SET_IOTUNE_FIELD(read_bytes_sec_max, - VIR_DOMAIN_BLOCK_IOTUNE_READ_BYTES_SEC_MAX, - VIR_DOMAIN_TUNABLE_BLKDEV_READ_BYTES_SEC_MAX); - SET_IOTUNE_FIELD(write_bytes_sec_max, - VIR_DOMAIN_BLOCK_IOTUNE_WRITE_BYTES_SEC_MAX, - VIR_DOMAIN_TUNABLE_BLKDEV_WRITE_BYTES_SEC_MAX); - SET_IOTUNE_FIELD(total_iops_sec_max, - VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_IOPS_SEC_MAX, - VIR_DOMAIN_TUNABLE_BLKDEV_TOTAL_IOPS_SEC_MAX); - SET_IOTUNE_FIELD(read_iops_sec_max, - VIR_DOMAIN_BLOCK_IOTUNE_READ_IOPS_SEC_MAX, - VIR_DOMAIN_TUNABLE_BLKDEV_READ_IOPS_SEC_MAX); - SET_IOTUNE_FIELD(write_iops_sec_max, - VIR_DOMAIN_BLOCK_IOTUNE_WRITE_IOPS_SEC_MAX, - VIR_DOMAIN_TUNABLE_BLKDEV_WRITE_IOPS_SEC_MAX); - SET_IOTUNE_FIELD(size_iops_sec, - VIR_DOMAIN_BLOCK_IOTUNE_SIZE_IOPS_SEC, - VIR_DOMAIN_TUNABLE_BLKDEV_SIZE_IOPS_SEC); - + SET_IOTUNE_FIELD(total_bytes_sec, TOTAL_BYTES_SEC); + SET_IOTUNE_FIELD(read_bytes_sec, READ_BYTES_SEC); + SET_IOTUNE_FIELD(write_bytes_sec, WRITE_BYTES_SEC); + SET_IOTUNE_FIELD(total_iops_sec, TOTAL_IOPS_SEC); + SET_IOTUNE_FIELD(read_iops_sec, READ_IOPS_SEC); + SET_IOTUNE_FIELD(write_iops_sec, WRITE_IOPS_SEC); + + SET_IOTUNE_FIELD(total_bytes_sec_max, TOTAL_BYTES_SEC_MAX); + SET_IOTUNE_FIELD(read_bytes_sec_max, READ_BYTES_SEC_MAX); + SET_IOTUNE_FIELD(write_bytes_sec_max, WRITE_BYTES_SEC_MAX); + SET_IOTUNE_FIELD(total_iops_sec_max, TOTAL_IOPS_SEC_MAX); + SET_IOTUNE_FIELD(read_iops_sec_max, READ_IOPS_SEC_MAX); + SET_IOTUNE_FIELD(write_iops_sec_max, WRITE_IOPS_SEC_MAX); + SET_IOTUNE_FIELD(size_iops_sec, SIZE_IOPS_SEC); + + /* NB: Cannot use macro since this is a value.s not a value.ul */ if (STREQ(param->field, VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME)) { - VIR_FREE(info.group_name); - info.group_name = g_strdup(param->value.s); - if (virTypedParamsAddString(&eventParams, - &eventNparams, - &eventMaxparams, + VIR_FREE(info->group_name); + info->group_name = g_strdup(param->value.s); + if (virTypedParamsAddString(eventParams, eventNparams, + eventMaxparams, VIR_DOMAIN_TUNABLE_BLKDEV_GROUP_NAME, param->value.s) < 0) - goto cleanup; + goto endjob; continue; } - SET_IOTUNE_FIELD(total_bytes_sec_max_length, - VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC_MAX_LENGTH, - VIR_DOMAIN_TUNABLE_BLKDEV_TOTAL_BYTES_SEC_MAX_LENGTH); - SET_IOTUNE_FIELD(read_bytes_sec_max_length, - VIR_DOMAIN_BLOCK_IOTUNE_READ_BYTES_SEC_MAX_LENGTH, - VIR_DOMAIN_TUNABLE_BLKDEV_READ_BYTES_SEC_MAX_LENGTH); - SET_IOTUNE_FIELD(write_bytes_sec_max_length, - VIR_DOMAIN_BLOCK_IOTUNE_WRITE_BYTES_SEC_MAX_LENGTH, - VIR_DOMAIN_TUNABLE_BLKDEV_WRITE_BYTES_SEC_MAX_LENGTH); - SET_IOTUNE_FIELD(total_iops_sec_max_length, - VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_IOPS_SEC_MAX_LENGTH, - VIR_DOMAIN_TUNABLE_BLKDEV_TOTAL_IOPS_SEC_MAX_LENGTH); - SET_IOTUNE_FIELD(read_iops_sec_max_length, - VIR_DOMAIN_BLOCK_IOTUNE_READ_IOPS_SEC_MAX_LENGTH, - VIR_DOMAIN_TUNABLE_BLKDEV_READ_IOPS_SEC_MAX_LENGTH); - SET_IOTUNE_FIELD(write_iops_sec_max_length, - VIR_DOMAIN_BLOCK_IOTUNE_WRITE_IOPS_SEC_MAX_LENGTH, - VIR_DOMAIN_TUNABLE_BLKDEV_WRITE_IOPS_SEC_MAX_LENGTH); + SET_IOTUNE_FIELD(total_bytes_sec_max_length, TOTAL_BYTES_SEC_MAX_LENGTH); + SET_IOTUNE_FIELD(read_bytes_sec_max_length, READ_BYTES_SEC_MAX_LENGTH); + SET_IOTUNE_FIELD(write_bytes_sec_max_length, WRITE_BYTES_SEC_MAX_LENGTH); + SET_IOTUNE_FIELD(total_iops_sec_max_length, TOTAL_IOPS_SEC_MAX_LENGTH); + SET_IOTUNE_FIELD(read_iops_sec_max_length, READ_IOPS_SEC_MAX_LENGTH); + SET_IOTUNE_FIELD(write_iops_sec_max_length, WRITE_IOPS_SEC_MAX_LENGTH); } + #undef SET_IOTUNE_FIELD - if ((info.total_bytes_sec && info.read_bytes_sec) || - (info.total_bytes_sec && info.write_bytes_sec)) { + ret = 0; + endjob: + return ret; +} + + +static int +testDomainCheckBlockIoTuneMutualExclusion(virDomainBlockIoTuneInfo *info) +{ + if ((info->total_bytes_sec && info->read_bytes_sec) || + (info->total_bytes_sec && info->write_bytes_sec)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of bytes_sec cannot be set at the same time")); - goto cleanup; + return -1; } - if ((info.total_iops_sec && info.read_iops_sec) || - (info.total_iops_sec && info.write_iops_sec)) { + if ((info->total_iops_sec && info->read_iops_sec) || + (info->total_iops_sec && info->write_iops_sec)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of iops_sec cannot be set at the same time")); - goto cleanup; + return -1; } - if ((info.total_bytes_sec_max && info.read_bytes_sec_max) || - (info.total_bytes_sec_max && info.write_bytes_sec_max)) { + if ((info->total_bytes_sec_max && info->read_bytes_sec_max) || + (info->total_bytes_sec_max && info->write_bytes_sec_max)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of bytes_sec_max cannot be set at the same time")); - goto cleanup; + return -1; } - if ((info.total_iops_sec_max && info.read_iops_sec_max) || - (info.total_iops_sec_max && info.write_iops_sec_max)) { + if ((info->total_iops_sec_max && info->read_iops_sec_max) || + (info->total_iops_sec_max && info->write_iops_sec_max)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("total and read/write of iops_sec_max cannot be set at the same time")); - goto cleanup; + return -1; } + return 0; +} + + +static int +testDomainCheckBlockIoTuneMax(virDomainBlockIoTuneInfo *info) +{ + int ret = -1; #define TEST_BLOCK_IOTUNE_MAX_CHECK(FIELD, FIELD_MAX) \ do { \ - if (info.FIELD > info.FIELD_MAX) { \ + if (info->FIELD > info->FIELD_MAX) { \ virReportError(VIR_ERR_INVALID_ARG, \ _("%1$s cannot be set higher than %2$s"), \ #FIELD, #FIELD_MAX); \ - goto cleanup; \ + goto endjob; \ } \ } while (0); @@ -4009,6 +3967,69 @@ testDomainSetBlockIoTune(virDomainPtr dom, #undef TEST_BLOCK_IOTUNE_MAX_CHECK + ret = 0; + endjob: + return ret; +} + + +static int +testDomainSetBlockIoTune(virDomainPtr dom, + const char *path, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainBlockIoTuneInfo info = {0}; + virDomainDiskDef *conf_disk = NULL; + virTypedParameterPtr eventParams = NULL; + int eventNparams = 0; + int eventMaxparams = 0; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + + if (testDomainValidateBlockIoTune(params, nparams) < 0) + return -1; + + if (!(vm = testDomObjFromDomain(dom))) + return -1; + + if (!(def = virDomainObjGetOneDef(vm, flags))) + goto cleanup; + + if (!(conf_disk = virDomainDiskByName(def, path, true))) { + virReportError(VIR_ERR_INVALID_ARG, + _("missing persistent configuration for disk '%1$s'"), + path); + goto cleanup; + } + + info = conf_disk->blkdeviotune; + info.group_name = g_strdup(conf_disk->blkdeviotune.group_name); + + if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, + VIR_DOMAIN_TUNABLE_BLKDEV_DISK, path) < 0) + goto cleanup; + + if (testDomainSetBlockIoTuneFields(&info, + params, + nparams, + &eventParams, + &eventNparams, + &eventMaxparams) < 0) + goto cleanup; + + if (testDomainCheckBlockIoTuneMutualExclusion(&info) < 0) + goto cleanup; + + + if (testDomainCheckBlockIoTuneMax(&info) < 0) + goto cleanup; + virDomainDiskSetBlockIOTune(conf_disk, &info); ret = 0; @@ -4119,6 +4140,190 @@ testDomainGetBlockIoTune(virDomainPtr dom, virDomainObjEndAPI(&vm); return ret; } + + +static int +testDomainSetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainThrottleGroupDef info = { 0 }; + virDomainThrottleGroupDef *cur_info = NULL; + virTypedParameterPtr eventParams = NULL; + int eventNparams = 0; + int eventMaxparams = 0; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG, -1); + if (testDomainValidateBlockIoTune(params, nparams) < 0) + return -1; + + if (!(vm = testDomObjFromDomain(dom))) + return -1; + + if (!(def = virDomainObjGetOneDef(vm, flags))) + goto cleanup; + + if (virTypedParamsAddString(&eventParams, &eventNparams, &eventMaxparams, + VIR_DOMAIN_TUNABLE_BLKDEV_GROUP_NAME, group) < 0) + goto cleanup; + + if (testDomainSetBlockIoTuneFields(&info, + params, + nparams, + &eventParams, + &eventNparams, + &eventMaxparams) < 0) + goto cleanup; + + if (testDomainCheckBlockIoTuneMutualExclusion(&info) < 0) + goto cleanup; + + if (testDomainCheckBlockIoTuneMax(&info) < 0) + goto cleanup; + + cur_info = virDomainThrottleGroupByName(def, group); + if (cur_info != NULL) { + virDomainThrottleGroupUpdate(def, &info); + } else { + virDomainThrottleGroupAdd(def, &info); + } + ret = 0; + + cleanup: + VIR_FREE(info.group_name); + virDomainObjEndAPI(&vm); + if (eventNparams) + virTypedParamsFree(eventParams, eventNparams); + return ret; +} + + +static int +testDomainGetThrottleGroup(virDomainPtr dom, + const char *groupname, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainThrottleGroupDef groupDef = {0}; + virDomainThrottleGroupDef *reply = &groupDef; + int ret = -1; + int maxparams = 0; + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG | + VIR_TYPED_PARAM_STRING_OKAY, -1); + + + if (!(vm = testDomObjFromDomain(dom))) + return -1; + + if (!(def = virDomainObjGetOneDef(vm, flags))) + goto cleanup; + + + if (!(reply = virDomainThrottleGroupByName(def, groupname))) { + virReportError(VIR_ERR_INVALID_ARG, + _("throttle group '%1$s' was not found in the domain config"), + groupname); + goto cleanup; + } + reply->group_name = g_strdup(groupname); + +#define TEST_THROTTLE_GROUP_ASSIGN(name, var) \ + if (virTypedParamsAddULLong(params, \ + nparams, \ + &maxparams, \ + VIR_DOMAIN_BLOCK_IOTUNE_ ## name, \ + reply->var) < 0) \ + goto cleanup; + + if (virTypedParamsAddString(params, nparams, &maxparams, + "VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME", + reply->group_name) < 0) + goto cleanup; + + TEST_THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC, total_bytes_sec); + TEST_THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC, read_bytes_sec); + TEST_THROTTLE_GROUP_ASSIGN(WRITE_BYTES_SEC, write_bytes_sec); + TEST_THROTTLE_GROUP_ASSIGN(TOTAL_IOPS_SEC, total_iops_sec); + TEST_THROTTLE_GROUP_ASSIGN(READ_IOPS_SEC, read_iops_sec); + TEST_THROTTLE_GROUP_ASSIGN(WRITE_IOPS_SEC, write_iops_sec); + + TEST_THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC_MAX, total_bytes_sec_max); + TEST_THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC_MAX, read_bytes_sec_max); + TEST_THROTTLE_GROUP_ASSIGN(WRITE_BYTES_SEC_MAX, write_bytes_sec_max); + + TEST_THROTTLE_GROUP_ASSIGN(TOTAL_IOPS_SEC_MAX, total_iops_sec_max); + TEST_THROTTLE_GROUP_ASSIGN(READ_IOPS_SEC_MAX, read_iops_sec_max); + TEST_THROTTLE_GROUP_ASSIGN(WRITE_IOPS_SEC_MAX, write_iops_sec_max); + + TEST_THROTTLE_GROUP_ASSIGN(SIZE_IOPS_SEC, size_iops_sec); + + TEST_THROTTLE_GROUP_ASSIGN(TOTAL_BYTES_SEC_MAX_LENGTH, total_bytes_sec_max_length); + TEST_THROTTLE_GROUP_ASSIGN(READ_BYTES_SEC_MAX_LENGTH, read_bytes_sec_max_length); + TEST_THROTTLE_GROUP_ASSIGN(WRITE_BYTES_SEC_MAX_LENGTH, write_bytes_sec_max_length); + + TEST_THROTTLE_GROUP_ASSIGN(TOTAL_IOPS_SEC_MAX_LENGTH, total_iops_sec_max_length); + TEST_THROTTLE_GROUP_ASSIGN(READ_IOPS_SEC_MAX_LENGTH, read_iops_sec_max_length); + TEST_THROTTLE_GROUP_ASSIGN(WRITE_IOPS_SEC_MAX_LENGTH, write_iops_sec_max_length); +#undef TEST_THROTTLE_GROUP_ASSIGN + + ret = 0; + + cleanup: + if (reply != NULL && reply->group_name != NULL) { + g_free(reply->group_name); + } + virDomainObjEndAPI(&vm); + return ret; +} + + +static int +testDomainDelThrottleGroup(virDomainPtr dom, + const char *groupname, + unsigned int flags) +{ + virDomainObj *vm = NULL; + virDomainDef *def = NULL; + virDomainThrottleGroupDef groupDef = {0}; + virDomainThrottleGroupDef *reply = &groupDef; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | + VIR_DOMAIN_AFFECT_CONFIG | + VIR_TYPED_PARAM_STRING_OKAY, -1); + + + if (!(vm = testDomObjFromDomain(dom))) + return -1; + + if (!(def = virDomainObjGetOneDef(vm, flags))) + goto cleanup; + + if (!(reply = virDomainThrottleGroupByName(def, groupname))) { + virReportError(VIR_ERR_INVALID_ARG, + _("throttle group '%1$s' was not found in the domain config"), + groupname); + goto cleanup; + } + + ret = 0; + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + #undef TEST_SET_PARAM @@ -11210,6 +11415,9 @@ static virHypervisorDriver testHypervisorDriver = { .domainGetInterfaceParameters = testDomainGetInterfaceParameters, /* 5.6.0 */ .domainSetBlockIoTune = testDomainSetBlockIoTune, /* 5.7.0 */ .domainGetBlockIoTune = testDomainGetBlockIoTune, /* 5.7.0 */ + .domainSetThrottleGroup = testDomainSetThrottleGroup, /* 10.7.0 */ + .domainGetThrottleGroup = testDomainGetThrottleGroup, /* 10.7.0 */ + .domainDelThrottleGroup = testDomainDelThrottleGroup, /* 10.7.0 */ .domainSetBlkioParameters = testDomainSetBlkioParameters, /* 7.7.0 */ .domainGetBlkioParameters = testDomainGetBlkioParameters, /* 7.7.0 */ .connectListDefinedDomains = testConnectListDefinedDomains, /* 0.1.11 */ -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:22 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Test throttle group APIs
* Extract common methods for both "testDomainSetThrottleGroup" and "testDomainSetBlockIoTune": testDomainValidateBlockIoTune, testDomainSetBlockIoTuneFields, testDomainCheckBlockIoTuneMutualExclusion, testDomainCheckBlockIoTuneMax * Test "Set": testDomainSetThrottleGroup * Test "Get": testDomainGetThrottleGroup * Test "Del": testDomainDelThrottleGroup
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- src/test/test_driver.c | 452 ++++++++++++++++++++++++++++++----------- 1 file changed, 330 insertions(+), 122 deletions(-)
I'm guessing this will need some modifications similarly to the real impl but I won't really bother reviewing this in detail for now. This can be committed later in case there are problems.

From: Chun Feng Wu <danielwuwy@163.com> Define macro for iotune options, this macro is used by opts_blkdeviotune and later throttle group opts Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- tools/virsh-domain.c | 308 ++++++++++++++++++++++--------------------- 1 file changed, 156 insertions(+), 152 deletions(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index e4923284af..6eea5f176b 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1197,6 +1197,160 @@ static const vshCmdInfo info_blkdeviotune = { .desc = N_("Set or query disk I/O parameters such as block throttling."), }; +#define VSH_OPTS_IOTUNE() \ + {.name = "total_bytes_sec", \ + .type = VSH_OT_ALIAS, \ + .help = "total-bytes-sec" \ + }, \ + {.name = "total-bytes-sec", \ + .type = VSH_OT_INT, \ + .help = N_("total throughput limit, as scaled integer (default bytes)") \ + }, \ + {.name = "read_bytes_sec", \ + .type = VSH_OT_ALIAS, \ + .help = "read-bytes-sec" \ + }, \ + {.name = "read-bytes-sec", \ + .type = VSH_OT_INT, \ + .help = N_("read throughput limit, as scaled integer (default bytes)") \ + }, \ + {.name = "write_bytes_sec", \ + .type = VSH_OT_ALIAS, \ + .help = "write-bytes-sec" \ + }, \ + {.name = "write-bytes-sec", \ + .type = VSH_OT_INT, \ + .help = N_("write throughput limit, as scaled integer (default bytes)") \ + }, \ + {.name = "total_iops_sec", \ + .type = VSH_OT_ALIAS, \ + .help = "total-iops-sec" \ + }, \ + {.name = "total-iops-sec", \ + .type = VSH_OT_INT, \ + .help = N_("total I/O operations limit per second") \ + }, \ + {.name = "read_iops_sec", \ + .type = VSH_OT_ALIAS, \ + .help = "read-iops-sec" \ + }, \ + {.name = "read-iops-sec", \ + .type = VSH_OT_INT, \ + .help = N_("read I/O operations limit per second") \ + }, \ + {.name = "write_iops_sec", \ + .type = VSH_OT_ALIAS, \ + .help = "write-iops-sec" \ + }, \ + {.name = "write-iops-sec", \ + .type = VSH_OT_INT, \ + .help = N_("write I/O operations limit per second") \ + }, \ + {.name = "total_bytes_sec_max", \ + .type = VSH_OT_ALIAS, \ + .help = "total-bytes-sec-max" \ + }, \ + {.name = "total-bytes-sec-max", \ + .type = VSH_OT_INT, \ + .help = N_("total max, as scaled integer (default bytes)") \ + }, \ + {.name = "read_bytes_sec_max", \ + .type = VSH_OT_ALIAS, \ + .help = "read-bytes-sec-max" \ + }, \ + {.name = "read-bytes-sec-max", \ + .type = VSH_OT_INT, \ + .help = N_("read max, as scaled integer (default bytes)") \ + }, \ + {.name = "write_bytes_sec_max", \ + .type = VSH_OT_ALIAS, \ + .help = "write-bytes-sec-max" \ + }, \ + {.name = "write-bytes-sec-max", \ + .type = VSH_OT_INT, \ + .help = N_("write max, as scaled integer (default bytes)") \ + }, \ + {.name = "total_iops_sec_max", \ + .type = VSH_OT_ALIAS, \ + .help = "total-iops-sec-max" \ + }, \ + {.name = "total-iops-sec-max", \ + .type = VSH_OT_INT, \ + .help = N_("total I/O operations max") \ + }, \ + {.name = "read_iops_sec_max", \ + .type = VSH_OT_ALIAS, \ + .help = "read-iops-sec-max" \ + }, \ + {.name = "read-iops-sec-max", \ + .type = VSH_OT_INT, \ + .help = N_("read I/O operations max") \ + }, \ + {.name = "write_iops_sec_max", \ + .type = VSH_OT_ALIAS, \ + .help = "write-iops-sec-max" \ + }, \ + {.name = "write-iops-sec-max", \ + .type = VSH_OT_INT, \ + .help = N_("write I/O operations max") \ + }, \ + {.name = "size_iops_sec", \ + .type = VSH_OT_ALIAS, \ + .help = "size-iops-sec" \ + }, \ + {.name = "size-iops-sec", \ + .type = VSH_OT_INT, \ + .help = N_("I/O size in bytes") \ + }, \ + {.name = "total_bytes_sec_max_length", \ + .type = VSH_OT_ALIAS, \ + .help = "total-bytes-sec-max-length" \ + }, \ + {.name = "total-bytes-sec-max-length", \ + .type = VSH_OT_INT, \ + .help = N_("duration in seconds to allow total max bytes") \ + }, \ + {.name = "read_bytes_sec_max_length", \ + .type = VSH_OT_ALIAS, \ + .help = "read-bytes-sec-max-length" \ + }, \ + {.name = "read-bytes-sec-max-length", \ + .type = VSH_OT_INT, \ + .help = N_("duration in seconds to allow read max bytes") \ + }, \ + {.name = "write_bytes_sec_max_length", \ + .type = VSH_OT_ALIAS, \ + .help = "write-bytes-sec-max-length" \ + }, \ + {.name = "write-bytes-sec-max-length", \ + .type = VSH_OT_INT, \ + .help = N_("duration in seconds to allow write max bytes") \ + }, \ + {.name = "total_iops_sec_max_length", \ + .type = VSH_OT_ALIAS, \ + .help = "total-iops-sec-max-length" \ + }, \ + {.name = "total-iops-sec-max-length", \ + .type = VSH_OT_INT, \ + .help = N_("duration in seconds to allow total I/O operations max") \ + }, \ + {.name = "read_iops_sec_max_length", \ + .type = VSH_OT_ALIAS, \ + .help = "read-iops-sec-max-length" \ + }, \ + {.name = "read-iops-sec-max-length", \ + .type = VSH_OT_INT, \ + .help = N_("duration in seconds to allow read I/O operations max") \ + }, \ + {.name = "write_iops_sec_max_length", \ + .type = VSH_OT_ALIAS, \ + .help = "write-iops-sec-max-length" \ + }, \ + {.name = "write-iops-sec-max-length", \ + .type = VSH_OT_INT, \ + .help = N_("duration in seconds to allow write I/O operations max") \ + } \ + static const vshCmdOptDef opts_blkdeviotune[] = { VIRSH_COMMON_OPT_DOMAIN_FULL(0), {.name = "device", @@ -1206,110 +1360,6 @@ static const vshCmdOptDef opts_blkdeviotune[] = { .completer = virshDomainDiskTargetCompleter, .help = N_("block device") }, - {.name = "total_bytes_sec", - .type = VSH_OT_ALIAS, - .help = "total-bytes-sec" - }, - {.name = "total-bytes-sec", - .type = VSH_OT_INT, - .help = N_("total throughput limit, as scaled integer (default bytes)") - }, - {.name = "read_bytes_sec", - .type = VSH_OT_ALIAS, - .help = "read-bytes-sec" - }, - {.name = "read-bytes-sec", - .type = VSH_OT_INT, - .help = N_("read throughput limit, as scaled integer (default bytes)") - }, - {.name = "write_bytes_sec", - .type = VSH_OT_ALIAS, - .help = "write-bytes-sec" - }, - {.name = "write-bytes-sec", - .type = VSH_OT_INT, - .help = N_("write throughput limit, as scaled integer (default bytes)") - }, - {.name = "total_iops_sec", - .type = VSH_OT_ALIAS, - .help = "total-iops-sec" - }, - {.name = "total-iops-sec", - .type = VSH_OT_INT, - .help = N_("total I/O operations limit per second") - }, - {.name = "read_iops_sec", - .type = VSH_OT_ALIAS, - .help = "read-iops-sec" - }, - {.name = "read-iops-sec", - .type = VSH_OT_INT, - .help = N_("read I/O operations limit per second") - }, - {.name = "write_iops_sec", - .type = VSH_OT_ALIAS, - .help = "write-iops-sec" - }, - {.name = "write-iops-sec", - .type = VSH_OT_INT, - .help = N_("write I/O operations limit per second") - }, - {.name = "total_bytes_sec_max", - .type = VSH_OT_ALIAS, - .help = "total-bytes-sec-max" - }, - {.name = "total-bytes-sec-max", - .type = VSH_OT_INT, - .help = N_("total max, as scaled integer (default bytes)") - }, - {.name = "read_bytes_sec_max", - .type = VSH_OT_ALIAS, - .help = "read-bytes-sec-max" - }, - {.name = "read-bytes-sec-max", - .type = VSH_OT_INT, - .help = N_("read max, as scaled integer (default bytes)") - }, - {.name = "write_bytes_sec_max", - .type = VSH_OT_ALIAS, - .help = "write-bytes-sec-max" - }, - {.name = "write-bytes-sec-max", - .type = VSH_OT_INT, - .help = N_("write max, as scaled integer (default bytes)") - }, - {.name = "total_iops_sec_max", - .type = VSH_OT_ALIAS, - .help = "total-iops-sec-max" - }, - {.name = "total-iops-sec-max", - .type = VSH_OT_INT, - .help = N_("total I/O operations max") - }, - {.name = "read_iops_sec_max", - .type = VSH_OT_ALIAS, - .help = "read-iops-sec-max" - }, - {.name = "read-iops-sec-max", - .type = VSH_OT_INT, - .help = N_("read I/O operations max") - }, - {.name = "write_iops_sec_max", - .type = VSH_OT_ALIAS, - .help = "write-iops-sec-max" - }, - {.name = "write-iops-sec-max", - .type = VSH_OT_INT, - .help = N_("write I/O operations max") - }, - {.name = "size_iops_sec", - .type = VSH_OT_ALIAS, - .help = "size-iops-sec" - }, - {.name = "size-iops-sec", - .type = VSH_OT_INT, - .help = N_("I/O size in bytes") - }, {.name = "group_name", .type = VSH_OT_ALIAS, .help = "group-name" @@ -1319,59 +1369,13 @@ static const vshCmdOptDef opts_blkdeviotune[] = { .completer = virshCompleteEmpty, .help = N_("group name to share I/O quota between multiple drives") }, - {.name = "total_bytes_sec_max_length", - .type = VSH_OT_ALIAS, - .help = "total-bytes-sec-max-length" - }, - {.name = "total-bytes-sec-max-length", - .type = VSH_OT_INT, - .help = N_("duration in seconds to allow total max bytes") - }, - {.name = "read_bytes_sec_max_length", - .type = VSH_OT_ALIAS, - .help = "read-bytes-sec-max-length" - }, - {.name = "read-bytes-sec-max-length", - .type = VSH_OT_INT, - .help = N_("duration in seconds to allow read max bytes") - }, - {.name = "write_bytes_sec_max_length", - .type = VSH_OT_ALIAS, - .help = "write-bytes-sec-max-length" - }, - {.name = "write-bytes-sec-max-length", - .type = VSH_OT_INT, - .help = N_("duration in seconds to allow write max bytes") - }, - {.name = "total_iops_sec_max_length", - .type = VSH_OT_ALIAS, - .help = "total-iops-sec-max-length" - }, - {.name = "total-iops-sec-max-length", - .type = VSH_OT_INT, - .help = N_("duration in seconds to allow total I/O operations max") - }, - {.name = "read_iops_sec_max_length", - .type = VSH_OT_ALIAS, - .help = "read-iops-sec-max-length" - }, - {.name = "read-iops-sec-max-length", - .type = VSH_OT_INT, - .help = N_("duration in seconds to allow read I/O operations max") - }, - {.name = "write_iops_sec_max_length", - .type = VSH_OT_ALIAS, - .help = "write-iops-sec-max-length" - }, - {.name = "write-iops-sec-max-length", - .type = VSH_OT_INT, - .help = N_("duration in seconds to allow write I/O operations max") - }, + VSH_OPTS_IOTUNE(), VIRSH_COMMON_OPT_DOMAIN_CONFIG, VIRSH_COMMON_OPT_DOMAIN_LIVE, VIRSH_COMMON_OPT_DOMAIN_CURRENT, {.name = NULL} }; +#undef VSH_OPTS_IOTUNE static bool cmdBlkdeviotune(vshControl *ctl, const vshCmd *cmd) -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:23 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Define macro for iotune options, this macro is used by opts_blkdeviotune and later throttle group opts
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- tools/virsh-domain.c | 308 ++++++++++++++++++++++--------------------- 1 file changed, 156 insertions(+), 152 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <danielwuwy@163.com> Implement new throttle cmds * Add new virsh cmds: domthrottlegroupset, domthrottlegrouplist, domthrottlegroupinfo, domthrottlegroupdel * Add doc for new cmds at docs/manpages/virsh.rst * Add cmd helper "virshDomainThrottleGroupCompleter", which is used by domthrottlegroupset, domthrottlegroupinfo, domthrottlegroupdel Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/manpages/virsh.rst | 132 +++++++++++++ tools/virsh-completer-domain.c | 55 ++++++ tools/virsh-completer-domain.h | 11 ++ tools/virsh-domain.c | 349 ++++++++++++++++++++++++++++++++- 4 files changed, 546 insertions(+), 1 deletion(-) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index 6776ea53d0..3b78f77384 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -1122,6 +1122,138 @@ given, but *--current* is exclusive. For querying only one of *--live*, is different depending on hypervisor. +domthrottlegroupset +------------------- + +**Syntax:** + +:: + + domthrottlegroupset domain group-name [[--config] [--live] | [--current]] + [[total-bytes-sec] | [read-bytes-sec] [write-bytes-sec]] + [[total-iops-sec] | [read-iops-sec] [write-iops-sec]] + [[total-bytes-sec-max] | [read-bytes-sec-max] [write-bytes-sec-max]] + [[total-iops-sec-max] | [read-iops-sec-max] [write-iops-sec-max]] + [[total-bytes-sec-max-length] | + [read-bytes-sec-max-length] [write-bytes-sec-max-length]] + [[total-iops-sec-max-length] | + [read-iops-sec-max-length] [write-iops-sec-max-length]] + [size-iops-sec] + +Add or update a throttle group against specific *domain*. +*group-name* specifies a unique throttle group name, which defines limit, and +will be referenced by drives. +*domain* (see also ``domblklist`` for listing these names). + +If no limit is specified, default them as all zeros, which will fail, +Otherwise, set limits with these flags: +*--total-bytes-sec* specifies total throughput limit as a scaled integer, the +default being bytes per second if no suffix is specified. +*--read-bytes-sec* specifies read throughput limit as a scaled integer, the +default being bytes per second if no suffix is specified. +*--write-bytes-sec* specifies write throughput limit as a scaled integer, the +default being bytes per second if no suffix is specified. +*--total-iops-sec* specifies total I/O operations limit per second. +*--read-iops-sec* specifies read I/O operations limit per second. +*--write-iops-sec* specifies write I/O operations limit per second. +*--total-bytes-sec-max* specifies maximum total throughput limit as a scaled +integer, the default being bytes per second if no suffix is specified +*--read-bytes-sec-max* specifies maximum read throughput limit as a scaled +integer, the default being bytes per second if no suffix is specified. +*--write-bytes-sec-max* specifies maximum write throughput limit as a scaled +integer, the default being bytes per second if no suffix is specified. +*--total-iops-sec-max* specifies maximum total I/O operations limit per second. +*--read-iops-sec-max* specifies maximum read I/O operations limit per second. +*--write-iops-sec-max* specifies maximum write I/O operations limit per second. +*--total-bytes-sec-max-length* specifies duration in seconds to allow maximum +total throughput limit. +*--read-bytes-sec-max-length* specifies duration in seconds to allow maximum +read throughput limit. +*--write-bytes-sec-max-length* specifies duration in seconds to allow maximum +write throughput limit. +*--total-iops-sec-max-length* specifies duration in seconds to allow maximum +total I/O operations limit. +*--read-iops-sec-max-length* specifies duration in seconds to allow maximum +read I/O operations limit. +*--write-iops-sec-max-length* specifies duration in seconds to allow maximum +write I/O operations limit. +*--size-iops-sec* specifies size I/O operations limit per second. + +Bytes and iops values are independent, but setting only one value (such +as --read-bytes-sec) resets the other two in that category to unlimited. +An explicit 0 also clears any limit. A non-zero value for a given total +cannot be mixed with non-zero values for read or write. + +It is up to the hypervisor to determine how to handle the length values. +For the QEMU hypervisor, if an I/O limit value or maximum value is set, +then the default value of 1 second will be displayed. Supplying a 0 will +reset the value back to the default. + +If *--live* is specified, affect a running guest. +If *--config* is specified, affect the next start of a persistent guest. +If *--current* is specified, it is equivalent to either *--live* or +*--config*, depending on the current state of the guest. +When setting the disk io parameters both *--live* and *--config* flags may be +given, but *--current* is exclusive. If no flag is specified, behavior +is different depending on hypervisor. + + +domthrottlegroupdel +------------------- + +**Syntax:** + +:: + + domthrottlegroupdel domain group-name [[--config] [--live] | [--current]] + +Delete a Throttlegroup from the domain using the specified *group-name*. +If an Throttlegroup is currently referenced by a disk resource such as via the +``attach-disk`` command, then the attempt to remove the Throttlegroup will fail. +If the *group-name* does not exist an error will occur. + +If *--live* is specified, affect a running guest. If the guest is not +running an error is returned. +If *--config* is specified, affect the next start of a persistent guest. +If *--current* is specified, it is equivalent to either *--live* or +*--config*, depending on the current state of the guest. + + +domthrottlegroupinfo +-------------------- + +**Syntax:** + +:: + + domthrottlegroupinfo domain group-name [[--config] [--live] | [--current]] + +Display domain Throttlegroup information including I/O limits setting. + +If *--live* is specified, get the Throttlegroup data from the running guest. If +the guest is not running, an error is returned. +If *--config* is specified, get the Throttlegroup data from the next start of +a persistent guest. +If *--current* is specified or *--live* and *--config* are not specified, +then get the Throttlegroup data based on the current guest state, which can +either be live or offline. + + +domthrottlegrouplist +-------------------- + +**Syntax:** + +:: + + domthrottlegrouplist domain [--inactive] + +Print a table showing names of all throttle groups +associated with *domain*. If *--inactive* is specified, query the +block devices that will be used on the next boot, rather than those +currently in use by a running domain. + + blkiotune --------- diff --git a/tools/virsh-completer-domain.c b/tools/virsh-completer-domain.c index 61362224a3..3b0df15c13 100644 --- a/tools/virsh-completer-domain.c +++ b/tools/virsh-completer-domain.c @@ -248,6 +248,61 @@ virshDomainMigrateDisksCompleter(vshControl *ctl, } +int +virshGetThrottleGroupNames(xmlXPathContext *ctxt, + xmlNodePtr **groups, + char ***groupNames) +{ + int ngroups; + size_t i; + + ngroups = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, groups); + if (ngroups < 0) + return -1; + + *groupNames = g_new0(char *, ngroups + 1); + + for (i = 0; i < ngroups; i++) { + ctxt->node = (*groups)[i]; + if (!((*groupNames)[i] = virXPathString("string(./group_name)", ctxt))) { + g_strfreev(*groupNames); + *groupNames = NULL; + return -1; + } + } + + return ngroups; +} + + +char ** +virshDomainThrottleGroupCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags) +{ + virshControl *priv = ctl->privData; + g_autoptr(xmlDoc) xmldoc = NULL; + g_autoptr(xmlXPathContext) ctxt = NULL; + g_autofree xmlNodePtr *groups = NULL; + g_auto(GStrv) groupNames = NULL; + int ngroups; + + virCheckFlags(0, NULL); + + if (!priv->conn || virConnectIsAlive(priv->conn) <= 0) + return NULL; + + if (virshDomainGetXML(ctl, cmd, 0, &xmldoc, &ctxt) < 0) + return NULL; + + ngroups = virshGetThrottleGroupNames(ctxt, &groups, &groupNames); + if (ngroups < 0) + return NULL; + + return g_steal_pointer(&groupNames); +} + + char ** virshDomainUndefineStorageDisksCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-completer-domain.h b/tools/virsh-completer-domain.h index 27cf963912..680b3fc018 100644 --- a/tools/virsh-completer-domain.h +++ b/tools/virsh-completer-domain.h @@ -21,6 +21,7 @@ #pragma once #include "vsh.h" +#include <libxml/xpath.h> char ** virshDomainNameCompleter(vshControl *ctl, @@ -41,6 +42,16 @@ virshDomainDiskTargetCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags); +int +virshGetThrottleGroupNames(xmlXPathContext *ctxt, + xmlNodePtr **groups, + char ***groupNames); + +char ** +virshDomainThrottleGroupCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags); + char ** virshDomainInterfaceStateCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 6eea5f176b..b7004979c6 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1375,7 +1375,7 @@ static const vshCmdOptDef opts_blkdeviotune[] = { VIRSH_COMMON_OPT_DOMAIN_CURRENT, {.name = NULL} }; -#undef VSH_OPTS_IOTUNE + static bool cmdBlkdeviotune(vshControl *ctl, const vshCmd *cmd) @@ -1513,6 +1513,329 @@ cmdBlkdeviotune(vshControl *ctl, const vshCmd *cmd) goto cleanup; } + +/* + * "throttlegrouplist" command + */ +static const vshCmdInfo info_throttlegrouplist = { + .help = N_("list all domain throttlegroups."), + .desc = N_("Get the summary of throttle groups for a domain."), +}; + + +static const vshCmdOptDef opts_throttlegrouplist[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "inactive", + .type = VSH_OT_BOOL, + .help = N_("get inactive rather than running configuration") + }, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupList(vshControl *ctl, + const vshCmd *cmd) +{ + unsigned int flags = 0; + size_t i; + g_autoptr(xmlDoc) xml = NULL; + g_autoptr(xmlXPathContext) ctxt = NULL; + g_autofree xmlNodePtr *groups = NULL; + g_auto(GStrv) groupNames = NULL; + ssize_t ngroups; + g_autoptr(vshTable) table = NULL; + + if (vshCommandOptBool(cmd, "inactive")) + flags |= VIR_DOMAIN_XML_INACTIVE; + + if (virshDomainGetXML(ctl, cmd, flags, &xml, &ctxt) < 0) + return false; + + table = vshTableNew(_("Name"), NULL); + + if (!table) + return false; + + ngroups = virshGetThrottleGroupNames(ctxt, &groups, &groupNames); + for (i = 0; i < ngroups; i++) { + if (vshTableRowAppend(table, groupNames[i], NULL) < 0) + return false; + } + + vshTablePrintToStdout(table, ctl); + + return true; +} + + +/* + * "throttlegroupset" command + */ +static const vshCmdInfo info_throttlegroupset = { + .help = N_("Add or update a throttling group."), + .desc = N_("Add or updte a throttling group."), +}; + + +static const vshCmdOptDef opts_throttlegroupset[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "group-name", + .type = VSH_OT_STRING, + .positional = true, + .required = true, + .completer = virshDomainThrottleGroupCompleter, + .help = N_("throttle group name") + }, + VSH_OPTS_IOTUNE(), + VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; +#undef VSH_OPTS_IOTUNE + + +static bool +cmdThrottleGroupSet(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *group_name = NULL; + unsigned long long value; + int nparams = 0; + int maxparams = 0; + virTypedParameterPtr params = NULL; + unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; + int rv = 0; + bool current = vshCommandOptBool(cmd, "current"); + bool config = vshCommandOptBool(cmd, "config"); + bool live = vshCommandOptBool(cmd, "live"); + bool ret = false; + + VSH_EXCLUSIVE_OPTIONS_VAR(current, live); + VSH_EXCLUSIVE_OPTIONS_VAR(current, config); + + if (config) + flags |= VIR_DOMAIN_AFFECT_CONFIG; + if (live) + flags |= VIR_DOMAIN_AFFECT_LIVE; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + goto cleanup; + + +#define VSH_SET_THROTTLE_GROUP_SCALED(PARAM, CONST) \ + if ((rv = vshCommandOptScaledInt(ctl, cmd, #PARAM, &value, \ + 1, ULLONG_MAX)) < 0) { \ + goto interror; \ + } else if (rv > 0) { \ + if (virTypedParamsAddULLong(¶ms, &nparams, &maxparams, \ + VIR_DOMAIN_BLOCK_IOTUNE_##CONST, \ + value) < 0) \ + goto save_error; \ + } + + VSH_SET_THROTTLE_GROUP_SCALED(total-bytes-sec, TOTAL_BYTES_SEC); + VSH_SET_THROTTLE_GROUP_SCALED(read-bytes-sec, READ_BYTES_SEC); + VSH_SET_THROTTLE_GROUP_SCALED(write-bytes-sec, WRITE_BYTES_SEC); + VSH_SET_THROTTLE_GROUP_SCALED(total-bytes-sec-max, TOTAL_BYTES_SEC_MAX); + VSH_SET_THROTTLE_GROUP_SCALED(read-bytes-sec-max, READ_BYTES_SEC_MAX); + VSH_SET_THROTTLE_GROUP_SCALED(write-bytes-sec-max, WRITE_BYTES_SEC_MAX); +#undef VSH_SET_THROTTLE_GROUP_SCALED + +#define VSH_SET_THROTTLE_GROUP(PARAM, CONST) \ + if ((rv = vshCommandOptULongLong(ctl, cmd, #PARAM, &value)) < 0) { \ + goto interror; \ + } else if (rv > 0) { \ + if (virTypedParamsAddULLong(¶ms, &nparams, &maxparams, \ + VIR_DOMAIN_BLOCK_IOTUNE_##CONST, \ + value) < 0) \ + goto save_error; \ + } + + VSH_SET_THROTTLE_GROUP(total-iops-sec, TOTAL_IOPS_SEC); + VSH_SET_THROTTLE_GROUP(read-iops-sec, READ_IOPS_SEC); + VSH_SET_THROTTLE_GROUP(write-iops-sec, WRITE_IOPS_SEC); + VSH_SET_THROTTLE_GROUP(total-iops-sec-max, TOTAL_IOPS_SEC_MAX); + VSH_SET_THROTTLE_GROUP(read-iops-sec-max, READ_IOPS_SEC_MAX); + VSH_SET_THROTTLE_GROUP(write-iops-sec-max, WRITE_IOPS_SEC_MAX); + VSH_SET_THROTTLE_GROUP(size-iops-sec, SIZE_IOPS_SEC); + + VSH_SET_THROTTLE_GROUP(total-bytes-sec-max-length, TOTAL_BYTES_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(read-bytes-sec-max-length, READ_BYTES_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(write-bytes-sec-max-length, WRITE_BYTES_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(total-iops-sec-max-length, TOTAL_IOPS_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(read-iops-sec-max-length, READ_IOPS_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(write-iops-sec-max-length, WRITE_IOPS_SEC_MAX_LENGTH); +#undef VSH_SET_THROTTLE_GROUP + + if (vshCommandOptString(ctl, cmd, "group-name", &group_name) < 0) { + goto cleanup; + } + + if (group_name) { + if (virTypedParamsAddString(¶ms, &nparams, &maxparams, + VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME, + group_name) < 0) + goto save_error; + } + + if (virDomainSetThrottleGroup(dom, group_name, params, nparams, flags) < 0) + goto error; + vshPrintExtra(ctl, "%s", _("Throttle group set successfully\n")); + + ret = true; + + cleanup: + virTypedParamsFree(params, nparams); + return ret; + + save_error: + vshSaveLibvirtError(); + error: + vshError(ctl, "%s", _("Unable to set throttle group")); + goto cleanup; + + interror: + vshError(ctl, "%s", _("Unable to parse integer parameter")); + goto cleanup; +} + + +/* + * "throttlegroupdel" command + */ +static const vshCmdInfo info_throttlegroupdel = { + .help = N_("Delete a throttling group."), + .desc = N_("Delete a throttling group."), +}; + + +static const vshCmdOptDef opts_throttlegroupdel[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "group-name", + .type = VSH_OT_STRING, + .positional = true, + .required = true, + .completer = virshDomainThrottleGroupCompleter, + .help = N_("throttle group name") + }, + VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupDel(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *group_name = NULL; + bool config = vshCommandOptBool(cmd, "config"); + bool live = vshCommandOptBool(cmd, "live"); + bool current = vshCommandOptBool(cmd, "current"); + unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; + + VSH_EXCLUSIVE_OPTIONS_VAR(current, live); + VSH_EXCLUSIVE_OPTIONS_VAR(current, config); + + if (config) + flags |= VIR_DOMAIN_AFFECT_CONFIG; + if (live) + flags |= VIR_DOMAIN_AFFECT_LIVE; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptString(ctl, cmd, "group-name", &group_name) < 0) { + return false; + } + + if (virDomainDelThrottleGroup(dom, group_name, flags) < 0) + return false; + vshPrintExtra(ctl, "%s", _("Throttle group deleted successfully\n")); + + return true; +} + + +/* + * "throttlegroupinfo" command + */ +static const vshCmdInfo info_throttlegroupinfo = { + .help = N_("Get a throttling group."), + .desc = N_("Get a throttling group."), +}; + + +static const vshCmdOptDef opts_throttlegroupinfo[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "group-name", + .type = VSH_OT_STRING, + .positional = true, + .required = true, + .completer = virshDomainThrottleGroupCompleter, + .help = N_("throttle group name") + }, + VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupInfo(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *group_name = NULL; + int nparams = 0; + virTypedParameterPtr params = NULL; + unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; + size_t i; + bool current = vshCommandOptBool(cmd, "current"); + bool config = vshCommandOptBool(cmd, "config"); + bool live = vshCommandOptBool(cmd, "live"); + bool ret = false; + + VSH_EXCLUSIVE_OPTIONS_VAR(current, live); + VSH_EXCLUSIVE_OPTIONS_VAR(current, config); + + if (config) + flags |= VIR_DOMAIN_AFFECT_CONFIG; + if (live) + flags |= VIR_DOMAIN_AFFECT_LIVE; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + goto cleanup; + + if (vshCommandOptString(ctl, cmd, "group-name", &group_name) < 0) { + goto cleanup; + } + + if (virDomainGetThrottleGroup(dom, group_name, ¶ms, &nparams, flags) != 0) { + vshError(ctl, "%s", + _("Unable to get throttle group parameters")); + goto cleanup; + } + + for (i = 0; i < nparams; i++) { + g_autofree char *str = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, "%-15s: %s\n", params[i].field, str); + } + + ret = true; + + cleanup: + virTypedParamsFree(params, nparams); + return ret; +} + + /* * "blkiotune" command */ @@ -13408,6 +13731,30 @@ const vshCmdDef domManagementCmds[] = { .info = &info_blkdeviotune, .flags = 0 }, + {.name = "domthrottlegroupset", + .handler = cmdThrottleGroupSet, + .opts = opts_throttlegroupset, + .info = &info_throttlegroupset, + .flags = 0 + }, + {.name = "domthrottlegroupdel", + .handler = cmdThrottleGroupDel, + .opts = opts_throttlegroupdel, + .info = &info_throttlegroupdel, + .flags = 0 + }, + {.name = "domthrottlegroupinfo", + .handler = cmdThrottleGroupInfo, + .opts = opts_throttlegroupinfo, + .info = &info_throttlegroupinfo, + .flags = 0 + }, + {.name = "domthrottlegrouplist", + .handler = cmdThrottleGroupList, + .opts = opts_throttlegrouplist, + .info = &info_throttlegrouplist, + .flags = 0 + }, {.name = "blkiotune", .handler = cmdBlkiotune, .opts = opts_blkiotune, -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:24 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Implement new throttle cmds
* Add new virsh cmds: domthrottlegroupset, domthrottlegrouplist, domthrottlegroupinfo, domthrottlegroupdel * Add doc for new cmds at docs/manpages/virsh.rst * Add cmd helper "virshDomainThrottleGroupCompleter", which is used by domthrottlegroupset, domthrottlegroupinfo, domthrottlegroupdel
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/manpages/virsh.rst | 132 +++++++++++++ tools/virsh-completer-domain.c | 55 ++++++ tools/virsh-completer-domain.h | 11 ++ tools/virsh-domain.c | 349 ++++++++++++++++++++++++++++++++- 4 files changed, 546 insertions(+), 1 deletion(-)
diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index 6776ea53d0..3b78f77384 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -1122,6 +1122,138 @@ given, but *--current* is exclusive. For querying only one of *--live*, is different depending on hypervisor.
+domthrottlegroupset +------------------- + +**Syntax:** + +:: + + domthrottlegroupset domain group-name [[--config] [--live] | [--current]] + [[total-bytes-sec] | [read-bytes-sec] [write-bytes-sec]] + [[total-iops-sec] | [read-iops-sec] [write-iops-sec]] + [[total-bytes-sec-max] | [read-bytes-sec-max] [write-bytes-sec-max]] + [[total-iops-sec-max] | [read-iops-sec-max] [write-iops-sec-max]] + [[total-bytes-sec-max-length] | + [read-bytes-sec-max-length] [write-bytes-sec-max-length]] + [[total-iops-sec-max-length] | + [read-iops-sec-max-length] [write-iops-sec-max-length]] + [size-iops-sec] + +Add or update a throttle group against specific *domain*. +*group-name* specifies a unique throttle group name, which defines limit, and +will be referenced by drives. +*domain* (see also ``domblklist`` for listing these names).
Referencing domblklist doesn't seem to be relevant here.
+ +If no limit is specified, default them as all zeros, which will fail, +Otherwise, set limits with these flags: +*--total-bytes-sec* specifies total throughput limit as a scaled integer, the +default being bytes per second if no suffix is specified. +*--read-bytes-sec* specifies read throughput limit as a scaled integer, the +default being bytes per second if no suffix is specified. +*--write-bytes-sec* specifies write throughput limit as a scaled integer, the +default being bytes per second if no suffix is specified. +*--total-iops-sec* specifies total I/O operations limit per second. +*--read-iops-sec* specifies read I/O operations limit per second. +*--write-iops-sec* specifies write I/O operations limit per second. +*--total-bytes-sec-max* specifies maximum total throughput limit as a scaled +integer, the default being bytes per second if no suffix is specified +*--read-bytes-sec-max* specifies maximum read throughput limit as a scaled +integer, the default being bytes per second if no suffix is specified. +*--write-bytes-sec-max* specifies maximum write throughput limit as a scaled +integer, the default being bytes per second if no suffix is specified. +*--total-iops-sec-max* specifies maximum total I/O operations limit per second. +*--read-iops-sec-max* specifies maximum read I/O operations limit per second. +*--write-iops-sec-max* specifies maximum write I/O operations limit per second. +*--total-bytes-sec-max-length* specifies duration in seconds to allow maximum +total throughput limit. +*--read-bytes-sec-max-length* specifies duration in seconds to allow maximum +read throughput limit. +*--write-bytes-sec-max-length* specifies duration in seconds to allow maximum +write throughput limit. +*--total-iops-sec-max-length* specifies duration in seconds to allow maximum +total I/O operations limit. +*--read-iops-sec-max-length* specifies duration in seconds to allow maximum +read I/O operations limit. +*--write-iops-sec-max-length* specifies duration in seconds to allow maximum +write I/O operations limit. +*--size-iops-sec* specifies size I/O operations limit per second. + +Bytes and iops values are independent, but setting only one value (such +as --read-bytes-sec) resets the other two in that category to unlimited. +An explicit 0 also clears any limit. A non-zero value for a given total +cannot be mixed with non-zero values for read or write. + +It is up to the hypervisor to determine how to handle the length values. +For the QEMU hypervisor, if an I/O limit value or maximum value is set, +then the default value of 1 second will be displayed. Supplying a 0 will +reset the value back to the default. + +If *--live* is specified, affect a running guest. +If *--config* is specified, affect the next start of a persistent guest. +If *--current* is specified, it is equivalent to either *--live* or +*--config*, depending on the current state of the guest. +When setting the disk io parameters both *--live* and *--config* flags may be
This also seems to be referencing something that's not happening here.
+given, but *--current* is exclusive. If no flag is specified, behavior +is different depending on hypervisor. + + +domthrottlegroupdel +------------------- + +**Syntax:** + +:: + + domthrottlegroupdel domain group-name [[--config] [--live] | [--current]] + +Delete a Throttlegroup from the domain using the specified *group-name*. +If an Throttlegroup is currently referenced by a disk resource such as via the +``attach-disk`` command, then the attempt to remove the Throttlegroup will fail.
Referencing 'attach-disk' makes no sense. Stick to mentioning that it's used by a disk.
+If the *group-name* does not exist an error will occur. + +If *--live* is specified, affect a running guest. If the guest is not +running an error is returned. +If *--config* is specified, affect the next start of a persistent guest. +If *--current* is specified, it is equivalent to either *--live* or +*--config*, depending on the current state of the guest. + + +domthrottlegroupinfo +-------------------- + +**Syntax:** + +:: + + domthrottlegroupinfo domain group-name [[--config] [--live] | [--current]] + +Display domain Throttlegroup information including I/O limits setting. + +If *--live* is specified, get the Throttlegroup data from the running guest. If +the guest is not running, an error is returned. +If *--config* is specified, get the Throttlegroup data from the next start of +a persistent guest. +If *--current* is specified or *--live* and *--config* are not specified, +then get the Throttlegroup data based on the current guest state, which can +either be live or offline.
Missing note that --live and --config together makes no sense.
+ + +domthrottlegrouplist +-------------------- + +**Syntax:** + +:: + + domthrottlegrouplist domain [--inactive] + +Print a table showing names of all throttle groups +associated with *domain*. If *--inactive* is specified, query the +block devices that will be used on the next boot, rather than those
This is querying throttle groups; not block devices.
+currently in use by a running domain. + + blkiotune ---------
diff --git a/tools/virsh-completer-domain.c b/tools/virsh-completer-domain.c index 61362224a3..3b0df15c13 100644 --- a/tools/virsh-completer-domain.c +++ b/tools/virsh-completer-domain.c @@ -248,6 +248,61 @@ virshDomainMigrateDisksCompleter(vshControl *ctl, }
+int +virshGetThrottleGroupNames(xmlXPathContext *ctxt, + xmlNodePtr **groups, + char ***groupNames) +{ + int ngroups; + size_t i; + + ngroups = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, groups); + if (ngroups < 0) + return -1; + + *groupNames = g_new0(char *, ngroups + 1); + + for (i = 0; i < ngroups; i++) { + ctxt->node = (*groups)[i]; + if (!((*groupNames)[i] = virXPathString("string(./group_name)", ctxt))) { + g_strfreev(*groupNames); + *groupNames = NULL; + return -1; + } + } + + return ngroups; +} + + +char ** +virshDomainThrottleGroupCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags) +{ + virshControl *priv = ctl->privData; + g_autoptr(xmlDoc) xmldoc = NULL; + g_autoptr(xmlXPathContext) ctxt = NULL; + g_autofree xmlNodePtr *groups = NULL; + g_auto(GStrv) groupNames = NULL; + int ngroups; + + virCheckFlags(0, NULL); + + if (!priv->conn || virConnectIsAlive(priv->conn) <= 0) + return NULL; + + if (virshDomainGetXML(ctl, cmd, 0, &xmldoc, &ctxt) < 0) + return NULL; + + ngroups = virshGetThrottleGroupNames(ctxt, &groups, &groupNames); + if (ngroups < 0) + return NULL; + + return g_steal_pointer(&groupNames); +} + + char ** virshDomainUndefineStorageDisksCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-completer-domain.h b/tools/virsh-completer-domain.h index 27cf963912..680b3fc018 100644 --- a/tools/virsh-completer-domain.h +++ b/tools/virsh-completer-domain.h @@ -21,6 +21,7 @@ #pragma once
#include "vsh.h" +#include <libxml/xpath.h>
char ** virshDomainNameCompleter(vshControl *ctl, @@ -41,6 +42,16 @@ virshDomainDiskTargetCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags);
+int +virshGetThrottleGroupNames(xmlXPathContext *ctxt, + xmlNodePtr **groups, + char ***groupNames); + +char ** +virshDomainThrottleGroupCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags); + char ** virshDomainInterfaceStateCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 6eea5f176b..b7004979c6 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1375,7 +1375,7 @@ static const vshCmdOptDef opts_blkdeviotune[] = { VIRSH_COMMON_OPT_DOMAIN_CURRENT, {.name = NULL} }; -#undef VSH_OPTS_IOTUNE +
static bool cmdBlkdeviotune(vshControl *ctl, const vshCmd *cmd) @@ -1513,6 +1513,329 @@ cmdBlkdeviotune(vshControl *ctl, const vshCmd *cmd) goto cleanup; }
+ +/* + * "throttlegrouplist" command + */ +static const vshCmdInfo info_throttlegrouplist = { + .help = N_("list all domain throttlegroups."), + .desc = N_("Get the summary of throttle groups for a domain."), +}; + + +static const vshCmdOptDef opts_throttlegrouplist[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "inactive", + .type = VSH_OT_BOOL, + .help = N_("get inactive rather than running configuration") + }, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupList(vshControl *ctl, + const vshCmd *cmd) +{ + unsigned int flags = 0; + size_t i; + g_autoptr(xmlDoc) xml = NULL; + g_autoptr(xmlXPathContext) ctxt = NULL; + g_autofree xmlNodePtr *groups = NULL; + g_auto(GStrv) groupNames = NULL; + ssize_t ngroups; + g_autoptr(vshTable) table = NULL; + + if (vshCommandOptBool(cmd, "inactive")) + flags |= VIR_DOMAIN_XML_INACTIVE; + + if (virshDomainGetXML(ctl, cmd, flags, &xml, &ctxt) < 0) + return false; + + table = vshTableNew(_("Name"), NULL); + + if (!table) + return false; + + ngroups = virshGetThrottleGroupNames(ctxt, &groups, &groupNames); + for (i = 0; i < ngroups; i++) { + if (vshTableRowAppend(table, groupNames[i], NULL) < 0) + return false; + } + + vshTablePrintToStdout(table, ctl); + + return true; +} + + +/* + * "throttlegroupset" command + */ +static const vshCmdInfo info_throttlegroupset = { + .help = N_("Add or update a throttling group."), + .desc = N_("Add or updte a throttling group."), +}; + + +static const vshCmdOptDef opts_throttlegroupset[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "group-name", + .type = VSH_OT_STRING, + .positional = true, + .required = true, + .completer = virshDomainThrottleGroupCompleter, + .help = N_("throttle group name") + }, + VSH_OPTS_IOTUNE(), + VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; +#undef VSH_OPTS_IOTUNE + + +static bool +cmdThrottleGroupSet(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *group_name = NULL; + unsigned long long value; + int nparams = 0; + int maxparams = 0; + virTypedParameterPtr params = NULL; + unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; + int rv = 0; + bool current = vshCommandOptBool(cmd, "current"); + bool config = vshCommandOptBool(cmd, "config"); + bool live = vshCommandOptBool(cmd, "live"); + bool ret = false; + + VSH_EXCLUSIVE_OPTIONS_VAR(current, live); + VSH_EXCLUSIVE_OPTIONS_VAR(current, config); + + if (config) + flags |= VIR_DOMAIN_AFFECT_CONFIG; + if (live) + flags |= VIR_DOMAIN_AFFECT_LIVE; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + goto cleanup; + + +#define VSH_SET_THROTTLE_GROUP_SCALED(PARAM, CONST) \ + if ((rv = vshCommandOptScaledInt(ctl, cmd, #PARAM, &value, \ + 1, ULLONG_MAX)) < 0) { \ + goto interror; \ + } else if (rv > 0) { \ + if (virTypedParamsAddULLong(¶ms, &nparams, &maxparams, \ + VIR_DOMAIN_BLOCK_IOTUNE_##CONST, \ + value) < 0) \ + goto save_error; \ + } + + VSH_SET_THROTTLE_GROUP_SCALED(total-bytes-sec, TOTAL_BYTES_SEC); + VSH_SET_THROTTLE_GROUP_SCALED(read-bytes-sec, READ_BYTES_SEC); + VSH_SET_THROTTLE_GROUP_SCALED(write-bytes-sec, WRITE_BYTES_SEC); + VSH_SET_THROTTLE_GROUP_SCALED(total-bytes-sec-max, TOTAL_BYTES_SEC_MAX); + VSH_SET_THROTTLE_GROUP_SCALED(read-bytes-sec-max, READ_BYTES_SEC_MAX); + VSH_SET_THROTTLE_GROUP_SCALED(write-bytes-sec-max, WRITE_BYTES_SEC_MAX); +#undef VSH_SET_THROTTLE_GROUP_SCALED + +#define VSH_SET_THROTTLE_GROUP(PARAM, CONST) \ + if ((rv = vshCommandOptULongLong(ctl, cmd, #PARAM, &value)) < 0) { \ + goto interror; \ + } else if (rv > 0) { \ + if (virTypedParamsAddULLong(¶ms, &nparams, &maxparams, \ + VIR_DOMAIN_BLOCK_IOTUNE_##CONST, \ + value) < 0) \ + goto save_error; \ + } + + VSH_SET_THROTTLE_GROUP(total-iops-sec, TOTAL_IOPS_SEC); + VSH_SET_THROTTLE_GROUP(read-iops-sec, READ_IOPS_SEC); + VSH_SET_THROTTLE_GROUP(write-iops-sec, WRITE_IOPS_SEC); + VSH_SET_THROTTLE_GROUP(total-iops-sec-max, TOTAL_IOPS_SEC_MAX); + VSH_SET_THROTTLE_GROUP(read-iops-sec-max, READ_IOPS_SEC_MAX); + VSH_SET_THROTTLE_GROUP(write-iops-sec-max, WRITE_IOPS_SEC_MAX); + VSH_SET_THROTTLE_GROUP(size-iops-sec, SIZE_IOPS_SEC); + + VSH_SET_THROTTLE_GROUP(total-bytes-sec-max-length, TOTAL_BYTES_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(read-bytes-sec-max-length, READ_BYTES_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(write-bytes-sec-max-length, WRITE_BYTES_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(total-iops-sec-max-length, TOTAL_IOPS_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(read-iops-sec-max-length, READ_IOPS_SEC_MAX_LENGTH); + VSH_SET_THROTTLE_GROUP(write-iops-sec-max-length, WRITE_IOPS_SEC_MAX_LENGTH); +#undef VSH_SET_THROTTLE_GROUP + + if (vshCommandOptString(ctl, cmd, "group-name", &group_name) < 0) { + goto cleanup; + } + + if (group_name) { + if (virTypedParamsAddString(¶ms, &nparams, &maxparams, + VIR_DOMAIN_BLOCK_IOTUNE_GROUP_NAME, + group_name) < 0) + goto save_error; + } + + if (virDomainSetThrottleGroup(dom, group_name, params, nparams, flags) < 0) + goto error; + vshPrintExtra(ctl, "%s", _("Throttle group set successfully\n")); + + ret = true; + + cleanup: + virTypedParamsFree(params, nparams); + return ret; + + save_error: + vshSaveLibvirtError(); + error: + vshError(ctl, "%s", _("Unable to set throttle group")); + goto cleanup; + + interror: + vshError(ctl, "%s", _("Unable to parse integer parameter")); + goto cleanup; +} + + +/* + * "throttlegroupdel" command + */ +static const vshCmdInfo info_throttlegroupdel = { + .help = N_("Delete a throttling group."), + .desc = N_("Delete a throttling group."), +}; + + +static const vshCmdOptDef opts_throttlegroupdel[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "group-name", + .type = VSH_OT_STRING, + .positional = true, + .required = true, + .completer = virshDomainThrottleGroupCompleter, + .help = N_("throttle group name") + }, + VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupDel(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *group_name = NULL; + bool config = vshCommandOptBool(cmd, "config"); + bool live = vshCommandOptBool(cmd, "live"); + bool current = vshCommandOptBool(cmd, "current"); + unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; + + VSH_EXCLUSIVE_OPTIONS_VAR(current, live); + VSH_EXCLUSIVE_OPTIONS_VAR(current, config); + + if (config) + flags |= VIR_DOMAIN_AFFECT_CONFIG; + if (live) + flags |= VIR_DOMAIN_AFFECT_LIVE; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptString(ctl, cmd, "group-name", &group_name) < 0) { + return false; + } + + if (virDomainDelThrottleGroup(dom, group_name, flags) < 0) + return false; + vshPrintExtra(ctl, "%s", _("Throttle group deleted successfully\n")); + + return true; +} + + +/* + * "throttlegroupinfo" command + */ +static const vshCmdInfo info_throttlegroupinfo = { + .help = N_("Get a throttling group."), + .desc = N_("Get a throttling group."), +}; + + +static const vshCmdOptDef opts_throttlegroupinfo[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "group-name", + .type = VSH_OT_STRING, + .positional = true, + .required = true, + .completer = virshDomainThrottleGroupCompleter, + .help = N_("throttle group name") + }, + VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupInfo(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *group_name = NULL; + int nparams = 0; + virTypedParameterPtr params = NULL; + unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; + size_t i; + bool current = vshCommandOptBool(cmd, "current"); + bool config = vshCommandOptBool(cmd, "config"); + bool live = vshCommandOptBool(cmd, "live"); + bool ret = false; + + VSH_EXCLUSIVE_OPTIONS_VAR(current, live); + VSH_EXCLUSIVE_OPTIONS_VAR(current, config); + + if (config) + flags |= VIR_DOMAIN_AFFECT_CONFIG; + if (live) + flags |= VIR_DOMAIN_AFFECT_LIVE; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + goto cleanup; + + if (vshCommandOptString(ctl, cmd, "group-name", &group_name) < 0) { + goto cleanup; + } + + if (virDomainGetThrottleGroup(dom, group_name, ¶ms, &nparams, flags) != 0) {
This will most likely need to be reimplemented by querying the data from the XML as the API itself IMO doesn't make too much sense to exist as it simply queries what we've set.
+ vshError(ctl, "%s", + _("Unable to get throttle group parameters")); + goto cleanup; + } + + for (i = 0; i < nparams; i++) { + g_autofree char *str = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, "%-15s: %s\n", params[i].field, str); + } + + ret = true; + + cleanup: + virTypedParamsFree(params, nparams); + return ret; +}

On Mon, Nov 18, 2024 at 19:24:24 +0530, Harikumar R wrote: This will most likely need to be reimplemented by querying the data from the XML as the API itself IMO doesn't make too much sense to exist as it simply queries what we've set.
are you suggesting to use qemuDomainGetXMLDesc() to get the data instead of qemuDomainGetThrottleGroup->qemuMonitorGetThrottleGroup->qemuMonitorJSONGetThrottleGroup() /src/qemu/qemu_driver.c 20346 --- qemuDomainObjEnterMonitor(vm); --- rc = qemuMonitorGetThrottleGroup(qemuDomainGetMonitor(vm), groupname, reply); --- qemuDomainObjExitMonitor(vm); +++ qemuDomainGetXMLDesc

On Mon, Feb 03, 2025 at 09:29:52 -0000, Harikumar Rajkumar wrote:
On Mon, Nov 18, 2024 at 19:24:24 +0530, Harikumar R wrote: This will most likely need to be reimplemented by querying the data from the XML as the API itself IMO doesn't make too much sense to exist as it simply queries what we've set.
are you suggesting to use qemuDomainGetXMLDesc() to get the data instead of qemuDomainGetThrottleGroup->qemuMonitorGetThrottleGroup->qemuMonitorJSONGetThrottleGroup()
Yes. My argumentation is that qemuDomainGetThrottleGroup() API shouldn't really exist: - the values in QEMU should be exactly what was configured, if not it's a bug - thus the API will return exactly the same values as configured in the XML -> making the API pointless, as it returns same data as qemuDomainGetXMLDesc() As of such, no information will be lost by not having the getter API. Also as noted the implementation of the getter API was flawed. If you have reasons other than "it's done for symmetry" and "the old throttling API has a getter" please use it as justification for keeping the API but I currently don't see a reason to have a getter.

From: Chun Feng Wu <danielwuwy@163.com> Update "attach_disk" to support new option: throttle-groups to form filter chain in QEMU for specific disk Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/manpages/virsh.rst | 3 ++- tools/virsh-completer-domain.c | 27 +++++++++++++++++++++++++++ tools/virsh-completer-domain.h | 5 +++++ tools/virsh-domain.c | 25 ++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index 3b78f77384..bc99c69dfb 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -5001,7 +5001,7 @@ attach-disk [--source-protocol protocol] [--source-host-name hostname:port] [--source-host-transport transport] [--source-host-socket socket] [--serial serial] [--wwn wwn] [--rawio] [--address address] - [--multifunction] [--print-xml] + [--multifunction] [--print-xml] [--throttle-groups groups] Attach a new disk device to the domain. *source* is path for the files and devices unless *--source-protocol* @@ -5041,6 +5041,7 @@ ide:controller.bus.unit, usb:bus.port, sata:controller.bus.unit or ccw:cssid.ssid.devno. Virtio-ccw devices must have their cssid set to 0xfe. *multifunction* indicates specified pci address is a multifunction pci device address. +*throttle-groups* is comma separated list of throttle groups to be applied. There is also support for using a network disk. As specified, the user can provide a *--source-protocol* in which case the *source* parameter will diff --git a/tools/virsh-completer-domain.c b/tools/virsh-completer-domain.c index 3b0df15c13..b96180be9e 100644 --- a/tools/virsh-completer-domain.c +++ b/tools/virsh-completer-domain.c @@ -303,6 +303,33 @@ virshDomainThrottleGroupCompleter(vshControl *ctl, } +static char ** +virshDomainThrottleGroupListCompleter(vshControl *ctl, + const vshCmd *cmd, + const char *argname) +{ + const char *curval = NULL; + g_auto(GStrv) groups = virshDomainThrottleGroupCompleter(ctl, cmd, 0); + + if (vshCommandOptStringQuiet(ctl, cmd, argname, &curval) < 0) + return NULL; + + if (!groups) + return NULL; + + return virshCommaStringListComplete(curval, (const char **) groups); +} + + +char ** +virshDomainThrottleGroupsCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int completeflags G_GNUC_UNUSED) +{ + return virshDomainThrottleGroupListCompleter(ctl, cmd, "throttle-groups"); +} + + char ** virshDomainUndefineStorageDisksCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-completer-domain.h b/tools/virsh-completer-domain.h index 680b3fc018..23b432d05c 100644 --- a/tools/virsh-completer-domain.h +++ b/tools/virsh-completer-domain.h @@ -52,6 +52,11 @@ virshDomainThrottleGroupCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags); +char ** +virshDomainThrottleGroupsCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags); + char ** virshDomainInterfaceStateCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index b7004979c6..7f0cf81501 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -522,6 +522,11 @@ static const vshCmdOptDef opts_attach_disk[] = { .type = VSH_OT_STRING, .help = N_("host socket for source of disk device") }, + {.name = "throttle-groups", + .type = VSH_OT_STRING, + .completer = virshDomainThrottleGroupsCompleter, + .help = N_("comma separated list of throttle groups to be applied") + }, VIRSH_COMMON_OPT_DOMAIN_PERSISTENT, VIRSH_COMMON_OPT_DOMAIN_CONFIG, VIRSH_COMMON_OPT_DOMAIN_LIVE, @@ -611,6 +616,8 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) const char *host_name = NULL; const char *host_transport = NULL; const char *host_socket = NULL; + const char *throttle_groups_str = NULL; + g_autofree char **throttle_groups = NULL; int ret; unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; const char *stype = NULL; @@ -622,6 +629,7 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INIT_CHILD(&diskChildBuf); g_auto(virBuffer) hostAttrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) throttleChildBuf = VIR_BUFFER_INITIALIZER; g_autofree char *xml = NULL; struct stat st; bool current = vshCommandOptBool(cmd, "current"); @@ -665,9 +673,14 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) vshCommandOptString(ctl, cmd, "source-protocol", &source_protocol) < 0 || vshCommandOptString(ctl, cmd, "source-host-name", &host_name) < 0 || vshCommandOptString(ctl, cmd, "source-host-transport", &host_transport) < 0 || - vshCommandOptString(ctl, cmd, "source-host-socket", &host_socket) < 0) + vshCommandOptString(ctl, cmd, "source-host-socket", &host_socket) < 0 || + vshCommandOptString(ctl, cmd, "throttle-groups", &throttle_groups_str) < 0) return false; + if (throttle_groups_str) { + throttle_groups = g_strsplit(throttle_groups_str, ",", 0); + } + if (stype && (type = virshAttachDiskSourceTypeFromString(stype)) < 0) { vshError(ctl, _("Unknown source type: '%1$s'"), stype); @@ -714,6 +727,16 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) virXMLFormatElement(&diskChildBuf, "driver", &driverAttrBuf, NULL); + if (throttle_groups) { + char **iter; + for (iter = throttle_groups; *iter != NULL; iter++) { + g_auto(virBuffer) throttleAttrBuf = VIR_BUFFER_INITIALIZER; + virBufferAsprintf(&throttleAttrBuf, " group='%s'", *iter); + virXMLFormatElement(&throttleChildBuf, "throttlefilter", &throttleAttrBuf, NULL); + } + virXMLFormatElement(&diskChildBuf, "throttlefilters", NULL, &throttleChildBuf); + } + switch ((enum virshAttachDiskSourceType) type) { case VIRSH_ATTACH_DISK_SOURCE_TYPE_FILE: virBufferEscapeString(&sourceAttrBuf, " file='%s'", source); -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:25 +0530, Harikumar R wrote:
From: Chun Feng Wu <danielwuwy@163.com>
Update "attach_disk" to support new option: throttle-groups to form filter chain in QEMU for specific disk
Signed-off-by: Chun Feng Wu <danielwuwy@163.com> --- docs/manpages/virsh.rst | 3 ++- tools/virsh-completer-domain.c | 27 +++++++++++++++++++++++++++ tools/virsh-completer-domain.h | 5 +++++ tools/virsh-domain.c | 25 ++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 2 deletions(-)
[...]
@@ -611,6 +616,8 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) const char *host_name = NULL; const char *host_transport = NULL; const char *host_socket = NULL; + const char *throttle_groups_str = NULL; + g_autofree char **throttle_groups = NULL;
This is filled by g_strsplit, so this would leak the individual strings. You need to use g_auto(GStrv) to declare this.
int ret; unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; const char *stype = NULL; @@ -622,6 +629,7 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INIT_CHILD(&diskChildBuf); g_auto(virBuffer) hostAttrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) throttleChildBuf = VIR_BUFFER_INITIALIZER;
Declare this inside the block using it.
g_autofree char *xml = NULL; struct stat st; bool current = vshCommandOptBool(cmd, "current"); @@ -665,9 +673,14 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) vshCommandOptString(ctl, cmd, "source-protocol", &source_protocol) < 0 || vshCommandOptString(ctl, cmd, "source-host-name", &host_name) < 0 || vshCommandOptString(ctl, cmd, "source-host-transport", &host_transport) < 0 || - vshCommandOptString(ctl, cmd, "source-host-socket", &host_socket) < 0) + vshCommandOptString(ctl, cmd, "source-host-socket", &host_socket) < 0 || + vshCommandOptString(ctl, cmd, "throttle-groups", &throttle_groups_str) < 0) return false;
+ if (throttle_groups_str) { + throttle_groups = g_strsplit(throttle_groups_str, ",", 0); + } + if (stype && (type = virshAttachDiskSourceTypeFromString(stype)) < 0) { vshError(ctl, _("Unknown source type: '%1$s'"), stype); @@ -714,6 +727,16 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd)
virXMLFormatElement(&diskChildBuf, "driver", &driverAttrBuf, NULL);
+ if (throttle_groups) { + char **iter; + for (iter = throttle_groups; *iter != NULL; iter++) { + g_auto(virBuffer) throttleAttrBuf = VIR_BUFFER_INITIALIZER; + virBufferAsprintf(&throttleAttrBuf, " group='%s'", *iter); + virXMLFormatElement(&throttleChildBuf, "throttlefilter", &throttleAttrBuf, NULL); + } + virXMLFormatElement(&diskChildBuf, "throttlefilters", NULL, &throttleChildBuf); + } +
With the bug fixed: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Harikumar Rajkumar <harikumar.rajkumar@ibm.com> Signed-off-by: Harikumar R <harirajkumar230@gmail.com> --- tests/qemustatusxml2xmldata/throttlefilter-out.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/qemustatusxml2xmldata/throttlefilter-out.xml b/tests/qemustatusxml2xmldata/throttlefilter-out.xml index 2217ad929d..8751a42cce 100644 --- a/tests/qemustatusxml2xmldata/throttlefilter-out.xml +++ b/tests/qemustatusxml2xmldata/throttlefilter-out.xml @@ -231,6 +231,7 @@ </devices> <libDir path='/var/lib/libvirt/qemu/domain-4-copy'/> <channelTargetDir path='/var/lib/libvirt/qemu/channel/target/domain-4-copy'/> + <memoryBackingDir path='/var/lib/libvirt/qemu/ram/4-copy'/> <chardevStdioLogd/> <allowReboot value='yes'/> <nodename index='0'/> -- 2.39.5 (Apple Git-154)

On Mon, Nov 18, 2024 at 19:24:26 +0530, Harikumar R wrote:
From: Harikumar Rajkumar <harikumar.rajkumar@ibm.com>
Signed-off-by: Harikumar R <harirajkumar230@gmail.com> --- tests/qemustatusxml2xmldata/throttlefilter-out.xml | 1 + 1 file changed, 1 insertion(+)
diff --git a/tests/qemustatusxml2xmldata/throttlefilter-out.xml b/tests/qemustatusxml2xmldata/throttlefilter-out.xml index 2217ad929d..8751a42cce 100644 --- a/tests/qemustatusxml2xmldata/throttlefilter-out.xml +++ b/tests/qemustatusxml2xmldata/throttlefilter-out.xml @@ -231,6 +231,7 @@ </devices> <libDir path='/var/lib/libvirt/qemu/domain-4-copy'/> <channelTargetDir path='/var/lib/libvirt/qemu/channel/target/domain-4-copy'/> + <memoryBackingDir path='/var/lib/libvirt/qemu/ram/4-copy'/> <chardevStdioLogd/> <allowReboot value='yes'/> <nodename index='0'/>
Ah so this explains the failure in 13/18. As noted in our guidelines tests must pass after every commit, so this kind of fix up later on is not the correct fix. I'll start a proper review of these patches once I close up few other things, so please be patient. This feature is rather complex and thus potentially involves maintenance burden on the maintainers so I'll need to make sure it's as minimal as possible.

Hi Peter Krempa, any ETA to complete this patch request?

On Mon, Jan 06, 2025 at 10:45:16 -0000, harirajkumar230@gmail.com wrote:
Hi Peter Krempa, any ETA to complete this patch request?
I'm sorry. As I've noted multiple times due to personal reasons my work capacity is limited. I can't give you an ETA. As you seem to be only keen to get this feature merged and don't seem to get involved more widely into the project in other ways besides adding this feature. I'm thus expecting that the maintenance of this feature will be inherited by the maintainers of the project (-> e.g. me in this case). Thus please understand, that this is non-trivial amount of code, the feature itself is not likely going to be widely used and the maintenance will most likely fall on my shoulders in the long term. Thus it's not exactly high on my priority list. I'm sorry but that is the reality of things.

Hi Peter Krempa, Could you please prioritize reviews for remaining commits so we can create new patch request with all fix. We have a requirement which depends on this change and we are currently blocked.

On Wed, Jan 22, 2025 at 07:37:22 -0000, Harikumar Rajkumar wrote:
Hi Peter Krempa, Could you please prioritize reviews for remaining commits so we can create new patch request with all fix. We have a requirement which depends on this change and we are currently blocked.
Please refer to my previous reply: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7BLZ...

On Mon, Nov 18, 2024 at 19:24:08 +0530, Harikumar R wrote:
*** BLURB HERE ***
Chun Feng Wu (17): schema: Add new domain elements to support multiple throttle groups schema: Add new domain elements to support multiple throttle filters config: Introduce ThrottleGroup and corresponding XML parsing config: Introduce ThrottleFilter and corresponding XML parsing qemu: monitor: Add support for ThrottleGroup operations tests: Test qemuMonitorJSONGetThrottleGroup and qemuMonitorJSONUpdateThrottleGroup remote: New APIs for ThrottleGroup lifecycle management qemu: Refactor qemuDomainSetBlockIoTune to extract common methods qemu: Implement qemu driver for throttle API qemu: helper: throttle filter nodename and preparation processing qemu: block: Support block disk along with throttle filters config: validate: Verify iotune, throttle group and filter qemuxmlconftest: Add 'throttlefilter' tests test_driver: Test throttle group lifecycle APIs virsh: Refactor iotune options for re-use virsh: Add support for throttle group operations virsh: Add option "throttle-groups" to "attach_disk"
Harikumar Rajkumar (1): tests: Test qemuxmlactivetestThrottleGroup
The review is done now. There are few bits. Please note that I didn't point out *all* places needing a version number update, so make sure to check everything explicitly. Please use 11.1.0 for next posting. One important bit is that IMO the getter API makes IMO no sense to be added as the data can simply be queried from the XML. Having a special API is a bit overkill. Few other things I've pointed out: - some coding style issues - some pointless comments

On Mon, Nov 18, 2024 at 19:24:08 +0530, Harikumar R wrote:
The review is done now. There are few bits.
Please note that I didn't point out *all* places needing a version number update, so make sure to check everything explicitly. Please use 11.1.0 for next posting.
One important bit is that IMO the getter API makes IMO no sense to be added as the data can simply be queried from the XML. Having a special API is a bit overkill.
Few other things I've pointed out: - some coding style issues - some pointless comments
I have addressed the feedbacks and submitted new patch version 7. https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/TV3HJ...
participants (4)
-
Harikumar R
-
Harikumar Rajkumar
-
harirajkumar230@gmail.com
-
Peter Krempa