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