From c8a613874a488a356082f0903131d1e621ddd1fa Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Fri, 13 Feb 2026 15:40:07 +0800 Subject: [PATCH] feat: add cpu model selection (#702) Signed-off-by: Jack Yu --- ...erhci.io.virtualmachinetemplateversion.vue | 38 ++++- .../VirtualMachineCpuModel.vue | 133 ++++++++++++++++++ .../edit/kubevirt.io.virtualmachine/index.vue | 26 +++- pkg/harvester/l10n/en-us.yaml | 4 + pkg/harvester/mixins/harvester-vm/index.js | 3 + 5 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineCpuModel.vue diff --git a/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue b/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue index 81029521..3c56ef9f 100644 --- a/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue +++ b/pkg/harvester/edit/harvesterhci.io.virtualmachinetemplateversion.vue @@ -6,6 +6,7 @@ import { Checkbox } from '@components/Form/Checkbox'; import CruResource from '@shell/components/CruResource'; import NameNsDescription from '@shell/components/form/NameNsDescription'; import LabeledSelect from '@shell/components/form/LabeledSelect'; +import { set } from '@shell/utils/object'; import { Banner } from '@components/Banner'; import KeyValue from '@shell/components/form/KeyValue'; import NodeScheduling from '@shell/components/form/NodeScheduling'; @@ -22,6 +23,7 @@ import Reserved from './kubevirt.io.virtualmachine/VirtualMachineReserved'; import Volume from './kubevirt.io.virtualmachine/VirtualMachineVolume'; import Network from './kubevirt.io.virtualmachine/VirtualMachineNetwork'; import CpuMemory from './kubevirt.io.virtualmachine/VirtualMachineCpuMemory'; +import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel'; import CloudConfig from './kubevirt.io.virtualmachine/VirtualMachineCloudConfig'; import SSHKey from './kubevirt.io.virtualmachine/VirtualMachineSSHKey'; @@ -38,6 +40,7 @@ export default { Network, Checkbox, CpuMemory, + CpuModel, CruResource, CloudConfig, LabeledSelect, @@ -70,12 +73,12 @@ export default { return { templateId, - templateValue: null, - templateSpec: null, - versionName: '', - description: '', - defaultVersion: null, - isDefaultVersion: false, + templateValue: null, + templateSpec: null, + versionName: '', + description: '', + defaultVersion: null, + isDefaultVersion: false, }; }, @@ -154,6 +157,18 @@ export default { }, methods: { + updateCpuModel(value) { + if (!this.spec?.template?.spec?.domain?.cpu) { + set(this.spec, 'template.spec.domain.cpu', {}); + } + + if (value && value !== '') { + set(this.spec.template.spec.domain.cpu, 'model', value); + } else { + delete this.spec.template.spec.domain.cpu.model; + } + }, + async saveVMT(buttonCb) { this.parseVM(); @@ -436,6 +451,17 @@ export default { /> + +
+
+ +
+
+
+import YAML from 'yaml'; +import LabeledSelect from '@shell/components/form/LabeledSelect'; +import { CONFIG_MAP } from '@shell/config/types'; +import { Banner } from '@components/Banner'; + +const CPU_MODEL_CONFIG_MAP_ID = 'harvester-system/node-cpu-model-configuration'; + +export default { + name: 'HarvesterCpuModel', + + emits: ['update:value'], + + components: { + LabeledSelect, + Banner + }, + + props: { + value: { + type: String, + default: '' + }, + mode: { + type: String, + default: 'create', + }, + }, + + async fetch() { + const inStore = this.$store.getters['currentProduct'].inStore; + + try { + await this.$store.dispatch(`${ inStore }/find`, { type: CONFIG_MAP, id: CPU_MODEL_CONFIG_MAP_ID }); + this.fetchError = null; + } catch (e) { + this.fetchError = this.t('harvester.virtualMachine.cpuModel.fetchError', { error: e.message || e }); + } + }, + + data() { + return { fetchError: null }; + }, + + computed: { + localValue: { + get() { + return this.value ?? ''; + }, + set(val) { + this.$emit('update:value', val ?? ''); + } + }, + + cpuModelConfigMap() { + const inStore = this.$store.getters['currentProduct'].inStore; + + return this.$store.getters[`${ inStore }/byId`]( + CONFIG_MAP, + CPU_MODEL_CONFIG_MAP_ID + ); + }, + + cpuModelOptions() { + if (!this.cpuModelConfigMap?.data?.cpuModels) { + return [{ label: this.t('generic.default'), value: '' }]; + } + + let cpuModelsData; + + try { + cpuModelsData = YAML.parse(this.cpuModelConfigMap.data?.cpuModels || ''); + } catch (e) { + return [{ label: this.t('generic.default'), value: '' }]; + } + + const options = []; + + options.push({ + label: this.t('generic.default'), + value: '' + }); + + // Add global models (host-model, host-passthrough) + const globalModels = cpuModelsData.globalModels || []; + + globalModels.forEach((modelName) => { + options.push({ + label: modelName, + value: modelName + }); + }); + + // Add regular models with node count + const modelEntries = Object.entries(cpuModelsData.models || {}); + + // Sort models alphabetically for consistent display + modelEntries.sort((a, b) => a[0].localeCompare(b[0])); + + modelEntries.forEach(([modelName, modelInfo]) => { + const readyCount = modelInfo.readyCount || 0; + const label = this.t('harvester.virtualMachine.cpuModel.optionLabel', { modelName, count: readyCount }); + + options.push({ + label, + value: modelName + }); + }); + + return options; + }, + }, +}; + + + diff --git a/pkg/harvester/edit/kubevirt.io.virtualmachine/index.vue b/pkg/harvester/edit/kubevirt.io.virtualmachine/index.vue index 2af391e7..80d2eb35 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 } from '@shell/utils/object'; +import { clone, set } from '@shell/utils/object'; import Tab from '@shell/components/Tabbed/Tab'; import { Checkbox } from '@components/Form/Checkbox'; import CruResource from '@shell/components/CruResource'; @@ -32,6 +32,7 @@ import PciDevices from './VirtualMachinePciDevices/index'; import AccessCredentials from './VirtualMachineAccessCredentials'; import CloudConfig from './VirtualMachineCloudConfig'; import CpuMemory from './VirtualMachineCpuMemory'; +import CpuModel from './VirtualMachineCpuModel'; import Network from './VirtualMachineNetwork'; import Volume from './VirtualMachineVolume'; import SSHKey from './VirtualMachineSSHKey'; @@ -57,6 +58,7 @@ export default { SSHKey, Network, CpuMemory, + CpuModel, CloudConfig, NodeScheduling, PodAffinity, @@ -538,6 +540,18 @@ export default { return out; }, + + updateCpuModel(value) { + if (!this.spec?.template?.spec?.domain?.cpu) { + set(this.spec, 'template.spec.domain.cpu', {}); + } + + if (value && value !== '') { + set(this.spec.template.spec.domain.cpu, 'model', value); + } else { + delete this.spec.template.spec.domain.cpu.model; + } + }, }, }; @@ -870,6 +884,16 @@ export default {
+
+
+ +
+
+