From e6dd8d6771e5091fc89c1e2585f43b403211a0da Mon Sep 17 00:00:00 2001 From: Yiya Chen Date: Wed, 22 Oct 2025 17:33:28 +0800 Subject: [PATCH] feat: online Volume Resizing from the VM Page (#568) * feat: add conditions tab * feat: ignore restart for resizing * refactor: remove unused code --------- Signed-off-by: Yi-Ya Chen --- pkg/harvester/edit/harvesterhci.io.volume.vue | 11 ++++ .../edit/kubevirt.io.virtualmachine/index.vue | 66 ++++++++++--------- pkg/harvester/l10n/en-us.yaml | 11 ++-- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/pkg/harvester/edit/harvesterhci.io.volume.vue b/pkg/harvester/edit/harvesterhci.io.volume.vue index e2a4e376..5bdb5e47 100644 --- a/pkg/harvester/edit/harvesterhci.io.volume.vue +++ b/pkg/harvester/edit/harvesterhci.io.volume.vue @@ -7,6 +7,7 @@ import ResourceTabs from '@shell/components/form/ResourceTabs'; import LabeledSelect from '@shell/components/form/LabeledSelect'; import { LabeledInput } from '@components/Form/LabeledInput'; import NameNsDescription from '@shell/components/form/NameNsDescription'; +import Conditions from '@shell/components/form/Conditions'; import { Banner } from '@components/Banner'; import { allHash } from '@shell/utils/promise'; import { get } from '@shell/utils/object'; @@ -40,6 +41,7 @@ export default { LabeledSelect, LabeledInput, NameNsDescription, + Conditions }, mixins: [CreateEditView], @@ -593,6 +595,15 @@ export default { :label="t('nameNsDescription.name.label')" /> + + + diff --git a/pkg/harvester/edit/kubevirt.io.virtualmachine/index.vue b/pkg/harvester/edit/kubevirt.io.virtualmachine/index.vue index b2eb1edd..e30800f1 100644 --- a/pkg/harvester/edit/kubevirt.io.virtualmachine/index.vue +++ b/pkg/harvester/edit/kubevirt.io.virtualmachine/index.vue @@ -2,7 +2,7 @@ import { isEqual } from 'lodash'; import { mapGetters } from 'vuex'; import Tabbed from '@shell/components/Tabbed'; -import { clone, set } from '@shell/utils/object'; +import { clone } from '@shell/utils/object'; import Tab from '@shell/components/Tabbed/Tab'; import { Checkbox } from '@components/Form/Checkbox'; import CruResource from '@shell/components/CruResource'; @@ -434,38 +434,44 @@ export default { } }, + _compareDisksIgnoreStorage(disksA = [], disksB = []) { + if (disksA.length !== disksB.length) return false; + + const sortedA = [...disksA].sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); + const sortedB = [...disksB].sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); + + for (let i = 0; i < sortedA.length; i++) { + const cloneA = clone(sortedA[i]); + const cloneB = clone(sortedB[i]); + + // remove the storage field before comparison + delete cloneA?.spec?.resources?.requests?.storage; + delete cloneB?.spec?.resources?.requests?.storage; + + if (!isEqual(cloneA, cloneB)) return false; + } + + return true; + }, + + _shouldPromptRestart(oldVM, newVM) { + const specChanged = !isEqual(oldVM?.spec, newVM?.spec); + + // check if the disk defined in annotations has changed, ignoring storage size + const oldDisks = parseVolumeClaimTemplates(oldVM); + const newDisks = parseVolumeClaimTemplates(newVM); + const diskChanged = !this._compareDisksIgnoreStorage(oldDisks, newDisks); + + return specChanged || diskChanged; + }, + restartVM() { - if (this.mode !== 'edit') { - return; - } - if (!this.value.isRunning) { - return; - } - const cloneDeepNewVM = clone(this.value); + if (this.mode !== 'edit' || !this.value.isRunning) return; - // new VM - delete cloneDeepNewVM?.metadata; - delete cloneDeepNewVM?.__clone; + const needsRestart = this._shouldPromptRestart(this.cloneVM, this.value); - // old VM - delete this.cloneVM?.metadata; - delete this.cloneVM?.__clone; - - // add empty hostDevices to old VM as CRD does not have it. - const devicesObj = this.cloneVM?.spec?.template?.spec?.domain?.devices; - - if (devicesObj && devicesObj.hostDevices === undefined) { - set(devicesObj, 'hostDevices', []); - } - - const oldVM = JSON.parse(JSON.stringify(this.cloneVM)); - const newVM = JSON.parse(JSON.stringify(cloneDeepNewVM)); - - // we won't show restart dialog in yaml page as we don't have a way to detect change in yaml editor. - // only check VM is changed in form page. - if (isEqual(oldVM, newVM)) { - return; - } + // if no restart is needed (either no changes or only size changed) + if (!needsRestart) return; return new Promise((resolve) => { this.isOpen = true; diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index 9636d2e8..c4609d21 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -837,6 +837,7 @@ harvester: snapshots: Snapshots datasource: Data Source details: Details + conditions: Conditions size: Size volumeMode: Volume Mode source: Source @@ -1065,21 +1066,21 @@ harvester: acl: label: Access Control List tooltip: The ACL to apply to this Subnet. Must be one of the ACLs in the same namespace. - action: + action: label: Action placeholder: Please select an action - direction: + direction: label: Direction placeholder: Please select a direction addRule: Add Rule - priority: + priority: label: Priority placeholder: Please select a priority - match: + match: label: Match placeholder: e.g. ip4.dst == 10.10.0.2 banner: The supported field in ACL match can refer to KubeOvn Subnet ACL document - + vpc: noAddonEnabled: prefix: The kubeovn-operator add-on is not enabled, click