mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-13 09:36:05 +08:00
feat: expose KubeVirt high-performance storage features in VM and Volume UI (backport #1133) (#1191)
Signed-off-by: Jack Yu <jack.yu@suse.com> Co-authored-by: Alejandro Bonilla <abonilla@suse.com> Co-authored-by: Jack Yu <jack.yu@suse.com>
This commit is contained in:
co-authored by
Alejandro Bonilla
Jack Yu
parent
fb190996b6
commit
db27a174f9
+321
@@ -0,0 +1,321 @@
|
||||
<script>
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { _VIEW } from '@shell/config/query-params';
|
||||
import { DISK_CACHE_MODE, DISK_IO_MODE, DISK_PERFORMANCE_PROFILE } from '../../../config/harvester-map';
|
||||
|
||||
const { DEFAULT, HIGH, CUSTOM } = DISK_PERFORMANCE_PROFILE;
|
||||
|
||||
export default {
|
||||
name: 'DiskPerformanceOptions',
|
||||
|
||||
components: {
|
||||
LabeledSelect, Checkbox, Banner
|
||||
},
|
||||
|
||||
emits: ['update'],
|
||||
|
||||
props: {
|
||||
// The disk row. This component reads/writes value.cache, value.io,
|
||||
// value.dedicatedIOThread and (for the "High Performance" preset) value.bus.
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
const hasPerf = !!(this.value.cache || this.value.io || this.value.dedicatedIOThread);
|
||||
|
||||
return {
|
||||
DISK_PERFORMANCE_PROFILE,
|
||||
expanded: hasPerf,
|
||||
profile: this.detectProfile(),
|
||||
// Bus the disk used before the "High Performance" preset switched it to
|
||||
// virtio, so returning to "Default" can put it back.
|
||||
busBeforeHighProfile: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isView() {
|
||||
return this.mode === _VIEW;
|
||||
},
|
||||
|
||||
storagePerformanceEnabled() {
|
||||
return this.$store.getters['harvester-common/getFeatureEnabled']('highPerformanceStorage');
|
||||
},
|
||||
|
||||
isCdRom() {
|
||||
return this.value.type === 'cd-rom';
|
||||
},
|
||||
|
||||
isCustom() {
|
||||
return this.profile === CUSTOM;
|
||||
},
|
||||
|
||||
isNativeIo() {
|
||||
return this.value.io === 'native';
|
||||
},
|
||||
|
||||
profileOptions() {
|
||||
return [DEFAULT, HIGH, CUSTOM].map((value) => ({
|
||||
label: this.t(`harvester.virtualMachine.volume.performance.profile.${ value }`),
|
||||
value,
|
||||
}));
|
||||
},
|
||||
|
||||
cacheOptions() {
|
||||
return DISK_CACHE_MODE.map((value) => ({
|
||||
label: this.cacheLabel(value),
|
||||
value,
|
||||
// Native AIO only works with an uncached (O_DIRECT) disk, so while it is
|
||||
// selected the only valid cache option is "none" — disable everything
|
||||
// else, including "Default", to prevent an invalid combination.
|
||||
disabled: this.isNativeIo && value !== 'none',
|
||||
}));
|
||||
},
|
||||
|
||||
ioOptions() {
|
||||
return DISK_IO_MODE.map((value) => ({
|
||||
label: value === '' ? this.t('harvester.virtualMachine.volume.performance.ioMode.default') : this.t(`harvester.virtualMachine.volume.performance.ioMode.${ value }`),
|
||||
value,
|
||||
}));
|
||||
},
|
||||
|
||||
showFilesystemCacheWarning() {
|
||||
return this.value.cache === 'none' && this.value.volumeMode === 'Filesystem';
|
||||
},
|
||||
|
||||
showBusTip() {
|
||||
const hasPerf = !!(this.value.cache || this.value.io || this.value.dedicatedIOThread);
|
||||
|
||||
return hasPerf && this.value.bus && this.value.bus !== 'virtio';
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
// Keep the profile selector in sync if the row is repopulated (e.g. editing an existing VM).
|
||||
'value.cache'() {
|
||||
this.profile = this.detectProfile();
|
||||
},
|
||||
'value.io'() {
|
||||
this.profile = this.detectProfile();
|
||||
},
|
||||
'value.dedicatedIOThread'() {
|
||||
this.profile = this.detectProfile();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
detectProfile() {
|
||||
const { cache, io, dedicatedIOThread } = this.value;
|
||||
|
||||
if (!cache && !io && !dedicatedIOThread) {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
if (cache === 'none' && io === 'native' && dedicatedIOThread) {
|
||||
return HIGH;
|
||||
}
|
||||
|
||||
return CUSTOM;
|
||||
},
|
||||
|
||||
cacheLabel(value) {
|
||||
if (value === '') {
|
||||
return this.t('harvester.virtualMachine.volume.performance.cacheMode.default');
|
||||
}
|
||||
|
||||
return this.t(`harvester.virtualMachine.volume.performance.cacheMode.${ value }`);
|
||||
},
|
||||
|
||||
onProfileChange(profile) {
|
||||
const previous = this.profile;
|
||||
|
||||
this.profile = profile;
|
||||
|
||||
if (profile === DEFAULT) {
|
||||
this.value.cache = '';
|
||||
this.value.io = '';
|
||||
this.value.dedicatedIOThread = false;
|
||||
|
||||
// "Default" means no overrides, so also undo the virtio bus that the
|
||||
// "High Performance" preset applied. Skip it if the user has since
|
||||
// picked a different bus themselves — that choice is theirs to keep.
|
||||
if (this.busBeforeHighProfile !== null) {
|
||||
if (this.value.bus === 'virtio') {
|
||||
this.value.bus = this.busBeforeHighProfile;
|
||||
}
|
||||
this.busBeforeHighProfile = null;
|
||||
}
|
||||
} else if (profile === HIGH) {
|
||||
this.value.cache = 'none';
|
||||
this.value.io = 'native';
|
||||
this.value.dedicatedIOThread = true;
|
||||
|
||||
if (previous !== HIGH) {
|
||||
this.busBeforeHighProfile = this.value.bus || '';
|
||||
}
|
||||
this.value.bus = 'virtio';
|
||||
}
|
||||
// CUSTOM keeps whatever is currently set, including the bus.
|
||||
|
||||
this.update();
|
||||
},
|
||||
|
||||
onIoChange(io) {
|
||||
this.value.io = io;
|
||||
// Native AIO requires an uncached disk; force cache to "none" to keep the
|
||||
// combo valid — including when cache is still on its "Default" ('') value,
|
||||
// which libvirt would otherwise reject at domain start.
|
||||
if (io === 'native' && this.value.cache !== 'none') {
|
||||
this.value.cache = 'none';
|
||||
}
|
||||
this.update();
|
||||
},
|
||||
|
||||
update() {
|
||||
this.$emit('update');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!isCdRom && storagePerformanceEnabled"
|
||||
class="disk-performance"
|
||||
>
|
||||
<button
|
||||
v-if="!isView"
|
||||
type="button"
|
||||
class="btn btn-sm role-link expand-toggle"
|
||||
:aria-expanded="expanded"
|
||||
:aria-label="expanded ? t('harvester.virtualMachine.volume.performance.collapse') : t('harvester.virtualMachine.volume.performance.expand')"
|
||||
@click.prevent="expanded = !expanded"
|
||||
>
|
||||
<i
|
||||
class="icon"
|
||||
:class="expanded ? 'icon-chevron-down' : 'icon-chevron-right'"
|
||||
/>
|
||||
{{ t('harvester.virtualMachine.volume.performance.title') }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-if="expanded || isView"
|
||||
class="perf-body mt-10"
|
||||
>
|
||||
<div class="row mb-20">
|
||||
<div
|
||||
class="col span-6"
|
||||
data-testid="input-disk-perf-profile"
|
||||
>
|
||||
<LabeledSelect
|
||||
:value="profile"
|
||||
:label="t('harvester.virtualMachine.volume.performance.profile.label')"
|
||||
:tooltip="t('harvester.virtualMachine.volume.performance.profile.tip')"
|
||||
:options="profileOptions"
|
||||
:mode="mode"
|
||||
@update:value="onProfileChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Banner
|
||||
v-if="profile === DISK_PERFORMANCE_PROFILE.HIGH"
|
||||
color="info"
|
||||
:label="t('harvester.virtualMachine.volume.performance.highProfileTip')"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="isCustom || isView"
|
||||
class="row mb-20"
|
||||
>
|
||||
<div
|
||||
class="col span-6"
|
||||
data-testid="input-disk-cache"
|
||||
>
|
||||
<LabeledSelect
|
||||
v-model:value="value.cache"
|
||||
:label="t('harvester.virtualMachine.volume.performance.cacheMode.label')"
|
||||
:tooltip="t('harvester.virtualMachine.volume.performance.cacheMode.tip')"
|
||||
:options="cacheOptions"
|
||||
:mode="mode"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col span-6"
|
||||
data-testid="input-disk-io"
|
||||
>
|
||||
<LabeledSelect
|
||||
:value="value.io"
|
||||
:label="t('harvester.virtualMachine.volume.performance.ioMode.label')"
|
||||
:tooltip="t('harvester.virtualMachine.volume.performance.ioMode.tip')"
|
||||
:options="ioOptions"
|
||||
:mode="mode"
|
||||
@update:value="onIoChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isCustom || isView"
|
||||
class="row mb-10"
|
||||
>
|
||||
<div
|
||||
class="col span-12"
|
||||
data-testid="input-disk-dedicated-iothread"
|
||||
>
|
||||
<Checkbox
|
||||
v-model:value="value.dedicatedIOThread"
|
||||
:label="t('harvester.virtualMachine.volume.performance.dedicatedIOThread.label')"
|
||||
:tooltip="t('harvester.virtualMachine.volume.performance.dedicatedIOThread.tip')"
|
||||
:mode="mode"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Banner
|
||||
v-if="isNativeIo"
|
||||
color="info"
|
||||
:label="t('harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip')"
|
||||
/>
|
||||
<Banner
|
||||
v-if="showFilesystemCacheWarning"
|
||||
color="warning"
|
||||
:label="t('harvester.virtualMachine.volume.performance.cacheMode.filesystemWarning')"
|
||||
/>
|
||||
<Banner
|
||||
v-if="showBusTip"
|
||||
color="warning"
|
||||
:label="t('harvester.virtualMachine.volume.performance.busTip')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.disk-performance {
|
||||
border-top: 1px solid var(--border);
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
|
||||
.expand-toggle {
|
||||
padding: 0;
|
||||
font-weight: 600;
|
||||
|
||||
.icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user