[PATCH RFC v3 00/16] Support throttle block filters

From: Chun Feng Wu <wucf@linux.ibm.com> Hi, I am thinking to leverage "throttle block filter" in QEMU to support more flexible I/O limits(e.g. tiered I/O groups), one sample provided by QEMU doc is: https://github.com/qemu/qemu/blob/master/docs/throttle.txt "For example, let's say that we have three different drives and we want to set I/O limits for each one of them and an additional set of limits for the combined I/O of all three drives." The implementation idea is to - Define throttle groups(limit) in domain - Define throttle filter to reference throttle group within disk - Within domain disk, throttle filters references multiple throttle groups to form filter chain to apply multiple limits in QEMU like above sample - Add new virsh cmds for throttle group management: throttlegroupset Add or update a throttling group. throttlegroupdel Delete a throttling group. throttlegroupinfo Get a throttling group. throttlegrouplist list all domain throttlegroups - Update "attach-disk" to add one more option "--throttle-groups" to apply throttle filters e.g. "virsh attach-disk $VM_ID ${DISK_PATH}/vm1_disk_2.qcow2 vdd --driver qemu --subdriver qcow2 --targetbus virtio --throttle-groups limit2,limit012" - I chose above semantics as I felt they're appropriate, if there are better ones please kindly suggest. Note, this implementation requires flag "QEMU_CAPS_OBJECT_JSON". From QMP perspective, the sample flow works this way: - Throttle group creation: virsh qemu-monitor-command 1 '{"execute":"object-add", "arguments":{"qom-type":"throttle-group","id":"limit0","limits":{"iops-total":200,"iops-read":0,"iops-total-max":200,"iops-total-max-length":1}}}' virsh qemu-monitor-command 1 '{"execute":"object-add", "arguments":{"qom-type":"throttle-group","id":"limit1","limits":{"iops-total":250,"iops-read":0,"iops-total-max":250,"iops-total-max-length":1}}}' virsh qemu-monitor-command 1 '{"execute":"object-add", "arguments":{"qom-type":"throttle-group","id":"limit2","limits":{"iops-total":300,"iops-read":0,"iops-total-max":300,"iops-total-max-length":1}}}' virsh qemu-monitor-command 1 '{"execute":"object-add", "arguments":{"qom-type":"throttle-group","id":"limit012","limits":{"iops-total":400,"iops-read":0,"iops-total-max":400,"iops-total-max-length":1}}}' - Chain up filters during attaching disk to apply two filters(limit0 and limit012): virsh qemu-monitor-command 1 '{"execute":"blockdev-add", "arguments": {"driver":"file","filename":"/virt/disks/vm1_disk_1.qcow2","node-name":"test-3-storage","auto-read-only":true,"discard":"unmap"}}' virsh qemu-monitor-command 1 '{"execute":"blockdev-add", "arguments":{"node-name":"test-4-format","read-only":false,"driver":"qcow2","file":"test-3-storage","backing":null}}' virsh qemu-monitor-command 1 '{"execute":"blockdev-add", "arguments":{"driver":"throttle","node-name":"libvirt-5-filter","throttle-group": "limit0","file":"test-4-format"}}' virsh qemu-monitor-command 1 '{"execute":"blockdev-add", "arguments": {"driver":"throttle","node-name":"libvirt-6-filter","throttle-group":"limit012","file":"libvirt-5-filter"}}' virsh qemu-monitor-command 1 '{"execute": "device_add", "arguments": {"driver":"virtio-blk-pci","scsi":false,"bus":"pci.0","addr":"0x5","drive":"libvirt-6-filter","id":"virtio-disk1"}}' This patchset includes: - Throttle group XML schema definition in patch 1 - Throttle filter XML schema definition in patch 2 - Throttle group struct definition, parsing and formating in patch 3 - Throttle filter struct definition, parsing and formating in patch 4 - New QMP processing to update and get throttle group in patch 5&6 - New API definition and implementation in patch 7 - QEMU driver implementation in patch 8 - Hotplug processing for throttle filters in patch 9 - Extract common iotune validation in patch 10 - qemuProcessLaunch flow implemenation for throttle group in patch 11 - qemuProcessLaunch flow implemenation for throttle filter in patch 12 - Domain XML test for processing throttle groups and filters in patch 13 - Test new implemented driver in patch 14 - New virsh cmd implementation for group in patch 15 - Update Virsh cmd "attach_disk" to include throttle filters in patch 16 v3 changes: - re-org commits by splitting changes containing throttle group and filters - update commits msgs - move schema commits to be the first ones - refactor "diskIoTune" to extract common schema "iotune" - add new tests for throttle groups and filters in qemuxmlconftest - check flag "QEMU_CAPS_OBJECT_JSON" when preparing "-object"(qemu: command: Support throttle groups during qemuProcessLaunch ) or creating throttle group (qemu: Implement qemu driver for throttle API) - when creating throttle group through "object-add" (qemu: Implement qemu driver for throttle API), reuse "qemuMonitorAddObject" to check if "objectAddNoWrap"("props") is requried - remove "virObject parent;" in "_virDomainThrottleFilterDef" in domain_conf.h - remove "virDomainThrottleGroupIndexByName" in both domain_conf.h and domain_conf.c - remove "virDomainThrottleFilterDefNew" in domain_conf.c - update "virDomainThrottleFilterDefFree" to use "g_free" rather than "VIR_FREE" in domain_conf.c - update "virDomainDiskThrottleFilterDefParse" to remove "xmlXPathContextPtr ctx" parameter and check NULL against "filter->group_name" in domain_conf.c - use "virBufferEscapeString" instead of "virBufferAsprintf" in "virDomainDiskDefFormatThrottleFilterChain" - use "group->val > 0" instead of "if (group->val)" in FORMAT_THROTTLE_GROUP - remove NULL check for "group->group_name" since virBufferEscapeString checked NULL already in "virDomainThrottleGroupFormat" - I haven't added new conf module (src/conf/virdomainthrottle.c/h) because "virDomainThrottleGroupDef" is alias of "_virDomainBlockIoTuneInfo", try to avoid circular dependency - remove "NULLSTR" in qemuMonitorUpdateThrottleGroup and qemuMonitorGetThrottleGroup in qemu_monitor.c - refactor "qemuMonitorMakeThrottleGroupLimits" to use virJSONValueObjectAdd in qemu_monitor_json.c - use "g_strdup_printf" to avoid static buffers in "qemuMonitorJSONGetThrottleGroup" - remove virReportError after qemuMonitorJSONGetReply in "qemuMonitorJSONGetThrottleGroup" to avoid overriding error - remove "VIR_DOMAIN_THROTTLE_GROUP" in libvirt/include/libvirt/libvirt-domain.h - update "virDomainGetThrottleGroup" to not first query the number of parameters, - update "remote_domain_get_throttle_group_args" and "remote_domain_get_throttle_group_ret" to remove "nparams" in src/remote_protocol-structs, also updated "remote_domain_get_throttle_group_args" and "remote_domain_get_throttle_group_ret" in src/remote/remote_protocol.x - update parameter "virTypedParameterPtr params" to be "virTypedParameterPtr *params" in "virDrvDomainGetThrottleGroup" in driver-hypervisor.h - update "qemuDomainSetThrottleGroup" to not query number of parameters first - remove wrapper "qemuDomainThrottleGroupByName" and "qemuDomainSetThrottleGroupDefaults" - refactor "qemuDomainSetThrottleGroup" and "qemuDomainSetBlockIoTune" to use common logic - update "qemuDomainDelThrottleGroup" to use VIR_JOB_MODIFY by referencing "qemuDomainHotplugDelIOThread" - check if group is still being used by some filter(qemuDomainCheckThrottleGroupRef) during deletion - replace "ThrottleFilterChain" with "ThrottleFilters" - update "qemuDomainDiskGetTopNodename" to take top throttle node name if disk has throttles, and reuse "qemuDomainDiskGetBackendAlias" in "qemuBuildDiskDeviceProps" to get top node name as "drive" - after enabling throttlerfilter and if disk has throttlefilters, during blockcommit, the top node name is not "libvirt-x-format" anymore, instead, top node name referencies top filter like "libvirt-x-filter" - add check "cdrom device with throttle filters isn't supported" - delete "filternodenameindex" and reuse "nodenameindex" to generate index for throttle nodes - refactor detaching filters by adding "qemuBuildThrottleFiltersDetachPrepareBlockdev" to just build parameters for "blockdev-del" - refactor "testDomainSetBlockIoTune" and "testDomainSetThrottleGroup" to use common logic Any comments/suggestions will be appriciated! Chun Feng Wu (16): 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: Implement qemu driver for throttle API qemu: hotplug: Support hot attach and detach block disk along with throttle filters config: validate: Refactor disk iotune validation for reuse qemu: command: Support throttle groups during qemuProcessLaunch qemu: command: Support throttle filters during qemuProcessLaunch qemuxmlconftest: Add 'throttlefilter' tests test_driver: Test throttle group lifecycle APIs virsh: Add support for throttle group operations virsh: Add option "throttle-groups" to "attach_disk" docs/formatdomain.rst | 48 ++ include/libvirt/libvirt-domain.h | 21 + src/conf/domain_conf.c | 376 +++++++++++ src/conf/domain_conf.h | 51 ++ src/conf/domain_validate.c | 118 +++- src/conf/schemas/domaincommon.rng | 293 +++++---- src/conf/virconftypes.h | 4 + src/driver-hypervisor.h | 22 + src/libvirt-domain.c | 196 ++++++ src/libvirt_private.syms | 9 + src/libvirt_public.syms | 7 + src/qemu/qemu_block.c | 131 ++++ src/qemu/qemu_block.h | 53 ++ src/qemu/qemu_command.c | 182 ++++++ src/qemu/qemu_command.h | 12 + src/qemu/qemu_domain.c | 39 +- src/qemu/qemu_domain.h | 8 + src/qemu/qemu_driver.c | 617 +++++++++++++++--- src/qemu/qemu_hotplug.c | 33 + src/qemu/qemu_monitor.c | 34 + src/qemu/qemu_monitor.h | 14 + src/qemu/qemu_monitor_json.c | 150 +++++ 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.x86_64-latest.args | 43 ++ .../throttlefilter.x86_64-latest.xml | 65 ++ tests/qemuxmlconfdata/throttlefilter.xml | 55 ++ tests/qemuxmlconftest.c | 1 + tools/virsh-completer-domain.c | 64 ++ tools/virsh-completer-domain.h | 5 + tools/virsh-domain.c | 453 ++++++++++++- 36 files changed, 3447 insertions(+), 369 deletions(-) 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.34.1

From: Chun Feng Wu <wucf@linux.ibm.com> * Refactor "diskIoTune" to extract common schema "iotune" * Add new elements '<throttlegroups>' * <ThrottleGroups> contains <ThrottleGroup> defintion, which references "iotune" Signed-off-by: Chun Feng Wu <wucf@linux.ibm.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 00f861e385..b7e1f9cc83 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -1957,6 +1957,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.5.0` it is possible to create multiple named throttle groups +and then reference them within ``throttlefilters`` 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 a46a824f88..08c520e222 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"/> @@ -6653,6 +6654,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 @@ -7615,134 +7633,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.34.1

On Wed, Jun 12, 2024 at 03:02:09 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
We tend to add also a justification/description of the change rather than just mechanically outline what you did: Introduce schema for defining '<throttlegroups>' element which configures throttling groups which can be configured for multiple disks. Or something similar.
* Refactor "diskIoTune" to extract common schema "iotune" * Add new elements '<throttlegroups>' * <ThrottleGroups> contains <ThrottleGroup> defintion, which references "iotune"
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.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 00f861e385..b7e1f9cc83 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -1957,6 +1957,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.5.0` it is possible to create multiple named throttle groups
Don't forget to update to '10.7.0'
+and then reference them within ``throttlefilters`` to form filter chain in QEMU for
... within ``throttlefilters`` sub-element of ``disk`` element ...
+specific disk. The limits(throttlegroups) are shared within domain, hence the same group +can be referenced by different filters.
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <wucf@linux.ibm.com> * 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 <wucf@linux.ibm.com> --- docs/formatdomain.rst | 22 ++++++++++++++++++++++ src/conf/schemas/domaincommon.rng | 19 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index b7e1f9cc83..0fa8f1267c 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -2736,6 +2736,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> ... @@ -3217,6 +3226,19 @@ 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.5.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 nodes in qemu shape a chain like libvirt-4-filter(node name of "limit012") -> + libvirt-3-filter(node name of "limit2") -> libvirt-2-format -> libvirt-1-storage. + ``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 08c520e222..7ceb8c0be2 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -1578,7 +1578,10 @@ <ref name="encryption"/> </optional> <optional> - <ref name="diskIoTune"/> + <choice> + <ref name="throttlefilters"/> + <ref name="diskIoTune"/> + </choice> </optional> <optional> <ref name="alias"/> @@ -6671,6 +6674,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.34.1

On Wed, Jun 12, 2024 at 03:02:10 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* 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 <wucf@linux.ibm.com> --- docs/formatdomain.rst | 22 ++++++++++++++++++++++ src/conf/schemas/domaincommon.rng | 19 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index b7e1f9cc83..0fa8f1267c 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -2736,6 +2736,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> ...
@@ -3217,6 +3226,19 @@ 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.5.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 nodes in qemu shape a chain like libvirt-4-filter(node name of "limit012") -> + libvirt-3-filter(node name of "limit2") -> libvirt-2-format -> libvirt-1-storage. + ``throttlefilters`` and ``iotune`` should be used exclusively.
Node names are a qemu driver internal implementation detail and thus must not be noted in documentation.
+ + ``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 08c520e222..7ceb8c0be2 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -1578,7 +1578,10 @@ <ref name="encryption"/> </optional> <optional> - <ref name="diskIoTune"/> + <choice> + <ref name="throttlefilters"/> + <ref name="diskIoTune"/> + </choice> </optional> <optional> <ref name="alias"/> @@ -6671,6 +6674,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.34.1

On Tue, Jul 02, 2024 at 16:11:03 +0200, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:10 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* 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 <wucf@linux.ibm.com> --- docs/formatdomain.rst | 22 ++++++++++++++++++++++ src/conf/schemas/domaincommon.rng | 19 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index b7e1f9cc83..0fa8f1267c 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -2736,6 +2736,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> ...
@@ -3217,6 +3226,19 @@ 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.5.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 nodes in qemu shape a chain like libvirt-4-filter(node name of "limit012") -> + libvirt-3-filter(node name of "limit2") -> libvirt-2-format -> libvirt-1-storage. + ``throttlefilters`` and ``iotune`` should be used exclusively.
Node names are a qemu driver internal implementation detail and thus must not be noted in documentation.
I'm not exactly sure how the internals in qemu work here, but you also might want to document how the order of the filters impacts things (or that it does not impact things).

The order of such ``throttlefilter`` doesn't matter within ``throttlefilters``. I will put above statement into doc

On Tue, Aug 06, 2024 at 00:27:58 -0000, Chun Feng Wu wrote: Please keep the context in the reply. I had to check back what I've asked.
The order of such ``throttlefilter`` doesn't matter within ``throttlefilters``.
So IIUC, re-ordering of the filters doesn't have any guest-OS visible impact? I'm trying to understand whether one disk can exhaust one layer while be blocked on the next, in which case a different disk which has only one layer (equivalent to the first disk's first layer) would be starved, but if the filters were ordered the other way around at the first disk it would not. If the above can happen you'll need to document how it's supposed to behave.

my original conclusion is based on the following test xml: <domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> ... <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> ... <devices> <!-- Disk for the operating system --> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/images/jammy-server-cloudimg-amd64.img'/> <target dev='vda' bus='virtio'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> </disk> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_1.qcow2'/> <target dev='vdb' bus='virtio'/> <throttlefilters> <throttlefilter group='limit0'/> <throttlefilter group='limit012'/> </throttlefilters> <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> </disk> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2'/> <target dev='vdc' bus='virtio'/> <throttlefilters> <throttlefilter group='limit1'/> <throttlefilter group='limit012'/> </throttlefilters> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_3.qcow2'/> <target dev='vdd' bus='virtio'/> <throttlefilters> <throttlefilter group='limit2'/> <throttlefilter group='limit012'/> </throttlefilters> <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> </disk> ... </devices> </domain> if I re-order filters in vdc as below, fio tests(randwrite) show the same result for both concurrent(400 iops in total, around 133(400/3) for each disk) and individual disk test(200 for vdb, 250 for vdc, 300 for vdd). <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2'/> <target dev='vdc' bus='virtio'/> <throttlefilters> <throttlefilter group='limit012'/> <throttlefilter group='limit1'/> </throttlefilters> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk> and back to your case(vdb, vdc in the following xml): <domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> ... <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> ... <devices> <!-- Disk for the operating system --> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/images/jammy-server-cloudimg-amd64.img'/> <target dev='vda' bus='virtio'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> </disk> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_1.qcow2'/> <target dev='vdb' bus='virtio'/> <throttlefilters> <throttlefilter group='limit012'/> <throttlefilter group='limit0'/> </throttlefilters> <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> </disk> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2'/> <target dev='vdc' bus='virtio'/> <throttlefilters> <throttlefilter group='limit012'/> </throttlefilters> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk> ... </devices> </domain> with above xml, fio tests(randwrite) show: - concurrent: 400 iops in total, around 200(400/2) for each disk - individual disk test: 200 for vdb, 400 for vdc after I re-order vdb disk as below, tests have the same result: - concurrent: 400 iops in total, around 200(400/2) for each disk - individual disk test: 200 for vdb, 400 for vdc <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_1.qcow2'/> <target dev='vdb' bus='virtio'/> <throttlefilters> <throttlefilter group='limit0'/> <throttlefilter group='limit012'/> </throttlefilters> <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> </disk> let me know if I understand your case correctly, thanks! On 2024/8/6 15:36, Peter Krempa wrote:
On Tue, Aug 06, 2024 at 00:27:58 -0000, Chun Feng Wu wrote:
Please keep the context in the reply. I had to check back what I've asked.
The order of such ``throttlefilter`` doesn't matter within ``throttlefilters``. So IIUC, re-ordering of the filters doesn't have any guest-OS visible impact? I'm trying to understand whether one disk can exhaust one layer while be blocked on the next, in which case a different disk which has only one layer (equivalent to the first disk's first layer) would be starved, but if the filters were ordered the other way around at the first disk it would not.
If the above can happen you'll need to document how it's supposed to behave.
-- Thanks and Regards, Wu

From: Chun Feng Wu <wucf@linux.ibm.com> * Define new struct 'virDomainThrottleGroupDef' and corresponding destructor * Add operations(Add, Update, Del, Find, 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 <wucf@linux.ibm.com> --- src/conf/domain_conf.c | 281 ++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 31 +++++ src/conf/virconftypes.h | 2 + 3 files changed, 314 insertions(+) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index fde594f811..05d6f7ad3a 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3750,6 +3750,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) { @@ -4029,6 +4055,8 @@ void virDomainDefFree(virDomainDef *def) virDomainIOThreadIDDefArrayFree(def->iothreadids, def->niothreadids); + virDomainThrottleGroupDefArrayFree(def->throttlegroups, def->nthrottlegroups); + g_free(def->defaultIOThread); virBitmapFree(def->cputune.emulatorpin); @@ -7697,6 +7725,113 @@ 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 + + +virDomainThrottleGroupDef * +virDomainThrottleGroupByName(virDomainDef *def, + const char *name) +{ + virDomainThrottleGroupDef *tgroup; + size_t i; + int idx = -1; + + for (i = 0; i < def->nthrottlegroups; i++) { + tgroup = def->throttlegroups[i]; + if (STREQ(tgroup->group_name, name)) + idx = i; + } + + if (idx < 0) + return NULL; + + return def->throttlegroups[idx]; +} + + +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 (virDomainThrottleGroupFind(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, @@ -18880,6 +19015,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; @@ -22090,6 +22228,88 @@ virDomainIOThreadIDDel(virDomainDef *def, } +virDomainThrottleGroupDef * +virDomainThrottleGroupFind(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(name, def->throttlegroups[i]->group_name)) + return def->throttlegroups[i]; + } + + return NULL; +} + + +void +virDomainThrottleGroupDefCopy(const virDomainThrottleGroupDef *src, + virDomainThrottleGroupDef *dst) +{ + *dst = *src; + dst->group_name = g_strdup(src->group_name); +} + + +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); + } + } +} + + +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, @@ -27174,6 +27394,65 @@ 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(size_iops_sec); + + virBufferEscapeString(&childBuf, "<group_name>%s</group_name>\n", + group->group_name); + + 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); + + 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) @@ -27839,6 +28118,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 a06f015444..c9e3fcd924 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3000,6 +3000,9 @@ struct _virDomainDef { virDomainDefaultIOThreadDef *defaultIOThread; + size_t nthrottlegroups; + virDomainThrottleGroupDef **throttlegroups; + virDomainCputune cputune; virDomainResctrlDef **resctrls; @@ -4512,3 +4515,31 @@ 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); + +virDomainThrottleGroupDef * +virDomainThrottleGroupFind(const virDomainDef *def, + const char *name); + +void +virDomainThrottleGroupDel(virDomainDef *def, + const char *name); + +virDomainThrottleGroupDef * +virDomainThrottleGroupByName(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 0779bc224b..d51e8f5f40 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.34.1

On Wed, Jun 12, 2024 at 03:02:11 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* Define new struct 'virDomainThrottleGroupDef' and corresponding destructor * Add operations(Add, Update, Del, Find, 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 <wucf@linux.ibm.com> --- src/conf/domain_conf.c | 281 ++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 31 +++++ src/conf/virconftypes.h | 2 + 3 files changed, 314 insertions(+)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index fde594f811..05d6f7ad3a 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3750,6 +3750,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) { @@ -4029,6 +4055,8 @@ void virDomainDefFree(virDomainDef *def)
virDomainIOThreadIDDefArrayFree(def->iothreadids, def->niothreadids);
+ virDomainThrottleGroupDefArrayFree(def->throttlegroups, def->nthrottlegroups); + g_free(def->defaultIOThread);
virBitmapFree(def->cputune.emulatorpin); @@ -7697,6 +7725,113 @@ virDomainDiskDefIotuneParse(virDomainDiskDef *def, #undef PARSE_IOTUNE
+#define PARSE_THROTTLEGROUP(val) \ + if (virXPathULongLong("string(./" #val ")", \ + ctxt, &group->val) == -2) { \
This can also return -1, in which case you also want to at least return NULL, but for simplicity reporting the error as below is okay.
+ 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);
I'd prefer if we had only one copy of the code parsing this and the equivalent disk throttling data so that the code doesn't need to be duplicated. The cleanup can be done as a followup though.
+ + /* 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 + + +virDomainThrottleGroupDef * +virDomainThrottleGroupByName(virDomainDef *def, + const char *name) +{ + virDomainThrottleGroupDef *tgroup; + size_t i; + int idx = -1; + + for (i = 0; i < def->nthrottlegroups; i++) { + tgroup = def->throttlegroups[i]; + if (STREQ(tgroup->group_name, name)) + idx = i; + } + + if (idx < 0) + return NULL; + + return def->throttlegroups[idx]; +} + + +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 (virDomainThrottleGroupFind(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, @@ -18880,6 +19015,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; @@ -22090,6 +22228,88 @@ virDomainIOThreadIDDel(virDomainDef *def, }
+virDomainThrottleGroupDef * +virDomainThrottleGroupFind(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(name, def->throttlegroups[i]->group_name)) + return def->throttlegroups[i]; + } + + return NULL; +} + + +void +virDomainThrottleGroupDefCopy(const virDomainThrottleGroupDef *src, + virDomainThrottleGroupDef *dst) +{ + *dst = *src; + dst->group_name = g_strdup(src->group_name); +} + + +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); + } + } +} + +
Please add docs for any function that is being exported.
+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, @@ -27174,6 +27394,65 @@ 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(size_iops_sec); + + virBufferEscapeString(&childBuf, "<group_name>%s</group_name>\n", + group->group_name); + + 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); + + 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) @@ -27839,6 +28118,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 a06f015444..c9e3fcd924 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3000,6 +3000,9 @@ struct _virDomainDef {
virDomainDefaultIOThreadDef *defaultIOThread;
+ size_t nthrottlegroups; + virDomainThrottleGroupDef **throttlegroups; + virDomainCputune cputune;
virDomainResctrlDef **resctrls; @@ -4512,3 +4515,31 @@ 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); + +virDomainThrottleGroupDef * +virDomainThrottleGroupFind(const virDomainDef *def, + const char *name); + +void +virDomainThrottleGroupDel(virDomainDef *def, + const char *name); + +virDomainThrottleGroupDef * +virDomainThrottleGroupByName(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 0779bc224b..d51e8f5f40 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.34.1

Thanks Peter for your review! I am on vacation, and will start to address comments Aug. 5

On 2024/7/26 21:03, Peter Krempa wrote:
+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); I'd prefer if we had only one copy of the code parsing this and the equivalent disk throttling data so that the code doesn't need to be duplicated. The cleanup can be done as a followup though.
in v4 drafting, I am defining the following common macro to avoid duplication of iterating all options in all parsing and formatting codes for both iotune and throttlegroup: #define FOR_EACH_IOTUNE_ULL_OPTION(process) \ process(total_bytes_sec) \ process(read_bytes_sec) \ process(write_bytes_sec) \ process(total_iops_sec) \ process(read_iops_sec) \ process(write_iops_sec) \ process(total_bytes_sec_max) \ process(read_bytes_sec_max) \ process(write_bytes_sec_max) \ process(total_iops_sec_max) \ process(read_iops_sec_max) \ process(write_iops_sec_max) \ process(size_iops_sec) \ process(total_bytes_sec_max_length) \ process(read_bytes_sec_max_length) \ process(write_bytes_sec_max_length) \ process(total_iops_sec_max_length) \ process(read_iops_sec_max_length) \ process(write_iops_sec_max_length) and define different "process" macro for parse and format, and this can be reused by iotune and format by just calling e.g. FOR_EACH_IOTUNE_ULL_OPTION(PARSE_IOTUNE); FOR_EACH_IOTUNE_ULL_OPTION(FORMAT_IOTUNE); -- Thanks and Regards, Wu

On Fri, Aug 23, 2024 at 14:00:25 +0800, Chun Feng Wu wrote:
On 2024/7/26 21:03, Peter Krempa wrote:
+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); I'd prefer if we had only one copy of the code parsing this and the equivalent disk throttling data so that the code doesn't need to be duplicated. The cleanup can be done as a followup though.
in v4 drafting, I am defining the following common macro to avoid duplication of iterating all options in all parsing and formatting codes for both iotune and throttlegroup:
I was referring to the fact that the code that parses the old throttling XML is not refactored to use this new helper, thus keeping duplications.
#define FOR_EACH_IOTUNE_ULL_OPTION(process) \ process(total_bytes_sec) \ process(read_bytes_sec) \ process(write_bytes_sec) \ process(total_iops_sec) \ process(read_iops_sec) \ process(write_iops_sec) \ process(total_bytes_sec_max) \ process(read_bytes_sec_max) \ process(write_bytes_sec_max) \ process(total_iops_sec_max) \ process(read_iops_sec_max) \ process(write_iops_sec_max) \ process(size_iops_sec) \ process(total_bytes_sec_max_length) \ process(read_bytes_sec_max_length) \ process(write_bytes_sec_max_length) \ process(total_iops_sec_max_length) \ process(read_iops_sec_max_length) \ process(write_iops_sec_max_length)
and define different "process" macro for parse and format, and this can be reused by iotune and format by just calling e.g.
FOR_EACH_IOTUNE_ULL_OPTION(PARSE_IOTUNE);
FOR_EACH_IOTUNE_ULL_OPTION(FORMAT_IOTUNE);
Please do not do this. Do not hide this any deeper into macros. It's hard to understand and harder to debug.

On 2024/8/23 15:10, Peter Krempa wrote:
On Fri, Aug 23, 2024 at 14:00:25 +0800, Chun Feng Wu wrote:
On 2024/7/26 21:03, Peter Krempa wrote:
+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); I'd prefer if we had only one copy of the code parsing this and the equivalent disk throttling data so that the code doesn't need to be duplicated. The cleanup can be done as a followup though. in v4 drafting, I am defining the following common macro to avoid duplication of iterating all options in all parsing and formatting codes for both iotune and throttlegroup: I was referring to the fact that the code that parses the old throttling XML is not refactored to use this new helper, thus keeping duplications.
#define FOR_EACH_IOTUNE_ULL_OPTION(process) \ process(total_bytes_sec) \ process(read_bytes_sec) \ process(write_bytes_sec) \ process(total_iops_sec) \ process(read_iops_sec) \ process(write_iops_sec) \ process(total_bytes_sec_max) \ process(read_bytes_sec_max) \ process(write_bytes_sec_max) \ process(total_iops_sec_max) \ process(read_iops_sec_max) \ process(write_iops_sec_max) \ process(size_iops_sec) \ process(total_bytes_sec_max_length) \ process(read_bytes_sec_max_length) \ process(write_bytes_sec_max_length) \ process(total_iops_sec_max_length) \ process(read_iops_sec_max_length) \ process(write_iops_sec_max_length)
and define different "process" macro for parse and format, and this can be reused by iotune and format by just calling e.g.
FOR_EACH_IOTUNE_ULL_OPTION(PARSE_IOTUNE);
FOR_EACH_IOTUNE_ULL_OPTION(FORMAT_IOTUNE); Please do not do this. Do not hide this any deeper into macros. It's hard to understand and harder to debug. got it, thanks for your info!
-- Thanks and Regards, Wu

From: Chun Feng Wu <wucf@linux.ibm.com> * Define new struct 'virDomainThrottleFilterDef' and corresponding destructor * Update _virDomainDiskDef to include virDomainThrottleFilterDef * Support new resource "Parse" and "Format" for operations between DOM XML and structs Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_conf.c | 95 +++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 20 +++++++++ src/conf/virconftypes.h | 2 + 3 files changed, 117 insertions(+) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 05d6f7ad3a..0f6979091b 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3776,6 +3776,16 @@ virDomainThrottleGroupDefArrayFree(virDomainThrottleGroupDef **def, } +void +virDomainThrottleFilterDefFree(virDomainThrottleFilterDef *def) +{ + if (!def) + return; + g_free(def->group_name); + g_free(def->nodename); +} + + void virDomainResourceDefFree(virDomainResourceDef *resource) { @@ -7832,6 +7842,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, @@ -8322,6 +8379,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); @@ -22310,6 +22370,24 @@ virDomainThrottleGroupDel(virDomainDef *def, } +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, @@ -22926,6 +23004,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) @@ -23212,6 +23305,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 c9e3fcd924..952fe7f567 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -518,6 +518,15 @@ void virDomainDiskIothreadDefFree(virDomainDiskIothreadDef *def); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainDiskIothreadDef, virDomainDiskIothreadDefFree); +/* Stores information related to a ThrottleFilter resource. */ +struct _virDomainThrottleFilterDef { + char *group_name; + /* those below are internal fields used for runtime by qemu */ + unsigned int id; /* throttle filter identifier, 0 is unset */ + 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? */ @@ -550,6 +559,9 @@ struct _virDomainDiskDef { virDomainBlockIoTuneInfo blkdeviotune; + size_t nthrottlefilters; + virDomainThrottleFilterDef **throttlefilters; + char *driverName; char *serial; @@ -4543,3 +4555,11 @@ virDomainThrottleGroupByName(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 d51e8f5f40..1cf68b2814 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.34.1

From: Chun Feng Wu <wucf@linux.ibm.com> * 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 <wucf@linux.ibm.com> --- src/qemu/qemu_monitor.c | 34 ++++++++ src/qemu/qemu_monitor.h | 14 ++++ src/qemu/qemu_monitor_json.c | 150 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 14 ++++ 4 files changed, 212 insertions(+) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 34e2ccab97..2f067ab5d6 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2996,6 +2996,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 b78f539c85..6474ce124c 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1062,6 +1062,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 c5e758e7f8..462b40cb6b 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -4633,6 +4633,156 @@ 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(); + + if (qemuMonitorMakeThrottleGroupLimits(limits, info)<0) + return -1; + + if (!(cmd = qemuMonitorJSONMakeCommand("qom-set", + "s:property", "limits", + "s:path", qomid, + "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, \ + _("throttle group field '%1$s' missing in qemu's output"), \ + #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; + + g_autofree char *path = g_strdup_printf("/objects/%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 9684660d86..fa3138bbf9 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -404,6 +404,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.34.1

On Wed, Jun 12, 2024 at 03:02:13 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* 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 <wucf@linux.ibm.com> --- src/qemu/qemu_monitor.c | 34 ++++++++ src/qemu/qemu_monitor.h | 14 ++++ src/qemu/qemu_monitor_json.c | 150 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 14 ++++ 4 files changed, 212 insertions(+)
[...]
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index c5e758e7f8..462b40cb6b 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -4633,6 +4633,156 @@ 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,
Please format both the string and the value on a single line (disregarding any line length "suggestions"):
+ "P:bps-read", group->read_bytes_sec, + "P:bps-write", group->write_bytes_sec,
like that
+ "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,
Please note that I'll be on hollidays, so the rest of the review will be delayed.

Sure, enjoy your holidays!

On Wed, Jun 12, 2024 at 03:02:13 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* 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 <wucf@linux.ibm.com> --- src/qemu/qemu_monitor.c | 34 ++++++++ src/qemu/qemu_monitor.h | 14 ++++ src/qemu/qemu_monitor_json.c | 150 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 14 ++++ 4 files changed, 212 insertions(+)
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 34e2ccab97..2f067ab5d6 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2996,6 +2996,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 b78f539c85..6474ce124c 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1062,6 +1062,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 c5e758e7f8..462b40cb6b 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -4633,6 +4633,156 @@ 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(); + + if (qemuMonitorMakeThrottleGroupLimits(limits, info)<0)
Formatting.
+ return -1; + + if (!(cmd = qemuMonitorJSONMakeCommand("qom-set", + "s:property", "limits", + "s:path", qomid, + "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, \ + _("throttle group field '%1$s' missing in qemu's output"), \
The error string is incorrect, because you check first whether the field exists, thus at this point it did exist but 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; + + g_autofree char *path = g_strdup_printf("/objects/%s", gname);
Note that since all objects live in one namespace in qemu you'll have to add a prefix to the group name so that the user will not be able to accidentaly pick a group name (which would be equivalent with the object 'id') which we might either be using or introduce in the future. Add a 'throttle-' prefix so that we clearly separate the throttle group objects into their own namespace. This will need to be done everywhere where you pass the throttle group name as object name to qemu.
+ 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;

let me confirm my understanding, do you mean there should be group name mapping between DOM($group_name_in_DOM) and QOM(throttle-$group_name_in_DOM) for both throttle group and throttle filter? if so, there seems two ways to achieve that: - mapping group_name in callers like qemu_driver.c, qemu_hotplug.c, qemu_command.c - or put all mappings only into qemu_monitor.c/qemu_monitor_json.c, in this way, I may need to expose more methods within monitor to prepare virJSONValueObject for ThrottleGroup and ThrottleFilter creation, it seems this way can centralize mapping logic within monitor only On 2024/7/26 21:58, Peter Krempa wrote:
Note that since all objects live in one namespace in qemu you'll have to add a prefix to the group name so that the user will not be able to accidentaly pick a group name (which would be equivalent with the object 'id') which we might either be using or introduce in the future.
Add a 'throttle-' prefix so that we clearly separate the throttle group objects into their own namespace.
This will need to be done everywhere where you pass the throttle group name as object name to qemu.
-- Thanks and Regards, Wu

On Wed, Aug 07, 2024 at 12:07:26 +0800, Chun Feng Wu wrote: Please do not top-post on technical lists. Put your reply inline with the relevant context.
let me confirm my understanding, do you mean there should be group name mapping between DOM($group_name_in_DOM) and QOM(throttle-$group_name_in_DOM) for both throttle group and throttle filter? if so, there seems two ways to achieve that:
The main point is that the throttle group names are arbitrary, we must ensure that the user won't pick a name that collides with any other QOM name or for that matter for any thing else. There are multiple ways how to achieve that, but the easiest seems to add a prefix and sanitize the name to e.g contain only characters and numbers. Others like numbering them arbitrarily or so seem to be too much hassle. For the -blockdev throttle naming they should use existing node name generators and not be based on anything user provided. Node-names are length-limited so users might pick a name that'd exceed the limit. Thus not allowing any control is the best there.
- mapping group_name in callers like qemu_driver.c, qemu_hotplug.c, qemu_command.c
- or put all mappings only into qemu_monitor.c/qemu_monitor_json.c, in this way, I may need to expose more methods within monitor to prepare virJSONValueObject for ThrottleGroup and ThrottleFilter creation, it seems this way can centralize mapping logic within monitor only
It can't be limited to monitor, because you need to construct the trhottling '-object' also when starting up qemu. So it needs to apply wherever appropriate. As the prefix is constant you don't need any helpers or extra infra to decorate it. Just add it where appropriate.
On 2024/7/26 21:58, Peter Krempa wrote:
Note that since all objects live in one namespace in qemu you'll have to add a prefix to the group name so that the user will not be able to accidentaly pick a group name (which would be equivalent with the object 'id') which we might either be using or introduce in the future.
Add a 'throttle-' prefix so that we clearly separate the throttle group objects into their own namespace.
This will need to be done everywhere where you pass the throttle group name as object name to qemu.
-- Thanks and Regards,
Wu

From: Chun Feng Wu <wucf@linux.ibm.com> Within "testQemuMonitorJSONqemuMonitorJSONUpdateThrottleGroup" * Test qemuMonitorJSONGetThrottleGroup * Test qemuMonitorJSONUpdateThrottleGroup, which updates limits through "qom-set" Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- tests/qemumonitorjsontest.c | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index 45cee23798..7e034234c6 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -1853,6 +1853,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\": \"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) { @@ -2899,6 +2984,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.34.1

On Wed, Jun 12, 2024 at 03:02:14 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
Within "testQemuMonitorJSONqemuMonitorJSONUpdateThrottleGroup" * Test qemuMonitorJSONGetThrottleGroup * Test qemuMonitorJSONUpdateThrottleGroup, which updates limits through "qom-set"
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- tests/qemumonitorjsontest.c | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)
This will require some changes after adding the prefix for the throttle group objects, but that won't invalidate: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <wucf@linux.ibm.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 <wucf@linux.ibm.com> --- include/libvirt/libvirt-domain.h | 21 +++ src/driver-hypervisor.h | 22 ++++ src/libvirt-domain.c | 196 ++++++++++++++++++++++++++++ src/libvirt_private.syms | 9 ++ 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, 414 insertions(+), 1 deletion(-) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 2f5b01bbfe..4f71e2bf18 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -6525,4 +6525,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..37ba587b57 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -14162,3 +14162,199 @@ 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.5.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 and virTypedParameterFlags + * + * Get all block IO tunable parameters for specific throttle group. @group cannot be NULL. + * @nparams gives how many slots were filled with parameter information + * + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.5.0 + */ +int +virDomainGetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + virConnectPtr conn; + int rc; + + 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); + + rc = VIR_DRV_SUPPORTS_FEATURE(dom->conn->driver, dom->conn, + VIR_DRV_FEATURE_TYPED_PARAM_STRING); + if (rc < 0) + goto error; + if (rc) + flags |= VIR_TYPED_PARAM_STRING_OKAY; + + VIR_EXCLUSIVE_FLAGS_GOTO(VIR_DOMAIN_AFFECT_LIVE, + VIR_DOMAIN_AFFECT_CONFIG, + 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 and virTypedParameterFlags + * + * 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.5.0 + */ +int +virDomainDelThrottleGroup(virDomainPtr dom, + const char *group, + unsigned int flags) +{ + virConnectPtr conn; + int rc; + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(group, error); + + rc = VIR_DRV_SUPPORTS_FEATURE(dom->conn->driver, dom->conn, + VIR_DRV_FEATURE_TYPED_PARAM_STRING); + if (rc < 0) + goto error; + if (rc) + flags |= VIR_TYPED_PARAM_STRING_OKAY; + + VIR_EXCLUSIVE_FLAGS_GOTO(VIR_DOMAIN_AFFECT_LIVE, + VIR_DOMAIN_AFFECT_CONFIG, + 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 653c84a520..a438ffa795 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -668,6 +668,15 @@ virDomainTaintMessageTypeFromString; virDomainTaintMessageTypeToString; virDomainTaintTypeFromString; virDomainTaintTypeToString; +virDomainThrottleFilterDefFree; +virDomainThrottleFilterFind; +virDomainThrottleGroupAdd; +virDomainThrottleGroupByName; +virDomainThrottleGroupDefCopy; +virDomainThrottleGroupDefFree; +virDomainThrottleGroupDel; +virDomainThrottleGroupFind; +virDomainThrottleGroupUpdate; virDomainTimerModeTypeFromString; virDomainTimerModeTypeToString; virDomainTimerNameTypeFromString; diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 7a3492d9d7..2b37f29a28 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.5.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 cfc1067e40..f5a9e45303 100644 --- a/src/remote/remote_daemon_dispatch.c +++ b/src/remote/remote_daemon_dispatch.c @@ -3614,6 +3614,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..4f2b28e662 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.5.0 */ + .domainGetThrottleGroup = remoteDomainGetThrottleGroup, /* 10.5.0 */ + .domainDelThrottleGroup = remoteDomainDelThrottleGroup, /* 10.5.0 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 41c045ff78..e7e91dfb07 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:read + */ + 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.34.1

On Wed, Jun 12, 2024 at 03:02:15 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.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
Please use shorter lines in commit message.
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- include/libvirt/libvirt-domain.h | 21 +++ src/driver-hypervisor.h | 22 ++++ src/libvirt-domain.c | 196 ++++++++++++++++++++++++++++ src/libvirt_private.syms | 9 ++ 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, 414 insertions(+), 1 deletion(-)
[...]
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 7c6b93963c..37ba587b57 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -14162,3 +14162,199 @@ 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.5.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 and virTypedParameterFlags
Typed parameter flags are not to be passed by user.
+ * + * Get all block IO tunable parameters for specific throttle group. @group cannot be NULL. + * @nparams gives how many slots were filled with parameter information + * + * + * Returns -1 in case of error, 0 in case of success. + * + * Since: 10.5.0 + */ +int +virDomainGetThrottleGroup(virDomainPtr dom, + const char *group, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + virConnectPtr conn; + int rc; + + 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); + + rc = VIR_DRV_SUPPORTS_FEATURE(dom->conn->driver, dom->conn, + VIR_DRV_FEATURE_TYPED_PARAM_STRING);
All libvirt versions supporting this API support also string typed params, so this code is not needed and you can assume support.
+ if (rc < 0) + goto error; + if (rc) + flags |= VIR_TYPED_PARAM_STRING_OKAY; + + VIR_EXCLUSIVE_FLAGS_GOTO(VIR_DOMAIN_AFFECT_LIVE, + VIR_DOMAIN_AFFECT_CONFIG, + error);
You explicitly document that both can be used.
+ + 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 and virTypedParameterFlags
This takes not typed params.
+ * + * 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.
[1].
+ * 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.5.0 + */ +int +virDomainDelThrottleGroup(virDomainPtr dom, + const char *group, + unsigned int flags) +{ + virConnectPtr conn; + int rc; + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(group, error); + + rc = VIR_DRV_SUPPORTS_FEATURE(dom->conn->driver, dom->conn, + VIR_DRV_FEATURE_TYPED_PARAM_STRING);
This API is not passing any typed parameters so all of this code is not actually used.
+ if (rc < 0) + goto error; + if (rc) + flags |= VIR_TYPED_PARAM_STRING_OKAY; + + VIR_EXCLUSIVE_FLAGS_GOTO(VIR_DOMAIN_AFFECT_LIVE, + VIR_DOMAIN_AFFECT_CONFIG, + error);
This API is updating state of a VM thus must not be allowed on read-only connections. You'll need to also use proper flags in the .x file (which I've deleted while replying). Also note that many of existing APIs allow dual use of _LIVE and _CONFIG flags. You even document it [1].
+ + 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_public.syms b/src/libvirt_public.syms index 7a3492d9d7..2b37f29a28 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.5.0 {
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 cfc1067e40..f5a9e45303 100644 --- a/src/remote/remote_daemon_dispatch.c +++ b/src/remote/remote_daemon_dispatch.c @@ -3614,6 +3614,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..4f2b28e662 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.5.0 */ + .domainGetThrottleGroup = remoteDomainGetThrottleGroup, /* 10.5.0 */ + .domainDelThrottleGroup = remoteDomainDelThrottleGroup, /* 10.5.0 */
Don't forget to update these to 10.7.0
};

From: Chun Feng Wu <wucf@linux.ibm.com> Implement the following methods in qemu driver: * Extract common methods for "qemuDomainSetBlockIoTune" and "qemuDomainSetThrottleGroup": qemuDomainValidateBlockIoTune, qemuDomainSetBlockIoTuneFields, qemuDomainCheckBlockIoTuneMutualExclusion, qemuDomainCheckBlockIoTuneMax. * "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, throttle group feature requries such flag * "objectAddNoWrap"("props") check is done by reusing qemuMonitorAddObject in qemuDomainSetThrottleGroup Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/qemu/qemu_driver.c | 611 +++++++++++++++++++++++++++++++++++------ 1 file changed, 528 insertions(+), 83 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e2698c7924..f7e435d6d5 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14955,35 +14955,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, @@ -15028,32 +15001,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; \ @@ -15093,10 +15062,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; @@ -15119,56 +15088,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; + return 0; +} - cur_info = qemuDomainFindGroupBlockIoTune(def, disk, &info); - 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"), \ @@ -15180,7 +15149,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); \ @@ -15198,6 +15167,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 */ @@ -19995,6 +20054,389 @@ qemuDomainGraphicsReload(virDomainPtr domain, return ret; } + +/** + * qemuDomainCheckThrottleGroupReset: + * @groupname: group to create or update + * @newiotune: Pointer to iotune, which contains detailed items + * + * Check if params within @newiotune contain all zero values, if yes + * and @newiotune specifies the same group name as @groupname, return + * failure since it's meaningless to set all zero values in @newiotune + * + * Returns -1 on failure, or 0 on success. + */ +static int +qemuDomainCheckThrottleGroupReset(const char *groupname, + virDomainBlockIoTuneInfo *newiotune) +{ + if (virDomainBlockIoTuneInfoHasAny(newiotune)) + return 0; + + if (newiotune->group_name && + STRNEQ_NULLABLE(newiotune->group_name, groupname)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("creating a new group/updating existing with all parameters zero is not supported")); + return -1; + } + + /* all zero means remove any throttling and remove from group for qemu */ + VIR_FREE(newiotune->group_name); + + return 0; +} + + +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 (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU_CAPS_OBJECT_JSON support is required for throttle group creation")); + return -1; + } + + if (def) { + if (qemuDomainCheckThrottleGroupReset(groupname, &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 { + if (qemuMonitorThrottleGroupLimits(limits, &info)<0) + goto endjob; + if (qemuMonitorCreateObjectProps(&props, + "throttle-group", groupname, + "a:limits", &limits, + NULL) < 0) + goto endjob; + qemuDomainObjEnterMonitor(vm); + /* different QMP version has different format for "object-add", reuse + * "qemuMonitorAddObject" to check if "objectAddNoWrap"("props") is + * requried */ + 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 (qemuDomainCheckThrottleGroupReset(groupname, &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 | + VIR_TYPED_PARAM_STRING_OKAY, -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; + + /* the API check guarantees that only one of the definitions will be set */ + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; + + if (def) { + 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_INTERNAL_ERROR, + _("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 | + VIR_TYPED_PARAM_STRING_OKAY, -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; + + if (def) { + int rc = 0; + + /* check if this group is still being used by disks */ + if (qemuDomainCheckThrottleGroupRef(def, groupname) < 0) + goto endjob; + + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorDelObject(qemuDomainGetMonitor(vm), groupname, true); + qemuDomainObjExitMonitor(vm); + + if (rc < 0) + goto endjob; + + virDomainThrottleGroupDel(def, groupname); + qemuDomainSaveStatus(vm); + } + + if (persistentDef) { + /* check if this group is still being used by disks */ + if (qemuDomainCheckThrottleGroupRef(persistentDef, groupname) < 0) + goto endjob; + + 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, @@ -20245,6 +20687,9 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainSetLaunchSecurityState = qemuDomainSetLaunchSecurityState, /* 8.0.0 */ .domainFDAssociate = qemuDomainFDAssociate, /* 9.0.0 */ .domainGraphicsReload = qemuDomainGraphicsReload, /* 10.2.0 */ + .domainSetThrottleGroup = qemuDomainSetThrottleGroup, /* 10.5.0 */ + .domainGetThrottleGroup = qemuDomainGetThrottleGroup, /* 10.5.0 */ + .domainDelThrottleGroup = qemuDomainDelThrottleGroup, /* 10.5.0 */ }; -- 2.34.1

On Wed, Jun 12, 2024 at 03:02:16 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
Implement the following methods in qemu driver: * Extract common methods for "qemuDomainSetBlockIoTune" and "qemuDomainSetThrottleGroup": qemuDomainValidateBlockIoTune, qemuDomainSetBlockIoTuneFields, qemuDomainCheckBlockIoTuneMutualExclusion, qemuDomainCheckBlockIoTuneMax. * "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, throttle group feature requries such flag * "objectAddNoWrap"("props") check is done by reusing qemuMonitorAddObject in qemuDomainSetThrottleGroup
Same comment as before. Try to keep the lines shorter and preferrably do a high level description rather than repeating what the commit does.
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/qemu/qemu_driver.c | 611 +++++++++++++++++++++++++++++++++++------ 1 file changed, 528 insertions(+), 83 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e2698c7924..f7e435d6d5 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14955,35 +14955,8 @@ qemuDomainCheckBlockIoTuneReset(virDomainDiskDef *disk,
static int -qemuDomainSetBlockIoTune(virDomainPtr dom, - const char *path, - virTypedParameterPtr params, - int nparams, - unsigned int flags) +qemuDomainValidateBlockIoTune(virTypedParameterPtr params, int nparams)
This refactor should be separated to a separate commit as this commit is getting a bit too complex.
{ - 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, @@ -15028,32 +15001,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; \ @@ -15093,10 +15062,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; @@ -15119,56 +15088,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; + return 0; +}
- cur_info = qemuDomainFindGroupBlockIoTune(def, disk, &info);
- 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"), \ @@ -15180,7 +15149,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); \ @@ -15198,6 +15167,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 */
All of the above belongs to a separate or two separate commits.
@@ -19995,6 +20054,389 @@ qemuDomainGraphicsReload(virDomainPtr domain, return ret; }
+ +/** + * qemuDomainCheckThrottleGroupReset: + * @groupname: group to create or update + * @newiotune: Pointer to iotune, which contains detailed items + * + * Check if params within @newiotune contain all zero values, if yes + * and @newiotune specifies the same group name as @groupname, return + * failure since it's meaningless to set all zero values in @newiotune + * + * Returns -1 on failure, or 0 on success. + */ +static int +qemuDomainCheckThrottleGroupReset(const char *groupname, + virDomainBlockIoTuneInfo *newiotune) +{ + if (virDomainBlockIoTuneInfoHasAny(newiotune)) + return 0; + + if (newiotune->group_name && + STRNEQ_NULLABLE(newiotune->group_name, groupname)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("creating a new group/updating existing with all parameters zero is not supported")); + return -1; + } + + /* all zero means remove any throttling and remove from group for qemu */ + VIR_FREE(newiotune->group_name); + + return 0; +} + + +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;
Spacing.
+ + 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 (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU_CAPS_OBJECT_JSON support is required for throttle group creation")); + return -1; + }
If the VM is not alive the above check will not work. If the VM is not alive this must not be checked. Also _("QEMU_CAPS_OBJECT_JSON is an internal name and VIR_ERR_INTERNAL_ERROR is not appropriate.
+ + if (def) { + if (qemuDomainCheckThrottleGroupReset(groupname, &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 { + if (qemuMonitorThrottleGroupLimits(limits, &info)<0) + goto endjob; + if (qemuMonitorCreateObjectProps(&props, + "throttle-group", groupname,
As noted groupname must be prefixed when used with qemu to avoid namespacing issues by users being able to choose a name.
+ "a:limits", &limits, + NULL) < 0) + goto endjob; + qemuDomainObjEnterMonitor(vm); + /* different QMP version has different format for "object-add", reuse + * "qemuMonitorAddObject" to check if "objectAddNoWrap"("props") is + * requried */
Regardless of this comment all object creation must use the appropriate API so this comment seems pointless.
+ 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 (qemuDomainCheckThrottleGroupReset(groupname, &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 | + VIR_TYPED_PARAM_STRING_OKAY, -1);
As noted all callers which know this API are new enough to support typed param string so this flag doesn't make sense.
+ + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainGetThrottleGroupEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (virDomainObjBeginJob(vm, VIR_JOB_QUERY) < 0) + goto cleanup; + + /* the API check guarantees that only one of the definitions will be set */ + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob;
I might have misplaced a comment about supporting mutual exclusivity in reply to previous patch. If that is so please ignore that comment.
+ + if (def) { + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorGetThrottleGroup(qemuDomainGetMonitor(vm), groupname, reply);
You don't validate that the given throttle group even exists. You shouldn't allow passing that to qemu if it's not in the definition. Also as noted before you'll have to prefix the group name to avoid namespace collisions.
+ 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_INTERNAL_ERROR,
This is not an internal error but invalid argument from the user.
+ _("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 | + VIR_TYPED_PARAM_STRING_OKAY, -1);
This API is not taking typed params.
+ + 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; + + if (def) { + int rc = 0; + + /* check if this group is still being used by disks */ + if (qemuDomainCheckThrottleGroupRef(def, groupname) < 0) + goto endjob; + + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorDelObject(qemuDomainGetMonitor(vm), groupname, true); + qemuDomainObjExitMonitor(vm); + + if (rc < 0) + goto endjob; + + virDomainThrottleGroupDel(def, groupname); + qemuDomainSaveStatus(vm); + } + + if (persistentDef) { + /* check if this group is still being used by disks */ + if (qemuDomainCheckThrottleGroupRef(persistentDef, groupname) < 0) + goto endjob;
This should be done before the step modifying the live config so that you catch errors upfront and avoid a partial success with error reported.
+ + 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; +}

On 2024/7/26 23:06, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:16 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
Implement the following methods in qemu driver: * Extract common methods for "qemuDomainSetBlockIoTune" and "qemuDomainSetThrottleGroup": qemuDomainValidateBlockIoTune, qemuDomainSetBlockIoTuneFields, qemuDomainCheckBlockIoTuneMutualExclusion, qemuDomainCheckBlockIoTuneMax. * "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, throttle group feature requries such flag * "objectAddNoWrap"("props") check is done by reusing qemuMonitorAddObject in qemuDomainSetThrottleGroup Same comment as before. Try to keep the lines shorter and preferrably do a high level description rather than repeating what the commit does.
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/qemu/qemu_driver.c | 611 +++++++++++++++++++++++++++++++++++------ 1 file changed, 528 insertions(+), 83 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e2698c7924..f7e435d6d5 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14955,35 +14955,8 @@ qemuDomainCheckBlockIoTuneReset(virDomainDiskDef *disk,
static int -qemuDomainSetBlockIoTune(virDomainPtr dom, - const char *path, - virTypedParameterPtr params, - int nparams, - unsigned int flags) +qemuDomainValidateBlockIoTune(virTypedParameterPtr params, int nparams) This refactor should be separated to a separate commit as this commit is getting a bit too complex.
{ - 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, @@ -15028,32 +15001,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; \ @@ -15093,10 +15062,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; @@ -15119,56 +15088,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; + return 0; +}
- cur_info = qemuDomainFindGroupBlockIoTune(def, disk, &info);
- 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"), \ @@ -15180,7 +15149,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); \ @@ -15198,6 +15167,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 */ All of the above belongs to a separate or two separate commits.
@@ -19995,6 +20054,389 @@ qemuDomainGraphicsReload(virDomainPtr domain, return ret; }
+ +/** + * qemuDomainCheckThrottleGroupReset: + * @groupname: group to create or update + * @newiotune: Pointer to iotune, which contains detailed items + * + * Check if params within @newiotune contain all zero values, if yes + * and @newiotune specifies the same group name as @groupname, return + * failure since it's meaningless to set all zero values in @newiotune + * + * Returns -1 on failure, or 0 on success. + */ +static int +qemuDomainCheckThrottleGroupReset(const char *groupname, + virDomainBlockIoTuneInfo *newiotune) +{ + if (virDomainBlockIoTuneInfoHasAny(newiotune)) + return 0; + + if (newiotune->group_name && + STRNEQ_NULLABLE(newiotune->group_name, groupname)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("creating a new group/updating existing with all parameters zero is not supported")); + return -1; + } + + /* all zero means remove any throttling and remove from group for qemu */ + VIR_FREE(newiotune->group_name); + + return 0; +} + + +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; Spacing.
+ + 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 (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU_CAPS_OBJECT_JSON support is required for throttle group creation")); + return -1; + } If the VM is not alive the above check will not work. If the VM is not alive this must not be checked.
Also _("QEMU_CAPS_OBJECT_JSON is an internal name and VIR_ERR_INTERNAL_ERROR is not appropriate.
is it okay to update it as: if (virDomainObjIsActive(vm)) { if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("support for '-object' with json format in QEMU command is required when creating throttle group")); return -1; } } BTW, may I know if you finished all commits review for v3?
+ + if (def) { + if (qemuDomainCheckThrottleGroupReset(groupname, &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 { + if (qemuMonitorThrottleGroupLimits(limits, &info)<0) + goto endjob; + if (qemuMonitorCreateObjectProps(&props, + "throttle-group", groupname, As noted groupname must be prefixed when used with qemu to avoid namespacing issues by users being able to choose a name.
+ "a:limits", &limits, + NULL) < 0) + goto endjob; + qemuDomainObjEnterMonitor(vm); + /* different QMP version has different format for "object-add", reuse + * "qemuMonitorAddObject" to check if "objectAddNoWrap"("props") is + * requried */ Regardless of this comment all object creation must use the appropriate API so this comment seems pointless.
+ 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 (qemuDomainCheckThrottleGroupReset(groupname, &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 | + VIR_TYPED_PARAM_STRING_OKAY, -1); As noted all callers which know this API are new enough to support typed param string so this flag doesn't make sense.
+ + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + if (virDomainGetThrottleGroupEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (virDomainObjBeginJob(vm, VIR_JOB_QUERY) < 0) + goto cleanup; + + /* the API check guarantees that only one of the definitions will be set */ + if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) + goto endjob; I might have misplaced a comment about supporting mutual exclusivity in reply to previous patch. If that is so please ignore that comment.
+ + if (def) { + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorGetThrottleGroup(qemuDomainGetMonitor(vm), groupname, reply); You don't validate that the given throttle group even exists. You shouldn't allow passing that to qemu if it's not in the definition.
Also as noted before you'll have to prefix the group name to avoid namespace collisions.
+ 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_INTERNAL_ERROR, This is not an internal error but invalid argument from the user.
+ _("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 | + VIR_TYPED_PARAM_STRING_OKAY, -1); This API is not taking typed params.
+ + 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; + + if (def) { + int rc = 0; + + /* check if this group is still being used by disks */ + if (qemuDomainCheckThrottleGroupRef(def, groupname) < 0) + goto endjob; + + qemuDomainObjEnterMonitor(vm); + rc = qemuMonitorDelObject(qemuDomainGetMonitor(vm), groupname, true); + qemuDomainObjExitMonitor(vm); + + if (rc < 0) + goto endjob; + + virDomainThrottleGroupDel(def, groupname); + qemuDomainSaveStatus(vm); + } + + if (persistentDef) { + /* check if this group is still being used by disks */ + if (qemuDomainCheckThrottleGroupRef(persistentDef, groupname) < 0) + goto endjob; This should be done before the step modifying the live config so that you catch errors upfront and avoid a partial success with error reported.
+ + 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; +}
-- Thanks and Regards, Wu

On Fri, Aug 09, 2024 at 16:00:11 +0800, Chun Feng Wu wrote:
On 2024/7/26 23:06, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:16 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
Implement the following methods in qemu driver: * Extract common methods for "qemuDomainSetBlockIoTune" and "qemuDomainSetThrottleGroup": qemuDomainValidateBlockIoTune, qemuDomainSetBlockIoTuneFields, qemuDomainCheckBlockIoTuneMutualExclusion, qemuDomainCheckBlockIoTuneMax. * "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, throttle group feature requries such flag * "objectAddNoWrap"("props") check is done by reusing qemuMonitorAddObject in qemuDomainSetThrottleGroup Same comment as before. Try to keep the lines shorter and preferrably do a high level description rather than repeating what the commit does.
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/qemu/qemu_driver.c | 611 +++++++++++++++++++++++++++++++++++------ 1 file changed, 528 insertions(+), 83 deletions(-)
[...]
+ + 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 (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU_CAPS_OBJECT_JSON support is required for throttle group creation")); + return -1; + } If the VM is not alive the above check will not work. If the VM is not alive this must not be checked.
Also _("QEMU_CAPS_OBJECT_JSON is an internal name and VIR_ERR_INTERNAL_ERROR is not appropriate.
is it okay to update it as:
if (virDomainObjIsActive(vm)) { if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_JSON)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("support for '-object' with json format in QEMU command is required when creating throttle group"));
"This QEMU doesn't support throttle group creation" The detail about needing JSON for '-object' is not really necessary in the error message.
} }
BTW, may I know if you finished all commits review for v3?
Not yet. I need to get back to it. Sorry I was busy.

From: Chun Feng Wu <wucf@linux.ibm.com> When attaching disk along with specified throttle groups, those groups will be chained up by parent node name, this change includes service side codes: * Each filter references one throttle group by group name * Update "qemuDomainDiskGetTopNodename" to take top throttle node name if disk has throttles * Each filter has a nodename, and those filters are chained up in sequence * Filter nodename index is generated by reusing qemuDomainStorageIDNew and current global sequence number is persistented in virDomainObj->privateData(qemuDomainObjPrivate)->nodenameindex * During hotplug, filter is created through QMP request("blockdev-add" with "driver":"throttle") to QEMU * Delete filters by "qemuBlockThrottleFiltersDetach"("blockdev-del") when detaching device * Use "iotune" and "throttlefilters" exclusively for specific disk Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 8 +++ src/qemu/qemu_block.c | 131 +++++++++++++++++++++++++++++++++++++ src/qemu/qemu_block.h | 53 +++++++++++++++ src/qemu/qemu_command.c | 84 ++++++++++++++++++++++++ src/qemu/qemu_command.h | 9 +++ src/qemu/qemu_domain.c | 39 ++++++++++- src/qemu/qemu_domain.h | 8 +++ src/qemu/qemu_driver.c | 6 ++ src/qemu/qemu_hotplug.c | 33 ++++++++++ 9 files changed, 370 insertions(+), 1 deletion(-) diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 395e036e8f..4cc5ed7577 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -942,6 +942,14 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; } + if (disk->throttlefilters && (disk->blkdeviotune.group_name || + virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) { + virReportError(VIR_ERR_XML_ERROR, + _("block 'throttlefilters' can't be used together with 'iotune' for disk '%1$s'"), + disk->dst); + return -1; + } + return 0; } diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 738b72d7ea..9b8ff65887 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -2739,6 +2739,137 @@ 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. + * e.g. {"execute":"blockdev-add", "arguments":{"driver":"throttle", + * "node-name":"libvirt-2-filter", "throttle-group":"limits0", + * "file": "libvirt-1-format"}} + */ +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName) +{ + g_autoptr(virJSONValue) props = NULL; + + if (virJSONValueObjectAdd(&props, + "s:driver", "throttle", + "s:node-name", qemuBlockThrottleFilterGetNodename(filter), + "s:throttle-group", filter->group_name, + "s:file", parentNodeName, + NULL) < 0) + return 0; + + 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); + data->filterAttached = true; + + 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); +} + + +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 f9e961d85d..9888954ce4 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -214,6 +214,59 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, virStorageSource *src, virStorageSource *templ); +void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename); + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter); + +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName); + +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 2d0eddc79e..5ccae956d3 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1582,6 +1582,13 @@ qemuDiskConfigBlkdeviotuneEnabled(const virDomainDiskDef *disk) } +bool +qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk) +{ + return disk->nthrottlefilters > 0; +} + + /** * qemuDiskBusIsSD: * @bus: disk bus @@ -11055,6 +11062,83 @@ 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, which is used to build "blockdev-add" QMP request + * + * 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; + g_autofree char *tmp_nodename = NULL; + + data = g_new0(qemuBlockThrottleFiltersData, 1); + /* 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++) { + tmp_nodename = g_strdup(parentNodeName); + if (qemuBuildThrottleFiltersAttachPrepareBlockdevOne(data, disk->throttlefilters[i], tmp_nodename) < 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 dca8877703..ce39acfb2c 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -116,6 +116,15 @@ qemuBlockStorageSourceChainData * qemuBuildStorageSourceChainAttachPrepareBlockdevTop(virStorageSource *top, virStorageSource *backingStore); +qemuBlockThrottleFiltersData * +qemuBuildThrottleFiltersAttachPrepareBlockdev(virDomainDiskDef *disk); + +qemuBlockThrottleFiltersData * +qemuBuildThrottleFiltersDetachPrepareBlockdev(virDomainDiskDef *disk); + +bool +qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk); + virJSONValue * qemuBuildDiskDeviceProps(const virDomainDef *def, virDomainDiskDef *disk, diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 7ba2ea4a5e..2831036e23 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -7989,7 +7989,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). @@ -8005,6 +8006,10 @@ qemuDomainDiskGetTopNodename(virDomainDiskDef *disk) if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) return priv->nodeCopyOnRead; + /* If disk has throttles, take top throttle node name */ + if (disk->nthrottlefilters > 0) + return disk->throttlefilters[disk->nthrottlefilters-1]->nodename; + return qemuBlockStorageSourceGetEffectiveNodename(disk->src); } @@ -11336,6 +11341,32 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, } +int +qemuDomainPrepareThrottleFilterBlockdevNodename(virDomainThrottleFilterDef *filter, + const char *nodenameprefix) +{ + char *nodename = g_strdup_printf("%s-filter", nodenameprefix); + + qemuBlockThrottleFilterSetNodename(filter, nodename); + + return 0; +} + + +int +qemuDomainPrepareThrottleFilterBlockdev(virDomainThrottleFilterDef *filter, + qemuDomainObjPrivate *priv) +{ + g_autofree char *nodenameprefix = NULL; + + filter->id = qemuDomainStorageIDNew(priv); + + nodenameprefix = g_strdup_printf("libvirt-%u", filter->id); + + return qemuDomainPrepareThrottleFilterBlockdevNodename(filter, nodenameprefix); +} + + int qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDef *disk, virStorageSource *src, @@ -11359,6 +11390,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) @@ -11369,6 +11401,11 @@ qemuDomainPrepareDiskSourceBlockdev(virDomainDiskDef *disk, return -1; } + for (i = 0; i < disk->nthrottlefilters; i++) { + if (qemuDomainPrepareThrottleFilterBlockdev(disk->throttlefilters[i], priv) < 0) + return -1; + } + return 0; } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index a3089ea449..38bba4e3ae 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -772,6 +772,14 @@ int qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDef *disk, qemuDomainObjPrivate *priv, virQEMUDriverConfig *cfg); +int +qemuDomainPrepareThrottleFilterBlockdev(virDomainThrottleFilterDef *filter, + qemuDomainObjPrivate *priv); + +int +qemuDomainPrepareThrottleFilterBlockdevNodename(virDomainThrottleFilterDef *filter, + const char *nodenameprefix); + void qemuDomainCleanupAdd(virDomainObj *vm, qemuDomainCleanupCallback cb); void qemuDomainCleanupRemove(virDomainObj *vm, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f7e435d6d5..60989ae693 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14828,6 +14828,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 4a3f4f657e..103b9e9f00 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -657,6 +657,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; @@ -695,6 +696,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; @@ -766,6 +780,8 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (extensionDeviceAttached) ignore_value(qemuDomainDetachExtensionDevice(priv->mon, &disk->info)); + qemuBlockThrottleFiltersDetach(priv->mon, filterData); + qemuBlockStorageSourceChainDetach(priv->mon, data); qemuDomainObjExitMonitor(vm); @@ -921,6 +937,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")); @@ -4499,6 +4523,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; @@ -4537,6 +4562,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.34.1

On Wed, Jun 12, 2024 at 03:02:17 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
When attaching disk along with specified throttle groups, those groups will be chained up by parent node name, this change includes service side codes: * Each filter references one throttle group by group name * Update "qemuDomainDiskGetTopNodename" to take top throttle node name if disk has throttles * Each filter has a nodename, and those filters are chained up in sequence * Filter nodename index is generated by reusing qemuDomainStorageIDNew and current global sequence number is persistented in virDomainObj->privateData(qemuDomainObjPrivate)->nodenameindex * During hotplug, filter is created through QMP request("blockdev-add" with "driver":"throttle") to QEMU * Delete filters by "qemuBlockThrottleFiltersDetach"("blockdev-del") when detaching device * Use "iotune" and "throttlefilters" exclusively for specific disk
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 8 +++ src/qemu/qemu_block.c | 131 +++++++++++++++++++++++++++++++++++++ src/qemu/qemu_block.h | 53 +++++++++++++++ src/qemu/qemu_command.c | 84 ++++++++++++++++++++++++ src/qemu/qemu_command.h | 9 +++ src/qemu/qemu_domain.c | 39 ++++++++++- src/qemu/qemu_domain.h | 8 +++ src/qemu/qemu_driver.c | 6 ++ src/qemu/qemu_hotplug.c | 33 ++++++++++ 9 files changed, 370 insertions(+), 1 deletion(-)
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 395e036e8f..4cc5ed7577 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -942,6 +942,14 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; }
+ if (disk->throttlefilters && (disk->blkdeviotune.group_name || + virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) {
This is hard to read as you've broken up the line within a nested brace. It may confuse readers. Rewrite as: if (disk->throttlefilters && (disk->blkdeviotune.group_name || virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) {
+ virReportError(VIR_ERR_XML_ERROR,
Operation unsupported.
+ _("block 'throttlefilters' can't be used together with 'iotune' for disk '%1$s'"), + disk->dst); + return -1; + } + return 0; }
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 738b72d7ea..9b8ff65887 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -2739,6 +2739,137 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, }
+void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename) +{ + g_free(filter->nodename); + filter->nodename = nodename; +} + + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter) +{ + return filter->nodename; +}
All of this helper infrastructure doesn't belong to a patch that is declaring that it's dealing with the setup of qemu. (I also wrote that below as I've noticed it there). It makes this patch too complex and thus hard to review. It's also the reason it takes me so long. I'm demotivated on such commits as it takes way too long to go through. Any setup of nodenames and other libvirt-internal stuff, such as validatio of config etc should be separated.
+/** + * qemuBlockThrottleFilterGetProps: + * @filter: throttle filter + * @parentNodeName: parent nodename of @filter + * + * Build "arguments" part of "blockdev-add" QMP cmd. + * e.g. {"execute":"blockdev-add", "arguments":{"driver":"throttle", + * "node-name":"libvirt-2-filter", "throttle-group":"limits0", + * "file": "libvirt-1-format"}}
Avoid this JSON in comments, as it's obvious from the code. Rather describe any special handling if needed.
+ */ +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName) +{ + g_autoptr(virJSONValue) props = NULL; + + if (virJSONValueObjectAdd(&props, + "s:driver", "throttle", + "s:node-name", qemuBlockThrottleFilterGetNodename(filter), + "s:throttle-group", filter->group_name, + "s:file", parentNodeName, + NULL) < 0) + return 0;
return NULL; It looks confusing because 0 is success in our code.
+ + return g_steal_pointer(&props); +}
[...]
+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); + data->filterAttached = true;
Well, if you set this to true right here, it's pointless to even have the variable. The code you've copied this from uses the 'Attached' variable so that it's set only after the object is created in qemu. This allows safe rollback. If you set it to true for everything you will attempt to potentially roll back stuff that was not yet set in qemu, making the variable itself pointless.
+ + 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));
... here ...
+ + 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); +} + + +int +qemuBlockThrottleFiltersAttach(qemuMonitor *mon, + qemuBlockThrottleFiltersData *data) +{ + size_t i; + + for (i = 0; i < data->nfilterdata; i++) { + if (qemuMonitorBlockdevAdd(mon, &data->filterdata[i]->filterProps) < 0)
This function requires the monitor to be locked. If you want to have this as a separate function you must document it and state this fact.
+ 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 f9e961d85d..9888954ce4 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -214,6 +214,59 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, virStorageSource *src, virStorageSource *templ);
+void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename); + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter); + +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName);
This function isn't used outside of qemu_block.c so it doesn't need to be exported.
+ +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 2d0eddc79e..5ccae956d3 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1582,6 +1582,13 @@ qemuDiskConfigBlkdeviotuneEnabled(const virDomainDiskDef *disk) }
+bool +qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk)
This function doesn't need to be exported.
+{ + return disk->nthrottlefilters > 0; +} + + /** * qemuDiskBusIsSD: * @bus: disk bus @@ -11055,6 +11062,83 @@ 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, which is used to build "blockdev-add" QMP request
the stuff after the comma can be dropped.
+ * + * 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; + g_autofree char *tmp_nodename = NULL; + + data = g_new0(qemuBlockThrottleFiltersData, 1); + /* 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++) { + tmp_nodename = g_strdup(parentNodeName); + if (qemuBuildThrottleFiltersAttachPrepareBlockdevOne(data, disk->throttlefilters[i], tmp_nodename) < 0) + return NULL;
The nodename copied into 'tmp_nodename' is leaked on every iteration. Also qemuBuildThrottleFiltersAttachPrepareBlockdevOne doesn't modify it so there's no point in copying it.
+ 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]);
So I didn't yet see any code that serializes any of this in the status XML, thus it seems that this will not work after you restart libvirtd/virtqemud if a VM with filters is running, and then try to detach disks. You'll need to add that, or base the filter nodename on something else. Note that presence of the node name is authoritative and we must not try to regenerate it. That would hinder changing the node names in the future. See how the copyOnRead layer node name is stored.
+ 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_domain.c b/src/qemu/qemu_domain.c index 7ba2ea4a5e..2831036e23 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -7989,7 +7989,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). @@ -8005,6 +8006,10 @@ qemuDomainDiskGetTopNodename(virDomainDiskDef *disk) if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) return priv->nodeCopyOnRead;
+ /* If disk has throttles, take top throttle node name */ + if (disk->nthrottlefilters > 0) + return disk->throttlefilters[disk->nthrottlefilters-1]->nodename;
return disk->throttlefilters[disk->nthrottlefilters - 1]->nodename; (extra spaces around subtraction operator)
return qemuBlockStorageSourceGetEffectiveNodename(disk->src); }
@@ -11336,6 +11341,32 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, }
+int +qemuDomainPrepareThrottleFilterBlockdevNodename(virDomainThrottleFilterDef *filter,
This function is used only in this file so it doesn't need to be exported. Also it's used only in one place so it can be inlined as it makes the code harder to follow than necessary.
+ const char *nodenameprefix) +{ + char *nodename = g_strdup_printf("%s-filter", nodenameprefix); + + qemuBlockThrottleFilterSetNodename(filter, nodename); + + return 0; +} + + +int +qemuDomainPrepareThrottleFilterBlockdev(virDomainThrottleFilterDef *filter, + qemuDomainObjPrivate *priv)
This function is used only in this module thus it doesn't need to be exported. Also it can't fail so there's no point in having a return value.
+{ + g_autofree char *nodenameprefix = NULL; + + filter->id = qemuDomainStorageIDNew(priv); + + nodenameprefix = g_strdup_printf("libvirt-%u", filter->id);
nodename = g_strdup_printf("libvirt-%u-filter" ... Instead of the convoluted mess and the below call.
+ + return qemuDomainPrepareThrottleFilterBlockdevNodename(filter, nodenameprefix);
[...]
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f7e435d6d5..60989ae693 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14828,6 +14828,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; + }
Preferrably add all this infrastructure which doesn't deal with the setup of qemu in a separate patch. This patch is overly complex as it intermixes all of these validation bits with the actual qemu interaction.
+ return true; }
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 4a3f4f657e..103b9e9f00 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -657,6 +657,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; @@ -695,6 +696,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 above line.
+ */ + 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; + }
The ordering is wrong. In the prepare step you are ordering the node name dependencies such as device -> copyOnRead Layer -> throttle -> image chain OBviously to ensure hierarchy at hotplug they need to be plugged from the end. This block here is placed _AFTER_ copyOnRead layer instantiation, so that will not work with disks with copyOnRead.
+ if (disk->transient) { g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL; g_autoptr(GHashTable) blockNamedNodeData = NULL; @@ -766,6 +780,8 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (extensionDeviceAttached) ignore_value(qemuDomainDetachExtensionDevice(priv->mon, &disk->info));
+ qemuBlockThrottleFiltersDetach(priv->mon, filterData); + qemuBlockStorageSourceChainDetach(priv->mon, data);
qemuDomainObjExitMonitor(vm); @@ -921,6 +937,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"));
So and now this is why I don't like the fact that you are doing this on a per-disk level. On a per-image level (if you'd instantiate 'throttle' layers when adding the image) this would not be a problem. I'd strongly prefer if you modify this such that the trottle layers will be instantiated at per-storage-source level both in XML and in the code.
+ return -1; + } + } + if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("floppy device hotplug isn't supported")); @@ -4499,6 +4523,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; @@ -4537,6 +4562,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);
In which case this'd be automatic.
+ } + qemuDomainObjExitMonitor(vm); + qemuDomainObjEnterMonitor(vm);
if (diskBackend) -- 2.34.1
This patch should also contain the corresponding commandline infrastructure for the filter itself. The disk backend code is specifically unified by using the intermediate struct so that it's guaranteed that both hotplug and commandline work the same. Thus they need to be added simultaenously.

On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote:
From: Chun Feng Wu<wucf@linux.ibm.com>
When attaching disk along with specified throttle groups, those groups will be chained up by parent node name, this change includes service side codes: * Each filter references one throttle group by group name * Update "qemuDomainDiskGetTopNodename" to take top throttle node name if disk has throttles * Each filter has a nodename, and those filters are chained up in sequence * Filter nodename index is generated by reusing qemuDomainStorageIDNew and current global sequence number is persistented in virDomainObj->privateData(qemuDomainObjPrivate)->nodenameindex * During hotplug, filter is created through QMP request("blockdev-add" with "driver":"throttle") to QEMU * Delete filters by "qemuBlockThrottleFiltersDetach"("blockdev-del") when detaching device * Use "iotune" and "throttlefilters" exclusively for specific disk
Signed-off-by: Chun Feng Wu<wucf@linux.ibm.com> --- src/conf/domain_validate.c | 8 +++ src/qemu/qemu_block.c | 131 +++++++++++++++++++++++++++++++++++++ src/qemu/qemu_block.h | 53 +++++++++++++++ src/qemu/qemu_command.c | 84 ++++++++++++++++++++++++ src/qemu/qemu_command.h | 9 +++ src/qemu/qemu_domain.c | 39 ++++++++++- src/qemu/qemu_domain.h | 8 +++ src/qemu/qemu_driver.c | 6 ++ src/qemu/qemu_hotplug.c | 33 ++++++++++ 9 files changed, 370 insertions(+), 1 deletion(-)
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 395e036e8f..4cc5ed7577 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -942,6 +942,14 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; }
+ if (disk->throttlefilters && (disk->blkdeviotune.group_name || + virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) { This is hard to read as you've broken up the line within a nested brace. It may confuse readers.
Rewrite as:
if (disk->throttlefilters && (disk->blkdeviotune.group_name || virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) {
+ virReportError(VIR_ERR_XML_ERROR, Operation unsupported.
+ _("block 'throttlefilters' can't be used together with 'iotune' for disk '%1$s'"), + disk->dst); + return -1; + } + return 0; }
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 738b72d7ea..9b8ff65887 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -2739,6 +2739,137 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, }
+void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename) +{ + g_free(filter->nodename); + filter->nodename = nodename; +} + + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter) +{ + return filter->nodename; +} All of this helper infrastructure doesn't belong to a patch that is declaring that it's dealing with the setup of qemu. (I also wrote that below as I've noticed it there).
It makes this patch too complex and thus hard to review. It's also the reason it takes me so long. I'm demotivated on such commits as it takes way too long to go through.
Any setup of nodenames and other libvirt-internal stuff, such as validatio of config etc should be separated.
+/** + * qemuBlockThrottleFilterGetProps: + * @filter: throttle filter + * @parentNodeName: parent nodename of @filter + * + * Build "arguments" part of "blockdev-add" QMP cmd. + * e.g. {"execute":"blockdev-add", "arguments":{"driver":"throttle", + * "node-name":"libvirt-2-filter", "throttle-group":"limits0", + * "file": "libvirt-1-format"}} Avoid this JSON in comments, as it's obvious from the code. Rather describe any special handling if needed.
+ */ +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName) +{ + g_autoptr(virJSONValue) props = NULL; + + if (virJSONValueObjectAdd(&props, + "s:driver", "throttle", + "s:node-name", qemuBlockThrottleFilterGetNodename(filter), + "s:throttle-group", filter->group_name, + "s:file", parentNodeName, + NULL) < 0) + return 0; return NULL; It looks confusing because 0 is success in our code.
+ + return g_steal_pointer(&props); +} [...]
+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); + data->filterAttached = true; Well, if you set this to true right here, it's pointless to even have the variable.
The code you've copied this from uses the 'Attached' variable so that it's set only after the object is created in qemu. This allows safe rollback.
If you set it to true for everything you will attempt to potentially roll back stuff that was not yet set in qemu, making the variable itself pointless.
+ + 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)); ... here ...
+ + 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); +} + + +int +qemuBlockThrottleFiltersAttach(qemuMonitor *mon, + qemuBlockThrottleFiltersData *data) +{ + size_t i; + + for (i = 0; i < data->nfilterdata; i++) { + if (qemuMonitorBlockdevAdd(mon, &data->filterdata[i]->filterProps) < 0) This function requires the monitor to be locked. If you want to have this as a separate function you must document it and state this fact.
+ 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 f9e961d85d..9888954ce4 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -214,6 +214,59 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, virStorageSource *src, virStorageSource *templ);
+void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename); + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter); + +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName); This function isn't used outside of qemu_block.c so it doesn't need to be exported.
+ +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 2d0eddc79e..5ccae956d3 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1582,6 +1582,13 @@ qemuDiskConfigBlkdeviotuneEnabled(const virDomainDiskDef *disk) }
+bool +qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk) This function doesn't need to be exported.
+{ + return disk->nthrottlefilters > 0; +} + + /** * qemuDiskBusIsSD: * @bus: disk bus @@ -11055,6 +11062,83 @@ 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, which is used to build "blockdev-add" QMP request the stuff after the comma can be dropped.
+ * + * 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; + g_autofree char *tmp_nodename = NULL; + + data = g_new0(qemuBlockThrottleFiltersData, 1); + /* 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++) { + tmp_nodename = g_strdup(parentNodeName); + if (qemuBuildThrottleFiltersAttachPrepareBlockdevOne(data, disk->throttlefilters[i], tmp_nodename) < 0) + return NULL; The nodename copied into 'tmp_nodename' is leaked on every iteration. Also qemuBuildThrottleFiltersAttachPrepareBlockdevOne doesn't modify it so there's no point in copying it.
+ 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]); So I didn't yet see any code that serializes any of this in the status XML, thus it seems that this will not work after you restart libvirtd/virtqemud if a VM with filters is running, and then try to detach disks. You'll need to add that, or base the filter nodename on something else.
Note that presence of the node name is authoritative and we must not try to regenerate it. That would hinder changing the node names in the future.
See how the copyOnRead layer node name is stored.
Filter node name is generated by rule "libvirt-nodenameindex-filter" in method "qemuDomainPrepareThrottleFilterBlockdev", which is called by "qemuDomainPrepareDiskSourceBlockdev". it follows the same way like "libvirt-nodenameindex-format" node. And I tested restarting libvirtd, vm with throttle works well in this case.
+ 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_domain.c b/src/qemu/qemu_domain.c index 7ba2ea4a5e..2831036e23 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -7989,7 +7989,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). @@ -8005,6 +8006,10 @@ qemuDomainDiskGetTopNodename(virDomainDiskDef *disk) if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) return priv->nodeCopyOnRead;
+ /* If disk has throttles, take top throttle node name */ + if (disk->nthrottlefilters > 0) + return disk->throttlefilters[disk->nthrottlefilters-1]->nodename;
return disk->throttlefilters[disk->nthrottlefilters - 1]->nodename;
(extra spaces around subtraction operator)
return qemuBlockStorageSourceGetEffectiveNodename(disk->src); }
@@ -11336,6 +11341,32 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, }
+int +qemuDomainPrepareThrottleFilterBlockdevNodename(virDomainThrottleFilterDef *filter,
This function is used only in this file so it doesn't need to be exported. Also it's used only in one place so it can be inlined as it makes the code harder to follow than necessary.
+ const char *nodenameprefix) +{ + char *nodename = g_strdup_printf("%s-filter", nodenameprefix); + + qemuBlockThrottleFilterSetNodename(filter, nodename); + + return 0; +} + + +int +qemuDomainPrepareThrottleFilterBlockdev(virDomainThrottleFilterDef *filter, + qemuDomainObjPrivate *priv) This function is used only in this module thus it doesn't need to be exported. Also it can't fail so there's no point in having a return value.
+{ + g_autofree char *nodenameprefix = NULL; + + filter->id = qemuDomainStorageIDNew(priv); + + nodenameprefix = g_strdup_printf("libvirt-%u", filter->id); nodename = g_strdup_printf("libvirt-%u-filter" ...
Instead of the convoluted mess and the below call.
+ + return qemuDomainPrepareThrottleFilterBlockdevNodename(filter, nodenameprefix); [...]
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f7e435d6d5..60989ae693 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14828,6 +14828,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; + } Preferrably add all this infrastructure which doesn't deal with the setup of qemu in a separate patch.
This patch is overly complex as it intermixes all of these validation bits with the actual qemu interaction.
+ return true; }
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 4a3f4f657e..103b9e9f00 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -657,6 +657,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; @@ -695,6 +696,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 above line.
+ */ + 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; + } The ordering is wrong. In the prepare step you are ordering the node name dependencies such as
device -> copyOnRead Layer -> throttle -> image chain
OBviously to ensure hierarchy at hotplug they need to be plugged from the end.
This block here is placed _AFTER_ copyOnRead layer instantiation, so that will not work with disks with copyOnRead.
After updating qemuBuildThrottleFiltersAttachPrepareBlockdev and qemuDomainDiskGetTopNodename, order can be adjusted to be "device -> throttle-> copyOnRead Layer-> image chain", and throttle works with copyOnRead, but I am also considering your suggestion about per-storage-source
+ if (disk->transient) { g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL; g_autoptr(GHashTable) blockNamedNodeData = NULL; @@ -766,6 +780,8 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (extensionDeviceAttached) ignore_value(qemuDomainDetachExtensionDevice(priv->mon, &disk->info));
+ qemuBlockThrottleFiltersDetach(priv->mon, filterData); + qemuBlockStorageSourceChainDetach(priv->mon, data);
qemuDomainObjExitMonitor(vm); @@ -921,6 +937,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")); So and now this is why I don't like the fact that you are doing this on a per-disk level. On a per-image level (if you'd instantiate 'throttle' layers when adding the image) this would not be a problem.
I'd strongly prefer if you modify this such that the trottle layers will be instantiated at per-storage-source level both in XML and in the code.
regarding per-storage-source, do you mean xml like below? if so, when specifying "--throttle-groups" in "attach-disk" it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source? <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk>
+ return -1; + } + } + if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("floppy device hotplug isn't supported")); @@ -4499,6 +4523,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; @@ -4537,6 +4562,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); In which case this'd be automatic.
+ } + qemuDomainObjExitMonitor(vm); + qemuDomainObjEnterMonitor(vm);
if (diskBackend) -- 2.34.1
This patch should also contain the corresponding commandline infrastructure for the filter itself.
The disk backend code is specifically unified by using the intermediate struct so that it's guaranteed that both hotplug and commandline work the same. Thus they need to be added simultaenously.
-- Thanks and Regards, Wu

On 2024/8/15 13:04, Chun Feng Wu wrote:
On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote:
From: Chun Feng Wu<wucf@linux.ibm.com>
When attaching disk along with specified throttle groups, those groups will be chained up by parent node name, this change includes service side codes: * Each filter references one throttle group by group name * Update "qemuDomainDiskGetTopNodename" to take top throttle node name if disk has throttles * Each filter has a nodename, and those filters are chained up in sequence * Filter nodename index is generated by reusing qemuDomainStorageIDNew and current global sequence number is persistented in virDomainObj->privateData(qemuDomainObjPrivate)->nodenameindex * During hotplug, filter is created through QMP request("blockdev-add" with "driver":"throttle") to QEMU * Delete filters by "qemuBlockThrottleFiltersDetach"("blockdev-del") when detaching device * Use "iotune" and "throttlefilters" exclusively for specific disk
Signed-off-by: Chun Feng Wu<wucf@linux.ibm.com> --- src/conf/domain_validate.c | 8 +++ src/qemu/qemu_block.c | 131 +++++++++++++++++++++++++++++++++++++ src/qemu/qemu_block.h | 53 +++++++++++++++ src/qemu/qemu_command.c | 84 ++++++++++++++++++++++++ src/qemu/qemu_command.h | 9 +++ src/qemu/qemu_domain.c | 39 ++++++++++- src/qemu/qemu_domain.h | 8 +++ src/qemu/qemu_driver.c | 6 ++ src/qemu/qemu_hotplug.c | 33 ++++++++++ 9 files changed, 370 insertions(+), 1 deletion(-)
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 395e036e8f..4cc5ed7577 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -942,6 +942,14 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; } + if (disk->throttlefilters && (disk->blkdeviotune.group_name || + virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) { This is hard to read as you've broken up the line within a nested brace. It may confuse readers.
Rewrite as:
if (disk->throttlefilters && (disk->blkdeviotune.group_name || virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) {
+ virReportError(VIR_ERR_XML_ERROR, Operation unsupported.
+ _("block 'throttlefilters' can't be used together with 'iotune' for disk '%1$s'"), + disk->dst); + return -1; + } + return 0; } diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 738b72d7ea..9b8ff65887 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -2739,6 +2739,137 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, } +void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename) +{ + g_free(filter->nodename); + filter->nodename = nodename; +} + + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter) +{ + return filter->nodename; +} All of this helper infrastructure doesn't belong to a patch that is declaring that it's dealing with the setup of qemu. (I also wrote that below as I've noticed it there).
It makes this patch too complex and thus hard to review. It's also the reason it takes me so long. I'm demotivated on such commits as it takes way too long to go through.
Any setup of nodenames and other libvirt-internal stuff, such as validatio of config etc should be separated.
+/** + * qemuBlockThrottleFilterGetProps: + * @filter: throttle filter + * @parentNodeName: parent nodename of @filter + * + * Build "arguments" part of "blockdev-add" QMP cmd. + * e.g. {"execute":"blockdev-add", "arguments":{"driver":"throttle", + * "node-name":"libvirt-2-filter", "throttle-group":"limits0", + * "file": "libvirt-1-format"}} Avoid this JSON in comments, as it's obvious from the code. Rather describe any special handling if needed.
+ */ +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName) +{ + g_autoptr(virJSONValue) props = NULL; + + if (virJSONValueObjectAdd(&props, + "s:driver", "throttle", + "s:node-name", qemuBlockThrottleFilterGetNodename(filter), + "s:throttle-group", filter->group_name, + "s:file", parentNodeName, + NULL) < 0) + return 0; return NULL; It looks confusing because 0 is success in our code.
+ + return g_steal_pointer(&props); +} [...]
+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); + data->filterAttached = true; Well, if you set this to true right here, it's pointless to even have the variable.
The code you've copied this from uses the 'Attached' variable so that it's set only after the object is created in qemu. This allows safe rollback.
If you set it to true for everything you will attempt to potentially roll back stuff that was not yet set in qemu, making the variable itself pointless.
+ + 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)); ... here ...
+ + 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); +} + + +int +qemuBlockThrottleFiltersAttach(qemuMonitor *mon, + qemuBlockThrottleFiltersData *data) +{ + size_t i; + + for (i = 0; i < data->nfilterdata; i++) { + if (qemuMonitorBlockdevAdd(mon, &data->filterdata[i]->filterProps) < 0) This function requires the monitor to be locked. If you want to have this as a separate function you must document it and state this fact.
+ 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 f9e961d85d..9888954ce4 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -214,6 +214,59 @@ qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData, virStorageSource *src, virStorageSource *templ); +void +qemuBlockThrottleFilterSetNodename(virDomainThrottleFilterDef *filter, + char *nodename); + +const char * +qemuBlockThrottleFilterGetNodename(virDomainThrottleFilterDef *filter); + +virJSONValue * +qemuBlockThrottleFilterGetProps(virDomainThrottleFilterDef *filter, + const char *parentNodeName); This function isn't used outside of qemu_block.c so it doesn't need to be exported.
+ +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 2d0eddc79e..5ccae956d3 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1582,6 +1582,13 @@ qemuDiskConfigBlkdeviotuneEnabled(const virDomainDiskDef *disk) } +bool +qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk) This function doesn't need to be exported.
+{ + return disk->nthrottlefilters > 0; +} + + /** * qemuDiskBusIsSD: * @bus: disk bus @@ -11055,6 +11062,83 @@ 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, which is used to build "blockdev-add" QMP request the stuff after the comma can be dropped.
+ * + * 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; + g_autofree char *tmp_nodename = NULL; + + data = g_new0(qemuBlockThrottleFiltersData, 1); + /* 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++) { + tmp_nodename = g_strdup(parentNodeName); + if (qemuBuildThrottleFiltersAttachPrepareBlockdevOne(data, disk->throttlefilters[i], tmp_nodename) < 0) + return NULL; The nodename copied into 'tmp_nodename' is leaked on every iteration. Also qemuBuildThrottleFiltersAttachPrepareBlockdevOne doesn't modify it so there's no point in copying it.
+ 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]); So I didn't yet see any code that serializes any of this in the status XML, thus it seems that this will not work after you restart libvirtd/virtqemud if a VM with filters is running, and then try to detach disks. You'll need to add that, or base the filter nodename on something else.
Note that presence of the node name is authoritative and we must not try to regenerate it. That would hinder changing the node names in the future.
See how the copyOnRead layer node name is stored.
Filter node name is generated by rule "libvirt-nodenameindex-filter" in method
"qemuDomainPrepareThrottleFilterBlockdev", which is called by
"qemuDomainPrepareDiskSourceBlockdev".
it follows the same way like "libvirt-nodenameindex-format" node.
And I tested restarting libvirtd, vm with throttle works well in this case.
+ 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_domain.c b/src/qemu/qemu_domain.c index 7ba2ea4a5e..2831036e23 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -7989,7 +7989,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). @@ -8005,6 +8006,10 @@ qemuDomainDiskGetTopNodename(virDomainDiskDef *disk) if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) return priv->nodeCopyOnRead; + /* If disk has throttles, take top throttle node name */ + if (disk->nthrottlefilters > 0) + return disk->throttlefilters[disk->nthrottlefilters-1]->nodename;
return disk->throttlefilters[disk->nthrottlefilters - 1]->nodename;
(extra spaces around subtraction operator)
return qemuBlockStorageSourceGetEffectiveNodename(disk->src); } @@ -11336,6 +11341,32 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef *disk, } +int +qemuDomainPrepareThrottleFilterBlockdevNodename(virDomainThrottleFilterDef *filter, This function is used only in this file so it doesn't need to be exported. Also it's used only in one place so it can be inlined as it makes the code harder to follow than necessary.
+ const char *nodenameprefix) +{ + char *nodename = g_strdup_printf("%s-filter", nodenameprefix); + + qemuBlockThrottleFilterSetNodename(filter, nodename); + + return 0; +} + + +int +qemuDomainPrepareThrottleFilterBlockdev(virDomainThrottleFilterDef *filter, + qemuDomainObjPrivate *priv) This function is used only in this module thus it doesn't need to be exported. Also it can't fail so there's no point in having a return value.
+{ + g_autofree char *nodenameprefix = NULL; + + filter->id = qemuDomainStorageIDNew(priv); + + nodenameprefix = g_strdup_printf("libvirt-%u", filter->id); nodename = g_strdup_printf("libvirt-%u-filter" ...
Instead of the convoluted mess and the below call.
+ + return qemuDomainPrepareThrottleFilterBlockdevNodename(filter, nodenameprefix); [...]
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f7e435d6d5..60989ae693 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14828,6 +14828,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; + } Preferrably add all this infrastructure which doesn't deal with the setup of qemu in a separate patch.
This patch is overly complex as it intermixes all of these validation bits with the actual qemu interaction.
+ return true; } diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 4a3f4f657e..103b9e9f00 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -657,6 +657,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; @@ -695,6 +696,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 above line.
+ */ + 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; + } The ordering is wrong. In the prepare step you are ordering the node name dependencies such as
device -> copyOnRead Layer -> throttle -> image chain
OBviously to ensure hierarchy at hotplug they need to be plugged from the end.
This block here is placed _AFTER_ copyOnRead layer instantiation, so that will not work with disks with copyOnRead.
After updating qemuBuildThrottleFiltersAttachPrepareBlockdev and qemuDomainDiskGetTopNodename,
order can be adjusted to be "device -> throttle-> copyOnRead Layer-> image chain", and throttle
works with copyOnRead, but I am also considering your suggestion about per-storage-source
+ if (disk->transient) { g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL; g_autoptr(GHashTable) blockNamedNodeData = NULL; @@ -766,6 +780,8 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm, if (extensionDeviceAttached) ignore_value(qemuDomainDetachExtensionDevice(priv->mon, &disk->info)); + qemuBlockThrottleFiltersDetach(priv->mon, filterData); + qemuBlockStorageSourceChainDetach(priv->mon, data); qemuDomainObjExitMonitor(vm); @@ -921,6 +937,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")); So and now this is why I don't like the fact that you are doing this on a per-disk level. On a per-image level (if you'd instantiate 'throttle' layers when adding the image) this would not be a problem.
I'd strongly prefer if you modify this such that the trottle layers will be instantiated at per-storage-source level both in XML and in the code.
regarding per-storage-source, do you mean xml like below? if so, when specifying "--throttle-groups" in "attach-disk"
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk>
and regarding code part for per-storage-source, do you mean preparing throttle filter chain json within "qemuBuildStorageSourceChainAttachPrepareBlockdev(virStorageSource *top)"? if so, current "qemuBuildStorageSourceChainAttachPrepareBlockdev" doesn't include copy-on-read layer, however, throttlefilter chain depends on it for top source, in that case, should preparation of copy-on-read be moved into "qemuBuildStorageSourceChainAttachPrepareBlockdev" as well? if yes, current parameter "virStorageSource *top" is not enough, also, do you see any concern about updating "qemuBuildStorageSourceChainAttachPrepareBlockdev" for throttle chain, it seems it has a lot of callers
+ return -1; + } + } + if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("floppy device hotplug isn't supported")); @@ -4499,6 +4523,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; @@ -4537,6 +4562,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); In which case this'd be automatic.
+ } + qemuDomainObjExitMonitor(vm); + qemuDomainObjEnterMonitor(vm); if (diskBackend) -- 2.34.1
This patch should also contain the corresponding commandline infrastructure for the filter itself.
The disk backend code is specifically unified by using the intermediate struct so that it's guaranteed that both hotplug and commandline work the same. Thus they need to be added simultaenously.
-- Thanks and Regards,
Wu

On Fri, Aug 16, 2024 at 11:51:17 +0800, Chun Feng Wu wrote:
On 2024/8/15 13:04, Chun Feng Wu wrote:
On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote:
From: Chun Feng Wu<wucf@linux.ibm.com>
[...]
I'd strongly prefer if you modify this such that the trottle layers will be instantiated at per-storage-source level both in XML and in the code.
regarding per-storage-source, do you mean xml like below? if so, when specifying "--throttle-groups" in "attach-disk"
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk> and regarding code part for per-storage-source, do you mean preparing throttle filter chain json within "qemuBuildStorageSourceChainAttachPrepareBlockdev(virStorageSource *top)"? if so, current "qemuBuildStorageSourceChainAttachPrepareBlockdev" doesn't include copy-on-read layer, however, throttlefilter chain depends on it for
Yeah, this would necessarily mean that copy-on-read is on top of the throttle layer.
top source, in that case, should preparation of copy-on-read be moved into "qemuBuildStorageSourceChainAttachPrepareBlockdev" as well? if yes, current
No. There's no sense to have multiple copy-on-read layers.
parameter "virStorageSource *top" is not enough, also, do you see any concern about updating "qemuBuildStorageSourceChainAttachPrepareBlockdev" for throttle chain, it seems it has a lot of callers
As I've said in previous reply, having it on per-node level may be overkill. If you don't ever forsee to use this differently then maybe let's stick to the per-disk config, by just keeping proper ordering and remembering the node names.

On Thu, Aug 15, 2024 at 13:04:47 +0800, Chun Feng Wu wrote: (I'd suggest you trim irrelevant stuff when responding. It's hard to find what you've responded to in this massive message .
On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote:
From: Chun Feng Wu<wucf@linux.ibm.com>
[...]
+ * 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]); So I didn't yet see any code that serializes any of this in the status XML, thus it seems that this will not work after you restart libvirtd/virtqemud if a VM with filters is running, and then try to detach disks. You'll need to add that, or base the filter nodename on something else.
Note that presence of the node name is authoritative and we must not try to regenerate it. That would hinder changing the node names in the future.
See how the copyOnRead layer node name is stored.
Filter node name is generated by rule "libvirt-nodenameindex-filter" in method
"qemuDomainPrepareThrottleFilterBlockdev", which is called by
"qemuDomainPrepareDiskSourceBlockdev".
it follows the same way like "libvirt-nodenameindex-format" node.
And I tested restarting libvirtd, vm with throttle works well in this case.
The problem is that the actual error shows only in logs as the detaching of the backend-nodes is on a code path that can't meaningfully report errors. Did you check in the logs that: - the removal of the throttling node was actually requested - that it did have a proper node name That is on hot-unplug of the disk having such config after you've restarted libvirt. The code for other disks always stores the nodenames as-generated in the status XML (XML describing a running VM) which I don't see in this series thus I infer it doesn't/can't work. [...]
@@ -921,6 +937,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")); So and now this is why I don't like the fact that you are doing this on a per-disk level. On a per-image level (if you'd instantiate 'throttle' layers when adding the image) this would not be a problem.
I'd strongly prefer if you modify this such that the trottle layers will be instantiated at per-storage-source level both in XML and in the code.
regarding per-storage-source, do you mean xml like below? if so, when specifying "--throttle-groups" in "attach-disk"
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk>
Yeah, something like this. That allows adding filters on other layers too. As I said it depends on how you expect to use this feature, because both make sense. I do reckon that in most cases this is an overkill though. If you decide that this is too complicated, you can use the approach you did, but then need to store the nodename of the throttle layer on the disk level in the status XML.

On 2024/8/16 23:14, Peter Krempa wrote:
On Thu, Aug 15, 2024 at 13:04:47 +0800, Chun Feng Wu wrote:
(I'd suggest you trim irrelevant stuff when responding. It's hard to find what you've responded to in this massive message .
On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote:
From: Chun Feng Wu<wucf@linux.ibm.com> [...]
+ * 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]); So I didn't yet see any code that serializes any of this in the status XML, thus it seems that this will not work after you restart libvirtd/virtqemud if a VM with filters is running, and then try to detach disks. You'll need to add that, or base the filter nodename on something else.
Note that presence of the node name is authoritative and we must not try to regenerate it. That would hinder changing the node names in the future.
See how the copyOnRead layer node name is stored. Filter node name is generated by rule "libvirt-nodenameindex-filter" in method
"qemuDomainPrepareThrottleFilterBlockdev", which is called by
"qemuDomainPrepareDiskSourceBlockdev".
it follows the same way like "libvirt-nodenameindex-format" node.
And I tested restarting libvirtd, vm with throttle works well in this case. The problem is that the actual error shows only in logs as the detaching of the backend-nodes is on a code path that can't meaningfully report errors. Did you check in the logs that:
- the removal of the throttling node was actually requested - that it did have a proper node name
That is on hot-unplug of the disk having such config after you've restarted libvirt.
The code for other disks always stores the nodenames as-generated in the status XML (XML describing a running VM) which I don't see in this series thus I infer it doesn't/can't work.
[...]
@@ -921,6 +937,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")); So and now this is why I don't like the fact that you are doing this on a per-disk level. On a per-image level (if you'd instantiate 'throttle' layers when adding the image) this would not be a problem.
I'd strongly prefer if you modify this such that the trottle layers will be instantiated at per-storage-source level both in XML and in the code. regarding per-storage-source, do you mean xml like below? if so, when specifying "--throttle-groups" in "attach-disk"
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk>
Yeah, something like this. That allows adding filters on other layers too.
As I said it depends on how you expect to use this feature, because both make sense.
I do reckon that in most cases this is an overkill though.
If you decide that this is too complicated, you can use the approach you did, but then need to store the nodename of the throttle layer on the disk level in the status XML.
sure, then let's stick to current implementation about filter chain(per-disk), about status XML, do you mean <privateData> to store nodename? if so, does the following xml look good to you? <throttlefilters> <throttlefilter group='limit2'> <privateData> <nodename type='throttle-filter' name='0123456789ABCDEF0123456789ABCDE'/> </privateData> </throttlefilter> </throttlefilters> -- Thanks and Regards, Wu

On Mon, Aug 19, 2024 at 18:27:46 +0800, Chun Feng Wu wrote:
On 2024/8/16 23:14, Peter Krempa wrote:
On Thu, Aug 15, 2024 at 13:04:47 +0800, Chun Feng Wu wrote:
(I'd suggest you trim irrelevant stuff when responding. It's hard to find what you've responded to in this massive message .
On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote:
From: Chun Feng Wu<wucf@linux.ibm.com>
[...]
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk>
Yeah, something like this. That allows adding filters on other layers too.
As I said it depends on how you expect to use this feature, because both make sense.
I do reckon that in most cases this is an overkill though.
If you decide that this is too complicated, you can use the approach you did, but then need to store the nodename of the throttle layer on the disk level in the status XML.
sure, then let's stick to current implementation about filter chain(per-disk), about status XML, do you mean <privateData> to store nodename? if so, does the following xml look good to you?
<throttlefilters> <throttlefilter group='limit2'> <privateData> <nodename type='throttle-filter' name='0123456789ABCDEF0123456789ABCDE'/> </privateData> </throttlefilter> </throttlefilters>
I suggest you don't add the privateData sub-element to the 'throttlefilters' as it will be very hard to plumb that in. You'd require private data callbacks which the XML parser (generic) calls from the qemu driver (specific) to parse this. I'd rather suggest you use the disk private data section and format them there. Use the group name as a key to find them as that is (or at least should be) unique.

On 2024/8/19 18:59, Peter Krempa wrote:
On Mon, Aug 19, 2024 at 18:27:46 +0800, Chun Feng Wu wrote:
On 2024/8/16 23:14, Peter Krempa wrote:
On Thu, Aug 15, 2024 at 13:04:47 +0800, Chun Feng Wu wrote:
(I'd suggest you trim irrelevant stuff when responding. It's hard to find what you've responded to in this massive message .
On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote:
From: Chun Feng Wu<wucf@linux.ibm.com> [...]
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk> Yeah, something like this. That allows adding filters on other layers too.
As I said it depends on how you expect to use this feature, because both make sense.
I do reckon that in most cases this is an overkill though.
If you decide that this is too complicated, you can use the approach you did, but then need to store the nodename of the throttle layer on the disk level in the status XML. sure, then let's stick to current implementation about filter chain(per-disk), about status XML, do you mean <privateData> to store nodename? if so, does the following xml look good to you?
<throttlefilters> <throttlefilter group='limit2'> <privateData> <nodename type='throttle-filter' name='0123456789ABCDEF0123456789ABCDE'/> </privateData> </throttlefilter> </throttlefilters> I suggest you don't add the privateData sub-element to the 'throttlefilters' as it will be very hard to plumb that in. You'd require private data callbacks which the XML parser (generic) calls from the qemu driver (specific) to parse this.
I'd rather suggest you use the disk private data section and format them there. Use the group name as a key to find them as that is (or at least should be) unique.
if so, it could be the following xml: <disk> <privateData> <nodenames> <nodename type='throttle-filter' name='libvirt-nodenameindex-filter' key='group_name'/> </nodenames> </privateData> </disk> and struct _qemuDomainDiskPrivate { ... GHashTable *throttleFilters; ... } any suggestion? -- Thanks and Regards, Wu

On Tue, Aug 20, 2024 at 22:48:53 +0800, Chun Feng Wu wrote:
On 2024/8/19 18:59, Peter Krempa wrote:
On Mon, Aug 19, 2024 at 18:27:46 +0800, Chun Feng Wu wrote:
On 2024/8/16 23:14, Peter Krempa wrote:
On Thu, Aug 15, 2024 at 13:04:47 +0800, Chun Feng Wu wrote:
(I'd suggest you trim irrelevant stuff when responding. It's hard to find what you've responded to in this massive message .
On 2024/8/9 22:04, Peter Krempa wrote:
On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote: > From: Chun Feng Wu<wucf@linux.ibm.com> [...]
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk> Yeah, something like this. That allows adding filters on other layers too.
As I said it depends on how you expect to use this feature, because both make sense.
I do reckon that in most cases this is an overkill though.
If you decide that this is too complicated, you can use the approach you did, but then need to store the nodename of the throttle layer on the disk level in the status XML. sure, then let's stick to current implementation about filter chain(per-disk), about status XML, do you mean <privateData> to store nodename? if so, does the following xml look good to you?
<throttlefilters> <throttlefilter group='limit2'> <privateData> <nodename type='throttle-filter' name='0123456789ABCDEF0123456789ABCDE'/> </privateData> </throttlefilter> </throttlefilters> I suggest you don't add the privateData sub-element to the 'throttlefilters' as it will be very hard to plumb that in. You'd require private data callbacks which the XML parser (generic) calls from the qemu driver (specific) to parse this.
I'd rather suggest you use the disk private data section and format them there. Use the group name as a key to find them as that is (or at least should be) unique.
if so, it could be the following xml:
<disk> <privateData> <nodenames> <nodename type='throttle-filter' name='libvirt-nodenameindex-filter' key='group_name'/> </nodenames> </privateData> </disk>
Yes. Although I'd name the 'key' field 'group' instead.
and
struct _qemuDomainDiskPrivate { ... GHashTable *throttleFilters; ... }
This shouldn't be needed. You can store the nodenames in the same data structure you do now. The formatter will format it from the data structure and parser will fill it into the appropriate fields based on the group name. Yes it's O(N2)-ish complexity but N is small, so having a hash table doesn't make sense. In fact you don't want a hash table because you need the filters ordered in the order defined in the XML and a hash table would re-order them randomly every time.

On 2024/8/20 23:08, Peter Krempa wrote:
On Tue, Aug 20, 2024 at 22:48:53 +0800, Chun Feng Wu wrote:
On 2024/8/19 18:59, Peter Krempa wrote:
On Mon, Aug 19, 2024 at 18:27:46 +0800, Chun Feng Wu wrote:
On 2024/8/16 23:14, Peter Krempa wrote:
On Thu, Aug 15, 2024 at 13:04:47 +0800, Chun Feng Wu wrote:
(I'd suggest you trim irrelevant stuff when responding. It's hard to find what you've responded to in this massive message .
On 2024/8/9 22:04, Peter Krempa wrote: > On Wed, Jun 12, 2024 at 03:02:17 -0700,wucf@linux.ibm.com wrote: >> From: Chun Feng Wu<wucf@linux.ibm.com> [...]
it apply groups on top source(vm1_disk_2.qcow2) only? is there scenario to apply throttle-groups on backing store source?
<disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/virt/disks/vm1_disk_2.qcow2' index='1'> <throttlefilters> <throttlefilter group='limit0'/> </throttlefilters> </source> <backingStore type='file' index='4'> <format type='qcow2'/> <source file='/virt/disks/backing.qcow2'> <throttlefilters> <throttlefilter group='limit1'/> </throttlefilters> </source> <backingStore/> </backingStore> <target dev='vdc' bus='virtio'/> <alias name='virtio-disk2'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> </disk> Yeah, something like this. That allows adding filters on other layers too.
As I said it depends on how you expect to use this feature, because both make sense.
I do reckon that in most cases this is an overkill though.
If you decide that this is too complicated, you can use the approach you did, but then need to store the nodename of the throttle layer on the disk level in the status XML. sure, then let's stick to current implementation about filter chain(per-disk), about status XML, do you mean <privateData> to store nodename? if so, does the following xml look good to you?
<throttlefilters> <throttlefilter group='limit2'> <privateData> <nodename type='throttle-filter' name='0123456789ABCDEF0123456789ABCDE'/> </privateData> </throttlefilter> </throttlefilters> I suggest you don't add the privateData sub-element to the 'throttlefilters' as it will be very hard to plumb that in. You'd require private data callbacks which the XML parser (generic) calls from the qemu driver (specific) to parse this.
I'd rather suggest you use the disk private data section and format them there. Use the group name as a key to find them as that is (or at least should be) unique.
if so, it could be the following xml:
<disk> <privateData> <nodenames> <nodename type='throttle-filter' name='libvirt-nodenameindex-filter' key='group_name'/> </nodenames> </privateData> </disk> Yes. Although I'd name the 'key' field 'group' instead.
and
struct _qemuDomainDiskPrivate { ... GHashTable *throttleFilters; ... } This shouldn't be needed. You can store the nodenames in the same data structure you do now. The formatter will format it from the data structure and parser will fill it into the appropriate fields based on the group name.
Yes it's O(N2)-ish complexity but N is small, so having a hash table doesn't make sense.
In fact you don't want a hash table because you need the filters ordered in the order defined in the XML and a hash table would re-order them randomly every time. Thanks for your suggestion! it makes more sense, I am drafting this part in v4
-- Thanks and Regards, Wu

From: Chun Feng Wu <wucf@linux.ibm.com> * Disk iotune validation can be reused for throttle group validation, refactor it into common method "virDomainDiskIoTuneValidate" Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 78 +++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 4cc5ed7577..d724046004 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -658,6 +658,49 @@ 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) @@ -712,41 +755,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")); - 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")); + if (virDomainDiskIoTuneValidate(disk->blkdeviotune) < 0) 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 -- 2.34.1

On Wed, Jun 12, 2024 at 03:02:18 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* Disk iotune validation can be reused for throttle group validation, refactor it into common method "virDomainDiskIoTuneValidate"
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 78 +++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 34 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

From: Chun Feng Wu <wucf@linux.ibm.com> * 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 Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 20 +++++++++++++ src/qemu/qemu_command.c | 58 ++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_command.h | 3 ++ 3 files changed, 81 insertions(+) diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index d724046004..27d7a9968b 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -1818,6 +1818,23 @@ virDomainDefValidateIOThreads(const virDomainDef *def) } +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) @@ -1873,6 +1890,9 @@ virDomainDefValidateInternal(const virDomainDef *def, if (virDomainDefValidateIOThreads(def) < 0) return -1; + if (virDomainDefValidateThrottleGroups(def) < 0) + return -1; + return 0; } diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 5ccae956d3..863544938f 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1589,6 +1589,14 @@ qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk) } +bool +qemuDiskConfigThrottleFilterEnabled(const virDomainThrottleGroupDef *group) +{ + return !!group->group_name && + virDomainBlockIoTuneInfoHasAny(group); +} + + /** * qemuDiskBusIsSD: * @bus: disk bus @@ -7475,6 +7483,53 @@ qemuBuildIOThreadCommandLine(virCommand *cmd, } +/** + * qemuBuildThrottleGroupCommandLine: + * @cmd: the command to modify + * @def: domain definition + * @qemuCaps: qemu capabilities object + * + * build throttle group object in json format + * e.g. -object '{"qom-type":"throttle-group","id":"limit0","limits":{"iops-total":200}}' + */ +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]; + + if (!qemuDiskConfigThrottleFilterEnabled(group)) { + continue; + } + + if (qemuMonitorThrottleGroupLimits(limits, group)<0) + return -1; + + if (qemuMonitorCreateObjectProps(&props, "throttle-group", group->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_INTERNAL_ERROR, "%s", + _("QEMU_CAPS_OBJECT_JSON support is required for throttle group creation")); + return -1; + } + if (qemuBuildObjectCommandlineFromJSON(cmd, props, qemuCaps) < 0) + return -1; + } + + return 0; +} + + static int qemuBuildNumaCellCache(virCommand *cmd, const virDomainDef *def, @@ -10509,6 +10564,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_command.h b/src/qemu/qemu_command.h index ce39acfb2c..ddc7a45fd9 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -125,6 +125,9 @@ qemuBuildThrottleFiltersDetachPrepareBlockdev(virDomainDiskDef *disk); bool qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk); +bool +qemuDiskConfigThrottleFilterEnabled(const virDomainThrottleGroupDef *group); + virJSONValue * qemuBuildDiskDeviceProps(const virDomainDef *def, virDomainDiskDef *disk, -- 2.34.1

On Wed, Jun 12, 2024 at 03:02:19 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* 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
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 20 +++++++++++++ src/qemu/qemu_command.c | 58 ++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_command.h | 3 ++ 3 files changed, 81 insertions(+)
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index d724046004..27d7a9968b 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -1818,6 +1818,23 @@ virDomainDefValidateIOThreads(const virDomainDef *def) }
+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;
... this is the validation step ... (see below). This valdiation should be also added all together with all the other validation bits that I've seen in other patches and not split randomly.
+ } + + return 0; +} + + static int virDomainDefValidateInternal(const virDomainDef *def, virDomainXMLOption *xmlopt) @@ -1873,6 +1890,9 @@ virDomainDefValidateInternal(const virDomainDef *def, if (virDomainDefValidateIOThreads(def) < 0) return -1;
+ if (virDomainDefValidateThrottleGroups(def) < 0) + return -1; + return 0; }
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 5ccae956d3..863544938f 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1589,6 +1589,14 @@ qemuDiskConfigThrottleFiltersEnabled(const virDomainDiskDef *disk) }
+bool +qemuDiskConfigThrottleFilterEnabled(const virDomainThrottleGroupDef *group)
Not used anywhere else, avoid export.
+{ + return !!group->group_name && + virDomainBlockIoTuneInfoHasAny(group); +} + + /** * qemuDiskBusIsSD: * @bus: disk bus @@ -7475,6 +7483,53 @@ qemuBuildIOThreadCommandLine(virCommand *cmd, }
+/** + * qemuBuildThrottleGroupCommandLine: + * @cmd: the command to modify + * @def: domain definition + * @qemuCaps: qemu capabilities object + * + * build throttle group object in json format + * e.g. -object '{"qom-type":"throttle-group","id":"limit0","limits":{"iops-total":200}}' + */ +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]; + + if (!qemuDiskConfigThrottleFilterEnabled(group)) { + continue; + } + + if (qemuMonitorThrottleGroupLimits(limits, group)<0)
coding style.
+ return -1; + + if (qemuMonitorCreateObjectProps(&props, "throttle-group", group->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_INTERNAL_ERROR, "%s", + _("QEMU_CAPS_OBJECT_JSON support is required for throttle group creation")); + return -1;
This belongs to the validation step and not to the command line building step. Also same comment as before about not using internal error and internal flag names, which are meaningless to the users.
+ } + if (qemuBuildObjectCommandlineFromJSON(cmd, props, qemuCaps) < 0) + return -1; + } + + return 0; +} + + static int qemuBuildNumaCellCache(virCommand *cmd,
[...]

From: Chun Feng Wu <wucf@linux.ibm.com> * Add qemuBuildDiskThrottleFiltersCommandLine in qemuBuildDiskCommandLine to add "blockdev" * Make sure referenced throttle group exists Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 12 ++++++++++++ src/qemu/qemu_command.c | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 27d7a9968b..bedc28d481 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -706,6 +706,7 @@ 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) { @@ -952,6 +953,17 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; } + /* referenced group should be defined */ + for (i = 0; i < disk->nthrottlefilters; i++) { + virDomainThrottleFilterDef *filter = disk->throttlefilters[i]; + if (!virDomainThrottleGroupFind(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_XML_ERROR, diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 863544938f..2194b15fd3 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -2229,6 +2229,43 @@ qemuBuildBlockStorageSourceAttachDataCommandline(virCommand *cmd, } +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, @@ -2286,6 +2323,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)) -- 2.34.1

On Wed, Jun 12, 2024 at 03:02:20 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* Add qemuBuildDiskThrottleFiltersCommandLine in qemuBuildDiskCommandLine to add "blockdev" * Make sure referenced throttle group exists
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- src/conf/domain_validate.c | 12 ++++++++++++ src/qemu/qemu_command.c | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+)
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 27d7a9968b..bedc28d481 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -706,6 +706,7 @@ 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) { @@ -952,6 +953,17 @@ virDomainDiskDefValidate(const virDomainDef *def, return -1; }
+ /* referenced group should be defined */ + for (i = 0; i < disk->nthrottlefilters; i++) { + virDomainThrottleFilterDef *filter = disk->throttlefilters[i]; + if (!virDomainThrottleGroupFind(def, filter->group_name)) { + virReportError(VIR_ERR_XML_ERROR, + _("throttle group '%1$s' not found"), + filter->group_name); + return -1; + } + }
As noted, please unify all the random bits of validation into single patch.
+ if (disk->throttlefilters && (disk->blkdeviotune.group_name || virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))) { virReportError(VIR_ERR_XML_ERROR, diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 863544938f..2194b15fd3 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -2229,6 +2229,43 @@ qemuBuildBlockStorageSourceAttachDataCommandline(virCommand *cmd, }
+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, @@ -2286,6 +2323,9 @@ qemuBuildDiskCommandLine(virCommand *cmd, if (qemuBuildDiskSourceCommandLine(cmd, disk, qemuCaps) < 0) return -1;
+ if (qemuBuildDiskThrottleFiltersCommandLine(cmd, disk) < 0) + return -1;
As noted this should be done all at once in one patch. Also as noted I strongly prefer if this is done along with the images and not here. Also as noted before this is ordered wrong for how this is supposed to work as you propose as here the 'copyOnRead' layer is already built.
+ /* SD cards are currently instantiated via -drive if=sd, so the -device * part must be skipped */ if (qemuDiskBusIsSD(disk->bus)) -- 2.34.1

From: Chun Feng Wu <wucf@linux.ibm.com> * Add tests for "throttlegroup" * Add tests for "throttlefilter" Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- .../throttlefilter.x86_64-latest.args | 43 ++++++++++++ .../throttlefilter.x86_64-latest.xml | 65 +++++++++++++++++++ tests/qemuxmlconfdata/throttlefilter.xml | 55 ++++++++++++++++ tests/qemuxmlconftest.c | 1 + 4 files changed, 164 insertions(+) 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/qemuxmlconfdata/throttlefilter.x86_64-latest.args b/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args new file mode 100644 index 0000000000..ed3f0145df --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.args @@ -0,0 +1,43 @@ +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":"limit0","limits":{"bps-total":5000,"iops-total":6000}}' \ +-object '{"qom-type":"throttle-group","id":"limit1","limits":{"bps-read":5000,"bps-write":5000,"iops-total":7000}}' \ +-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-3-storage","auto-read-only":true,"discard":"unmap","cache":{"direct":true,"no-flush":false}}' \ +-blockdev '{"node-name":"libvirt-3-format","read-only":false,"cache":{"direct":true,"no-flush":false},"driver":"qcow2","file":"libvirt-3-storage"}' \ +-blockdev '{"driver":"throttle","node-name":"libvirt-4-filter","throttle-group":"limit0","file":"libvirt-3-format"}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-4-filter","id":"ide0-0-0","bootindex":1,"write-cache":"on"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest2","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"}' \ +-blockdev '{"driver":"throttle","node-name":"libvirt-2-filter","throttle-group":"limit1","file":"libvirt-1-format"}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":1,"drive":"libvirt-2-filter","id":"ide0-0-1","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..c4c9602ff2 --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter.x86_64-latest.xml @@ -0,0 +1,65 @@ +<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>5000</total_bytes_sec> + <total_iops_sec>6000</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>7000</total_iops_sec> + <group_name>limit1</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='hda' bus='ide'/> + <throttlefilters> + <throttlefilter group='limit0'/> + </throttlefilters> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='hdb' bus='ide'/> + <throttlefilters> + <throttlefilter group='limit1'/> + </throttlefilters> + <address type='drive' controller='0' bus='0' target='0' unit='1'/> + </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..10b7d1c01b --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter.xml @@ -0,0 +1,55 @@ +<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>5000</total_bytes_sec> + <total_iops_sec>6000</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>7000</total_iops_sec> + <group_name>limit1</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='hda' bus='ide'/> + <throttlefilters> + <throttlefilter group='limit0'/> + </throttlefilters> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='hdb' bus='ide'/> + <throttlefilters> + <throttlefilter group='limit1'/> + </throttlefilters> + <address type='drive' controller='0' bus='0' target='0' unit='1'/> + </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 d2f62081b7..7ac83b3fd9 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -2324,6 +2324,7 @@ 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("multifunction-pci-device"); -- 2.34.1

On Wed, Jun 12, 2024 at 03:02:21 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* Add tests for "throttlegroup" * Add tests for "throttlefilter"
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- .../throttlefilter.x86_64-latest.args | 43 ++++++++++++ .../throttlefilter.x86_64-latest.xml | 65 +++++++++++++++++++ tests/qemuxmlconfdata/throttlefilter.xml | 55 ++++++++++++++++ tests/qemuxmlconftest.c | 1 + 4 files changed, 164 insertions(+) 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/qemuxmlconfdata/throttlefilter.xml b/tests/qemuxmlconfdata/throttlefilter.xml new file mode 100644 index 0000000000..10b7d1c01b --- /dev/null +++ b/tests/qemuxmlconfdata/throttlefilter.xml @@ -0,0 +1,55 @@ +<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>5000</total_bytes_sec> + <total_iops_sec>6000</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>7000</total_iops_sec> + <group_name>limit1</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='hda' bus='ide'/> + <throttlefilters> + <throttlefilter group='limit0'/> + </throttlefilters> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='hdb' bus='ide'/> + <throttlefilters> + <throttlefilter group='limit1'/>
Please add also one more complex example of a disk referencing multiple groups. Otherwise this'd be identical to what we already support. For completness you can also add one group that is not used by any disk.
+ </throttlefilters> + <address type='drive' controller='0' bus='0' target='0' unit='1'/> + </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>
I also expect at least one negative test case showing how the mixing of throttling via groups and directly you explicitly forbid is handled so that we don't regress in that regard.

From: Chun Feng Wu <wucf@linux.ibm.com> * 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 <wucf@linux.ibm.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 d2d1bc43e3..e9de033cb0 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -3782,25 +3782,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, @@ -3845,34 +3828,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; \ } @@ -3883,119 +3861,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); @@ -4008,6 +3966,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; @@ -4118,6 +4139,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 @@ -10516,6 +10721,9 @@ static virHypervisorDriver testHypervisorDriver = { .domainGetInterfaceParameters = testDomainGetInterfaceParameters, /* 5.6.0 */ .domainSetBlockIoTune = testDomainSetBlockIoTune, /* 5.7.0 */ .domainGetBlockIoTune = testDomainGetBlockIoTune, /* 5.7.0 */ + .domainSetThrottleGroup = testDomainSetThrottleGroup, /* 10.5.0 */ + .domainGetThrottleGroup = testDomainGetThrottleGroup, /* 10.5.0 */ + .domainDelThrottleGroup = testDomainDelThrottleGroup, /* 10.5.0 */ .domainSetBlkioParameters = testDomainSetBlkioParameters, /* 7.7.0 */ .domainGetBlkioParameters = testDomainGetBlkioParameters, /* 7.7.0 */ .connectListDefinedDomains = testConnectListDefinedDomains, /* 0.1.11 */ -- 2.34.1

From: Chun Feng Wu <wucf@linux.ibm.com> * Add new cmds: throttlegroupset, throttlegrouplist, throttlegroupinfo, throttlegroupdel Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- tools/virsh-domain.c | 428 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 50e80689a2..f84a65451a 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1509,6 +1509,410 @@ 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; + 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; + + ngroups = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, &groups); + if (ngroups < 0) + return false; + + table = vshTableNew(_("Name"), NULL); + + if (!table) + return false; + + for (i = 0; i < ngroups; i++) { + g_autofree char *name = NULL; + ctxt->node = groups[i]; + name = virXPathString("string(./group_name)", ctxt); + if (vshTableRowAppend(table, name, 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 = virshCompleteEmpty, + .help = N_("throttle group name") + }, + {.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_INT, + .help = N_("read throughput limit, as scaled integer (default bytes)") + }, + {.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_INT, + .help = N_("total I/O operations limit per second") + }, + {.name = "read-iops-sec", + .type = VSH_OT_INT, + .help = N_("read I/O operations limit per second") + }, + {.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_INT, + .help = N_("total max, as scaled integer (default bytes)") + }, + {.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_INT, + .help = N_("write max, as scaled integer (default bytes)") + }, + {.name = "total-iops-sec-max", + .type = VSH_OT_INT, + .help = N_("total I/O operations 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_INT, + .help = N_("write I/O operations max") + }, + {.name = "size-iops-sec", + .type = VSH_OT_INT, + .help = N_("I/O size in bytes") + }, + {.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_INT, + .help = N_("duration in seconds to allow read max bytes") + }, + {.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_INT, + .help = N_("duration in seconds to allow total I/O operations max") + }, + {.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_INT, + .help = N_("duration in seconds to allow write I/O operations max") + }, + VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupSet(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *name; + 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, &name))) + 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 change 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 = virshCompleteEmpty, + .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 = virshCompleteEmpty, + .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 *name; + 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, &name))) + 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 */ @@ -13378,6 +13782,30 @@ const vshCmdDef domManagementCmds[] = { .info = &info_blkdeviotune, .flags = 0 }, + {.name = "throttlegroupset", + .handler = cmdThrottleGroupSet, + .opts = opts_throttlegroupset, + .info = &info_throttlegroupset, + .flags = 0 + }, + {.name = "throttlegroupdel", + .handler = cmdThrottleGroupDel, + .opts = opts_throttlegroupdel, + .info = &info_throttlegroupdel, + .flags = 0 + }, + {.name = "throttlegroupinfo", + .handler = cmdThrottleGroupInfo, + .opts = opts_throttlegroupinfo, + .info = &info_throttlegroupinfo, + .flags = 0 + }, + {.name = "throttlegrouplist", + .handler = cmdThrottleGroupList, + .opts = opts_throttlegrouplist, + .info = &info_throttlegrouplist, + .flags = 0 + }, {.name = "blkiotune", .handler = cmdBlkiotune, .opts = opts_blkiotune, -- 2.34.1

On Wed, Jun 12, 2024 at 03:02:23 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.com>
* Add new cmds: throttlegroupset, throttlegrouplist, throttlegroupinfo, throttlegroupdel
Signed-off-by: Chun Feng Wu <wucf@linux.ibm.com> --- tools/virsh-domain.c | 428 +++++++++++++++++++++++++++++++++++++++++++
This patch is missing addition to the manpage documenting the commands in docs/manpages/virsh.rst.
1 file changed, 428 insertions(+)
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 50e80689a2..f84a65451a 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1509,6 +1509,410 @@ 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; + 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; + + ngroups = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, &groups); + if (ngroups < 0)
You need to report an error here, but on the other hand I don't think that having 0 groups is an error.
+ return false; + + table = vshTableNew(_("Name"), NULL); + + if (!table) + return false; + + for (i = 0; i < ngroups; i++) { + g_autofree char *name = NULL; + ctxt->node = groups[i]; + name = virXPathString("string(./group_name)", ctxt);
Here you don't handle NULL
+ if (vshTableRowAppend(table, name, NULL) < 0)
... which would make this fail use NULLSTR_EMPTY
+ 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 = virshCompleteEmpty,
It will be possible to auto-complete this argument so virshCompleteEmpty is not appropriate. That annotation must be used exclusively for fields which don't make sense tot be complted.
+ .help = N_("throttle group name") + }, + {.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_INT, + .help = N_("read throughput limit, as scaled integer (default bytes)") + }, + {.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_INT, + .help = N_("total I/O operations limit per second") + }, + {.name = "read-iops-sec", + .type = VSH_OT_INT, + .help = N_("read I/O operations limit per second") + }, + {.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_INT, + .help = N_("total max, as scaled integer (default bytes)") + }, + {.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_INT, + .help = N_("write max, as scaled integer (default bytes)") + }, + {.name = "total-iops-sec-max", + .type = VSH_OT_INT, + .help = N_("total I/O operations 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_INT, + .help = N_("write I/O operations max") + }, + {.name = "size-iops-sec", + .type = VSH_OT_INT, + .help = N_("I/O size in bytes") + }, + {.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_INT, + .help = N_("duration in seconds to allow read max bytes") + }, + {.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_INT, + .help = N_("duration in seconds to allow total I/O operations max") + }, + {.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_INT, + .help = N_("duration in seconds to allow write I/O operations max") + },
I guess that once we have two of the commands using these it'd make sense to have these as a macro to avoid having to update two places the next time another tunable is added.
+ VIRSH_COMMON_OPT_DOMAIN_CONFIG, + VIRSH_COMMON_OPT_DOMAIN_LIVE, + VIRSH_COMMON_OPT_DOMAIN_CURRENT, + {.name = NULL} +}; + + +static bool +cmdThrottleGroupSet(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *name; + 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, &name))) + goto cleanup;
This function doesn't use 'name' any more so there's no point in fetching it.
+ + +#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) {
gropu name is guaranteed to exist ...
+ 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;
otherwis this'd fail anyways.
+ 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 change 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 = virshCompleteEmpty,
Same as above regarding the annotation.
+ .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 = virshCompleteEmptya
Same as above. ,
+ .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 *name; + 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, &name))) + goto cleanup;
Name is not used.
+ + 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"));
Misaligned.
+ 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 */ @@ -13378,6 +13782,30 @@ const vshCmdDef domManagementCmds[] = { .info = &info_blkdeviotune, .flags = 0 }, + {.name = "throttlegroupset", + .handler = cmdThrottleGroupSet, + .opts = opts_throttlegroupset, + .info = &info_throttlegroupset, + .flags = 0 + }, + {.name = "throttlegroupdel", + .handler = cmdThrottleGroupDel, + .opts = opts_throttlegroupdel, + .info = &info_throttlegroupdel, + .flags = 0 + }, + {.name = "throttlegroupinfo", + .handler = cmdThrottleGroupInfo, + .opts = opts_throttlegroupinfo, + .info = &info_throttlegroupinfo, + .flags = 0 + }, + {.name = "throttlegrouplist", + .handler = cmdThrottleGroupList, + .opts = opts_throttlegrouplist, + .info = &info_throttlegrouplist, + .flags = 0 + },
I suggest using the 'dom' prefix as all of the objects are per-domain.
{.name = "blkiotune", .handler = cmdBlkiotune, .opts = opts_blkiotune, -- 2.34.1

From: Chun Feng Wu <wucf@linux.ibm.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 <wucf@linux.ibm.com> --- tools/virsh-completer-domain.c | 64 ++++++++++++++++++++++++++++++++++ tools/virsh-completer-domain.h | 5 +++ tools/virsh-domain.c | 25 ++++++++++++- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/tools/virsh-completer-domain.c b/tools/virsh-completer-domain.c index 61362224a3..000cf65c99 100644 --- a/tools/virsh-completer-domain.c +++ b/tools/virsh-completer-domain.c @@ -248,6 +248,70 @@ virshDomainMigrateDisksCompleter(vshControl *ctl, } +static 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; + int ngroups; + size_t i; + g_auto(GStrv) tmp = NULL; + + virCheckFlags(0, NULL); + + if (!priv->conn || virConnectIsAlive(priv->conn) <= 0) + return NULL; + + if (virshDomainGetXML(ctl, cmd, 0, &xmldoc, &ctxt) < 0) + return NULL; + + ngroups = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, &groups); + if (ngroups < 0) + return NULL; + + tmp = g_new0(char *, ngroups + 1); + + for (i = 0; i < ngroups; i++) { + ctxt->node = groups[i]; + if (!(tmp[i] = virXPathString("string(./group_name)", ctxt))) + return NULL; + } + + return g_steal_pointer(&tmp); +} + + +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 27cf963912..95ec3e28df 100644 --- a/tools/virsh-completer-domain.h +++ b/tools/virsh-completer-domain.h @@ -41,6 +41,11 @@ virshDomainDiskTargetCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags); +char ** +virshDomainThrottleGroupsCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int completeflags); + char ** virshDomainInterfaceStateCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index f84a65451a..ec2330c8dd 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.34.1

On Wed, Jun 12, 2024 at 03:02:24 -0700, wucf@linux.ibm.com wrote:
From: Chun Feng Wu <wucf@linux.ibm.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 <wucf@linux.ibm.com> --- tools/virsh-completer-domain.c | 64 ++++++++++++++++++++++++++++++++++ tools/virsh-completer-domain.h | 5 +++ tools/virsh-domain.c | 25 ++++++++++++- 3 files changed, 93 insertions(+), 1 deletion(-)
Missing corresponding manpage update.
diff --git a/tools/virsh-completer-domain.c b/tools/virsh-completer-domain.c index 61362224a3..000cf65c99 100644 --- a/tools/virsh-completer-domain.c +++ b/tools/virsh-completer-domain.c @@ -248,6 +248,70 @@ virshDomainMigrateDisksCompleter(vshControl *ctl, }
+static char ** +virshDomainThrottleGroupCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags)
Well, you see that it's possible to complete these. Add this helper beforehand and use it approprately also when adding the helpers in previous patch.
+{ + virshControl *priv = ctl->privData; + g_autoptr(xmlDoc) xmldoc = NULL; + g_autoptr(xmlXPathContext) ctxt = NULL; + g_autofree xmlNodePtr *groups = NULL; + int ngroups; + size_t i; + g_auto(GStrv) tmp = NULL; + + virCheckFlags(0, NULL); + + if (!priv->conn || virConnectIsAlive(priv->conn) <= 0) + return NULL; + + if (virshDomainGetXML(ctl, cmd, 0, &xmldoc, &ctxt) < 0) + return NULL; + + ngroups = virXPathNodeSet("./throttlegroups/throttlegroup", ctxt, &groups); + if (ngroups < 0) + return NULL; + + tmp = g_new0(char *, ngroups + 1); + + for (i = 0; i < ngroups; i++) { + ctxt->node = groups[i]; + if (!(tmp[i] = virXPathString("string(./group_name)", ctxt))) + return NULL;
Since this also does everything that the 'throttlegrouplist' command does, you can theoretically even reuse this there and use it to fill the table.
+ } + + return g_steal_pointer(&tmp); +}
[...]
participants (3)
-
Chun Feng Wu
-
Peter Krempa
-
wucf@linux.ibm.com