feat: add cpu model selection (#702)

Signed-off-by: Jack Yu <jack.yu@suse.com>
This commit is contained in:
Jack Yu 2026-02-13 15:40:07 +08:00 committed by GitHub
parent 2db7ee7397
commit c8a613874a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 197 additions and 7 deletions

View File

@ -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,
@ -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 {
/>
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<CpuModel
:value="spec.template.spec.domain.cpu?.model || ''"
:mode="mode"
@update:value="updateCpuModel"
/>
</div>
</div>
<div class="row mb-20">
<a
v-if="showAdvanced"

View File

@ -0,0 +1,133 @@
<script>
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;
},
},
};
</script>
<template>
<div>
<Banner
v-if="fetchError"
color="error"
class="mb-20"
>
{{ fetchError }}
</Banner>
<LabeledSelect
v-model:value="localValue"
:label="t('harvester.virtualMachine.cpuModel.label')"
:options="cpuModelOptions"
:mode="mode"
:disabled="!!fetchError"
/>
</div>
</template>

View File

@ -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;
}
},
},
};
</script>
@ -870,6 +884,16 @@ export default {
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<CpuModel
v-model:value="cpuModel"
:mode="mode"
@update:value="updateCpuModel"
/>
</div>
</div>
<div class="row mb-20">
<a
v-if="showAdvanced"

View File

@ -617,6 +617,10 @@ harvester:
virtualMachine:
label: Virtual Machines
osType: OS Type
cpuModel:
label: CPU Model
fetchError: 'Failed to load CPU model configuration: {error}'
optionLabel: "{modelName} ({count} {count, plural, one {node} other {nodes}})"
hotplug:
title: Enable CPU and memory hotplug
tooltip: The default maximum CPU and maximum memory are {hotPlugTimes} times based on CPU and memory.

View File

@ -182,6 +182,7 @@ export default {
immutableMode: this.realMode === _CREATE ? _CREATE : _VIEW,
terminationGracePeriodSeconds: '',
cpuPinning: false,
cpuModel: '',
};
},
@ -394,6 +395,7 @@ export default {
const efiPersistentStateEnabled = this.isEFIPersistentStateEnabled(spec);
const secureBoot = this.isSecureBoot(spec);
const cpuPinning = this.isCpuPinning(spec);
const cpuModel = spec.template.spec.domain.cpu?.model || '';
const secretRef = this.getSecret(spec);
const accessCredentials = this.getAccessCredentials(spec);
@ -431,6 +433,7 @@ export default {
this['tpmPersistentStateEnabled'] = tpmPersistentStateEnabled;
this['secureBoot'] = secureBoot;
this['cpuPinning'] = cpuPinning;
this['cpuModel'] = cpuModel;
this['hasCreateVolumes'] = hasCreateVolumes;
this['networkRows'] = networkRows;