diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py
index 3ffa3b71c9f4..182993ab06b9 100644
--- a/nova/tests/unit/virt/libvirt/test_config.py
+++ b/nova/tests/unit/virt/libvirt/test_config.py
@@ -1016,12 +1016,22 @@ class LibvirtConfigGuestDiskTest(LibvirtConfigBaseTest):
obj.source_path = "/tmp/hello"
obj.target_dev = "/dev/hda"
obj.target_bus = "ide"
+ # Note that read/write iops/bytes values cannot be used with
+ # total values. These are only here for illustrative purposes.
obj.disk_read_bytes_sec = 1024000
obj.disk_read_iops_sec = 1000
obj.disk_total_bytes_sec = 2048000
obj.disk_write_bytes_sec = 1024000
obj.disk_write_iops_sec = 1000
obj.disk_total_iops_sec = 2000
+ obj.disk_write_bytes_sec_max = 1536000
+ obj.disk_write_iops_sec_max = 1500
+ obj.disk_total_iops_sec_max = 3072000
+ obj.disk_read_bytes_sec_max = 1536000
+ obj.disk_read_iops_sec_max = 1500
+ obj.disk_total_iops_sec_max = 2000
+ obj.disk_total_bytes_sec_max = 3072000
+ obj.disk_size_iops_sec = 16
xml = obj.to_xml()
self.assertXmlEqual(xml, """
@@ -1035,6 +1045,13 @@ class LibvirtConfigGuestDiskTest(LibvirtConfigBaseTest):
1000
2048000
2000
+ 1536000
+ 1536000
+ 3072000
+ 1500
+ 1500
+ 2000
+ 16
""")
diff --git a/nova/tests/unit/virt/libvirt/volume/test_volume.py b/nova/tests/unit/virt/libvirt/volume/test_volume.py
index c6bb8b93b237..847557e1ea2c 100644
--- a/nova/tests/unit/virt/libvirt/volume/test_volume.py
+++ b/nova/tests/unit/virt/libvirt/volume/test_volume.py
@@ -118,8 +118,18 @@ class LibvirtISCSIVolumeBaseTestCase(LibvirtVolumeBaseTestCase):
'target_lun': 1,
'device_path': dev_path,
'qos_specs': {
+ # Note that read/write iops/bytes values cannot
+ # be used with total values.
+ # These are only here for illustrative purposes.
'total_bytes_sec': '102400',
'read_iops_sec': '200',
+ 'read_bytes_sec_max': '150000',
+ 'read_iops_sec_max': '2000',
+ 'write_bytes_sec_max': '250000',
+ 'write_iops_sec_max': '3000',
+ 'total_bytes_sec_max': '400000',
+ 'total_iops_sec_max': '4000',
+ 'size_iops_sec': '16',
}
}
}
diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py
index 8ab1216d8719..12ab86d1a22b 100644
--- a/nova/virt/libvirt/config.py
+++ b/nova/virt/libvirt/config.py
@@ -707,6 +707,13 @@ class LibvirtConfigGuestDisk(LibvirtConfigGuestDevice):
self.disk_write_iops_sec = None
self.disk_total_bytes_sec = None
self.disk_total_iops_sec = None
+ self.disk_read_bytes_sec_max = None
+ self.disk_write_bytes_sec_max = None
+ self.disk_total_bytes_sec_max = None
+ self.disk_read_iops_sec_max = None
+ self.disk_write_iops_sec_max = None
+ self.disk_total_iops_sec_max = None
+ self.disk_size_iops_sec = None
self.logical_block_size = None
self.physical_block_size = None
self.readonly = False
@@ -718,6 +725,64 @@ class LibvirtConfigGuestDisk(LibvirtConfigGuestDevice):
self.mirror = None
self.encryption = None
+ def _format_iotune(self, dev):
+ iotune = etree.Element("iotune")
+
+ if self.disk_read_bytes_sec is not None:
+ iotune.append(self._text_node("read_bytes_sec",
+ self.disk_read_bytes_sec))
+
+ if self.disk_read_iops_sec is not None:
+ iotune.append(self._text_node("read_iops_sec",
+ self.disk_read_iops_sec))
+
+ if self.disk_write_bytes_sec is not None:
+ iotune.append(self._text_node("write_bytes_sec",
+ self.disk_write_bytes_sec))
+
+ if self.disk_write_iops_sec is not None:
+ iotune.append(self._text_node("write_iops_sec",
+ self.disk_write_iops_sec))
+
+ if self.disk_total_bytes_sec is not None:
+ iotune.append(self._text_node("total_bytes_sec",
+ self.disk_total_bytes_sec))
+
+ if self.disk_total_iops_sec is not None:
+ iotune.append(self._text_node("total_iops_sec",
+ self.disk_total_iops_sec))
+
+ if self.disk_read_bytes_sec_max is not None:
+ iotune.append(self._text_node("read_bytes_sec_max",
+ self.disk_read_bytes_sec_max))
+
+ if self.disk_write_bytes_sec_max is not None:
+ iotune.append(self._text_node("write_bytes_sec_max",
+ self.disk_write_bytes_sec_max))
+
+ if self.disk_total_bytes_sec_max is not None:
+ iotune.append(self._text_node("total_bytes_sec_max",
+ self.disk_total_bytes_sec_max))
+
+ if self.disk_read_iops_sec_max is not None:
+ iotune.append(self._text_node("read_iops_sec_max",
+ self.disk_read_iops_sec_max))
+
+ if self.disk_write_iops_sec_max is not None:
+ iotune.append(self._text_node("write_iops_sec_max",
+ self.disk_write_iops_sec_max))
+
+ if self.disk_total_iops_sec_max is not None:
+ iotune.append(self._text_node("total_iops_sec_max",
+ self.disk_total_iops_sec_max))
+
+ if self.disk_size_iops_sec is not None:
+ iotune.append(self._text_node("size_iops_sec",
+ self.disk_size_iops_sec))
+
+ if len(iotune) > 0:
+ dev.append(iotune)
+
def format_dom(self):
dev = super(LibvirtConfigGuestDisk, self).format_dom()
@@ -774,34 +839,7 @@ class LibvirtConfigGuestDisk(LibvirtConfigGuestDevice):
if self.serial is not None:
dev.append(self._text_node("serial", self.serial))
- iotune = etree.Element("iotune")
-
- if self.disk_read_bytes_sec is not None:
- iotune.append(self._text_node("read_bytes_sec",
- self.disk_read_bytes_sec))
-
- if self.disk_read_iops_sec is not None:
- iotune.append(self._text_node("read_iops_sec",
- self.disk_read_iops_sec))
-
- if self.disk_write_bytes_sec is not None:
- iotune.append(self._text_node("write_bytes_sec",
- self.disk_write_bytes_sec))
-
- if self.disk_write_iops_sec is not None:
- iotune.append(self._text_node("write_iops_sec",
- self.disk_write_iops_sec))
-
- if self.disk_total_bytes_sec is not None:
- iotune.append(self._text_node("total_bytes_sec",
- self.disk_total_bytes_sec))
-
- if self.disk_total_iops_sec is not None:
- iotune.append(self._text_node("total_iops_sec",
- self.disk_total_iops_sec))
-
- if len(iotune) > 0:
- dev.append(iotune)
+ self._format_iotune(dev)
# Block size tuning
if (self.logical_block_size is not None or
diff --git a/nova/virt/libvirt/volume/volume.py b/nova/virt/libvirt/volume/volume.py
index d8c552d62e1a..aaffaa074c75 100644
--- a/nova/virt/libvirt/volume/volume.py
+++ b/nova/virt/libvirt/volume/volume.py
@@ -63,7 +63,11 @@ class LibvirtBaseVolumeDriver(object):
if 'qos_specs' in data and data['qos_specs']:
tune_opts = ['total_bytes_sec', 'read_bytes_sec',
'write_bytes_sec', 'total_iops_sec',
- 'read_iops_sec', 'write_iops_sec']
+ 'read_iops_sec', 'write_iops_sec',
+ 'read_bytes_sec_max', 'read_iops_sec_max',
+ 'write_bytes_sec_max', 'write_iops_sec_max',
+ 'total_bytes_sec_max', 'total_iops_sec_max',
+ 'size_iops_sec']
specs = data['qos_specs']
if isinstance(specs, dict):
for k, v in specs.items():
diff --git a/releasenotes/notes/enhanced-kvm-storage-qos-f8f67d404949c0b0.yaml b/releasenotes/notes/enhanced-kvm-storage-qos-f8f67d404949c0b0.yaml
new file mode 100644
index 000000000000..a4a78a0c24a0
--- /dev/null
+++ b/releasenotes/notes/enhanced-kvm-storage-qos-f8f67d404949c0b0.yaml
@@ -0,0 +1,18 @@
+---
+features:
+ - |
+ The libvirt driver now supports additional Cinder front-end
+ QoS specs, allowing the specification of additional IO
+ burst limits applied for each attached disk, individually.
+
+ - quota:read_bytes_sec_max
+ - quota:write_bytes_sec_max
+ - quota:total_bytes_sec_max
+ - quota:read_iops_sec_max
+ - quota:write_iops_sec_max
+ - quota:total_iops_sec_max
+ - quota:size_iops_sec
+
+ For more information, see the Cinder admin guide:
+
+ https://docs.openstack.org/cinder/latest/admin/blockstorage-basic-volume-qos.html