From 7976687a1ec5dc0c05b37dfd2f2bdef61e9d48c2 Mon Sep 17 00:00:00 2001 From: Marcelo Fukumoto Date: Thu, 5 Nov 2026 17:51:46 +0000 Subject: [PATCH] feat: align the storage performance options with the Rancher machine pool form (#1212) * feat: align the storage performance options with the Rancher machine pool form Show the storage performance profile on every volume instead of behind a collapsible toggle, and move the VM-wide options to a plain Storage Performance section in the Advanced Options tab. Put the Native I/O tip in a live region so screen readers announce the cache mode change, use an integer input for the I/O thread count, and use the same copy as the Rancher form. Skip the view-only backup row in the image and container volumes when it has nothing to show, so it no longer adds an empty gap above the profile. Signed-off-by: Marcelo Fukumoto * fix: announce the storage performance warnings and name the virtio requirement - Put the bus tip and the Filesystem warning in an always-rendered status region. They appear above the field being changed, so screen readers need them announced. - Say in the bus tip that a dedicated I/O thread needs the virtio bus, because KubeVirt rejects it on any other bus. - Mark I/O Thread Count as required while editing, like the Rancher form. Signed-off-by: Marcelo Fukumoto * fix: state only the dedicated I/O thread requirement in the bus tip Neither the Harvester docs nor KubeVirt tie cache mode or I/O mode to the bus, so stop saying that those options work best on virtio. Signed-off-by: Marcelo Fukumoto --------- Signed-off-by: Marcelo Fukumoto (cherry picked from commit 12c28c08fa92f928d739210185498fa259590db9) # Conflicts: # pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/DiskPerformanceOptions.vue # pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/VMPerformanceOptions.vue # pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/__tests__/DiskPerformanceOptions.test.ts # pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/__tests__/VMPerformanceOptions.test.js --- .../kubevirt.io.virtualmachine/index.vue | 8 + ...erhci.io.virtualmachinetemplateversion.vue | 15 +- .../DiskPerformanceOptions.vue | 86 ++++++---- .../VMPerformanceOptions.vue | 136 +++++++-------- .../__tests__/DiskPerformanceOptions.test.ts | 162 ++++++++++++++++++ .../__tests__/VMPerformanceOptions.test.js | 105 ++++++++++++ .../VirtualMachineVolume/index.vue | 41 +---- .../__tests__/HarvesterEditContainer.test.ts | 7 + .../__tests__/HarvesterEditVMImage.test.ts | 26 +++ .../VirtualMachineVolume/type/container.vue | 10 +- .../VirtualMachineVolume/type/vmImage.vue | 10 +- .../edit/kubevirt.io.virtualmachine/index.vue | 15 +- pkg/harvester/l10n/en-us.yaml | 35 ++-- pkg/harvester/mixins/harvester-vm/index.js | 8 + 14 files changed, 488 insertions(+), 176 deletions(-) create mode 100644 pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/__tests__/VMPerformanceOptions.test.js diff --git a/pkg/harvester/detail/kubevirt.io.virtualmachine/index.vue b/pkg/harvester/detail/kubevirt.io.virtualmachine/index.vue index b9565aff..9d87c2d1 100644 --- a/pkg/harvester/detail/kubevirt.io.virtualmachine/index.vue +++ b/pkg/harvester/detail/kubevirt.io.virtualmachine/index.vue @@ -18,6 +18,7 @@ import { getLatestMigration } from '../../utils/vm'; import CloudConfig from '../../edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig'; import Volume from '../../edit/kubevirt.io.virtualmachine/VirtualMachineVolume'; +import VMPerformanceOptions from '../../edit/kubevirt.io.virtualmachine/VirtualMachineVolume/VMPerformanceOptions'; import Network from '../../edit/kubevirt.io.virtualmachine/VirtualMachineNetwork'; import AccessCredentials from '../../edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials'; import Events from './VirtualMachineTabs/VirtualMachineEvents'; @@ -40,6 +41,7 @@ export default { OverviewBasics, LabelValue, Volume, + VMPerformanceOptions, Network, OverviewKeypairs, CloudConfig, @@ -253,9 +255,15 @@ export default { :namespace="value.metadata.namespace" :vm="value" :resource-type="value.type" + /> + diff --git a/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue b/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue index aa4fd34f..2500cb11 100644 --- a/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue +++ b/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue @@ -21,6 +21,7 @@ import { HCI } from '../types'; import VM_MIXIN from '../mixins/harvester-vm'; import Reserved from './kubevirt.io.virtualmachine/VirtualMachineReserved'; import Volume from './kubevirt.io.virtualmachine/VirtualMachineVolume'; +import VMPerformanceOptions from './kubevirt.io.virtualmachine/VirtualMachineVolume/VMPerformanceOptions'; import Network from './kubevirt.io.virtualmachine/VirtualMachineNetwork'; import CpuMemory from './kubevirt.io.virtualmachine/VirtualMachineCpuMemory'; import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel'; @@ -37,6 +38,7 @@ export default { Tab, SSHKey, Volume, + VMPerformanceOptions, Tabbed, Network, Checkbox, @@ -390,9 +392,6 @@ export default { > + + +<<<<<<< HEAD - - { expect(wrapper.emitted('update')).toHaveLength(1); }); + + it('should not render an empty row when there is no backup to show', () => { + const wrapper = mount(HarvesterEditContainer, mountOptions()); + const emptyRows = wrapper.findAll('.row').filter((row) => row.element.children.length === 0); + + expect(emptyRows).toHaveLength(0); + }); }); diff --git a/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVMImage.test.ts b/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVMImage.test.ts index e627543a..1cee8229 100644 --- a/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVMImage.test.ts +++ b/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVMImage.test.ts @@ -133,4 +133,30 @@ describe('component: HarvesterEditVMImage', () => { expect(wrapper.emitted('update')).toHaveLength(1); }); + + it('should not render an empty row when there is no backup to show', () => { + const wrapper = mount(HarvesterEditVMImage, { + props: { + mode: _CREATE, + isVirtualType: false, + newCreateId: '123', + validateRequired: true, + idx: 1 + }, + global: { + mocks: { + $store: { + getters: { + 'harvester-common/getFeatureEnabled': () => true, + 'harvester/all': jest.fn(), + 'i18n/t': jest.fn(), + } + }, + } + } + }); + const emptyRows = wrapper.findAll('.row').filter((row) => row.element.children.length === 0); + + expect(emptyRows).toHaveLength(0); + }); }); diff --git a/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/container.vue b/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/container.vue index 830de50f..6bf86f54 100644 --- a/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/container.vue +++ b/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/container.vue @@ -132,11 +132,11 @@ export default { -
-
+
+
-
-
+
+
+ + R.type === 'disk' && R.bus === 'virtio'); + }, + + hasDedicatedIOThread() { + return (this.diskRows || []).some((R) => R.dedicatedIOThread); + }, + needNewSecret() { // When creating a template it is always necessary to create a new secret. return this.isCreate || this.showYaml ? false : this.resourceType === HCI.VM_VERSION;