mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-11-15 13:26:15 +08:00
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 <marcelo.fukumoto@suse.com>
* 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 <marcelo.fukumoto@suse.com>
* 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 <marcelo.fukumoto@suse.com>
---------
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
(cherry picked from commit 12c28c08fa)
# 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
This commit is contained in:
@@ -18,6 +18,7 @@ import { getLatestMigration } from '../../utils/vm';
|
|||||||
|
|
||||||
import CloudConfig from '../../edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
|
import CloudConfig from '../../edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
|
||||||
import Volume from '../../edit/kubevirt.io.virtualmachine/VirtualMachineVolume';
|
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 Network from '../../edit/kubevirt.io.virtualmachine/VirtualMachineNetwork';
|
||||||
import AccessCredentials from '../../edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials';
|
import AccessCredentials from '../../edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials';
|
||||||
import Events from './VirtualMachineTabs/VirtualMachineEvents';
|
import Events from './VirtualMachineTabs/VirtualMachineEvents';
|
||||||
@@ -40,6 +41,7 @@ export default {
|
|||||||
OverviewBasics,
|
OverviewBasics,
|
||||||
LabelValue,
|
LabelValue,
|
||||||
Volume,
|
Volume,
|
||||||
|
VMPerformanceOptions,
|
||||||
Network,
|
Network,
|
||||||
OverviewKeypairs,
|
OverviewKeypairs,
|
||||||
CloudConfig,
|
CloudConfig,
|
||||||
@@ -253,9 +255,15 @@ export default {
|
|||||||
:namespace="value.metadata.namespace"
|
:namespace="value.metadata.namespace"
|
||||||
:vm="value"
|
:vm="value"
|
||||||
:resource-type="value.type"
|
:resource-type="value.type"
|
||||||
|
/>
|
||||||
|
<VMPerformanceOptions
|
||||||
:block-multi-queue="blockMultiQueue"
|
:block-multi-queue="blockMultiQueue"
|
||||||
:io-threads-policy="ioThreadsPolicy"
|
:io-threads-policy="ioThreadsPolicy"
|
||||||
:io-thread-count="ioThreadCount"
|
:io-thread-count="ioThreadCount"
|
||||||
|
:has-virtio-disk="hasVirtioDisk"
|
||||||
|
:has-dedicated-iothread="hasDedicatedIOThread"
|
||||||
|
mode="view"
|
||||||
|
class="mt-20"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { HCI } from '../types';
|
|||||||
import VM_MIXIN from '../mixins/harvester-vm';
|
import VM_MIXIN from '../mixins/harvester-vm';
|
||||||
import Reserved from './kubevirt.io.virtualmachine/VirtualMachineReserved';
|
import Reserved from './kubevirt.io.virtualmachine/VirtualMachineReserved';
|
||||||
import Volume from './kubevirt.io.virtualmachine/VirtualMachineVolume';
|
import Volume from './kubevirt.io.virtualmachine/VirtualMachineVolume';
|
||||||
|
import VMPerformanceOptions from './kubevirt.io.virtualmachine/VirtualMachineVolume/VMPerformanceOptions';
|
||||||
import Network from './kubevirt.io.virtualmachine/VirtualMachineNetwork';
|
import Network from './kubevirt.io.virtualmachine/VirtualMachineNetwork';
|
||||||
import CpuMemory from './kubevirt.io.virtualmachine/VirtualMachineCpuMemory';
|
import CpuMemory from './kubevirt.io.virtualmachine/VirtualMachineCpuMemory';
|
||||||
import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel';
|
import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel';
|
||||||
@@ -37,6 +38,7 @@ export default {
|
|||||||
Tab,
|
Tab,
|
||||||
SSHKey,
|
SSHKey,
|
||||||
Volume,
|
Volume,
|
||||||
|
VMPerformanceOptions,
|
||||||
Tabbed,
|
Tabbed,
|
||||||
Network,
|
Network,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
@@ -390,9 +392,6 @@ export default {
|
|||||||
>
|
>
|
||||||
<Volume
|
<Volume
|
||||||
v-model:value="diskRows"
|
v-model:value="diskRows"
|
||||||
v-model:block-multi-queue="blockMultiQueue"
|
|
||||||
v-model:io-threads-policy="ioThreadsPolicy"
|
|
||||||
v-model:io-thread-count="ioThreadCount"
|
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:namespace="value.metadata.namespace"
|
:namespace="value.metadata.namespace"
|
||||||
:existing-volume-disabled="true"
|
:existing-volume-disabled="true"
|
||||||
@@ -578,6 +577,16 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<VMPerformanceOptions
|
||||||
|
v-model:block-multi-queue="blockMultiQueue"
|
||||||
|
v-model:io-threads-policy="ioThreadsPolicy"
|
||||||
|
v-model:io-thread-count="ioThreadCount"
|
||||||
|
:has-virtio-disk="hasVirtioDisk"
|
||||||
|
:has-dedicated-iothread="hasDedicatedIOThread"
|
||||||
|
:mode="mode"
|
||||||
|
class="mb-20"
|
||||||
|
/>
|
||||||
|
|
||||||
<CloudConfig
|
<CloudConfig
|
||||||
ref="yamlEditor"
|
ref="yamlEditor"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
|||||||
+55
-31
@@ -2,7 +2,6 @@
|
|||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
import { Banner } from '@components/Banner';
|
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';
|
import { DISK_CACHE_MODE, DISK_IO_MODE, DISK_PERFORMANCE_PROFILE } from '../../../config/harvester-map';
|
||||||
|
|
||||||
const { DEFAULT, HIGH, CUSTOM } = DISK_PERFORMANCE_PROFILE;
|
const { DEFAULT, HIGH, CUSTOM } = DISK_PERFORMANCE_PROFILE;
|
||||||
@@ -31,11 +30,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
const hasPerf = !!(this.value.cache || this.value.io || this.value.dedicatedIOThread);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
DISK_PERFORMANCE_PROFILE,
|
DISK_PERFORMANCE_PROFILE,
|
||||||
expanded: hasPerf,
|
|
||||||
profile: this.detectProfile(),
|
profile: this.detectProfile(),
|
||||||
// Bus the disk used before the "High Performance" preset switched it to
|
// Bus the disk used before the "High Performance" preset switched it to
|
||||||
// virtio, so returning to "Default" can put it back.
|
// virtio, so returning to "Default" can put it back.
|
||||||
@@ -44,10 +40,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isView() {
|
|
||||||
return this.mode === _VIEW;
|
|
||||||
},
|
|
||||||
|
|
||||||
storagePerformanceEnabled() {
|
storagePerformanceEnabled() {
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('highPerformanceStorage');
|
return this.$store.getters['harvester-common/getFeatureEnabled']('highPerformanceStorage');
|
||||||
},
|
},
|
||||||
@@ -193,6 +185,7 @@ export default {
|
|||||||
v-if="!isCdRom && storagePerformanceEnabled"
|
v-if="!isCdRom && storagePerformanceEnabled"
|
||||||
class="disk-performance"
|
class="disk-performance"
|
||||||
>
|
>
|
||||||
|
<<<<<<< HEAD
|
||||||
<button
|
<button
|
||||||
v-if="!isView"
|
v-if="!isView"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -234,10 +227,51 @@ export default {
|
|||||||
:label="t('harvester.virtualMachine.volume.performance.highProfileTip')"
|
:label="t('harvester.virtualMachine.volume.performance.highProfileTip')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
=======
|
||||||
|
<div class="row mb-20">
|
||||||
|
>>>>>>> 12c28c0 (feat: align the storage performance options with the Rancher machine pool form (#1212))
|
||||||
<div
|
<div
|
||||||
v-if="isCustom || isView"
|
class="col span-6"
|
||||||
class="row mb-20"
|
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-key="harvester.virtualMachine.volume.performance.highProfileTip"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Always rendered, so screen readers announce these warnings when changing the fields below makes them appear
|
||||||
|
above the focused field -->
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
data-testid="disk-perf-warning-status"
|
||||||
|
>
|
||||||
|
<Banner
|
||||||
|
v-if="showBusTip"
|
||||||
|
color="warning"
|
||||||
|
data-testid="disk-perf-bus-tip"
|
||||||
|
:label="t('harvester.virtualMachine.volume.performance.busTip', { bus: value.bus }, true)"
|
||||||
|
/>
|
||||||
|
<Banner
|
||||||
|
v-if="showFilesystemCacheWarning"
|
||||||
|
color="warning"
|
||||||
|
label-key="harvester.virtualMachine.volume.performance.cacheMode.filesystemWarning"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="isCustom">
|
||||||
|
<div class="row mb-20">
|
||||||
<div
|
<div
|
||||||
class="col span-6"
|
class="col span-6"
|
||||||
data-testid="input-disk-cache"
|
data-testid="input-disk-cache"
|
||||||
@@ -266,10 +300,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="row mb-20">
|
||||||
v-if="isCustom || isView"
|
|
||||||
class="row mb-10"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="col span-12"
|
class="col span-12"
|
||||||
data-testid="input-disk-dedicated-iothread"
|
data-testid="input-disk-dedicated-iothread"
|
||||||
@@ -283,12 +314,20 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Always rendered, also outside Custom, so screen readers announce the tip when picking Native I/O changes
|
||||||
|
the cache mode, even if the new values switch the profile to High Performance -->
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
data-testid="disk-perf-native-status"
|
||||||
|
>
|
||||||
<Banner
|
<Banner
|
||||||
v-if="isNativeIo"
|
v-if="isNativeIo"
|
||||||
color="info"
|
color="info"
|
||||||
:label="t('harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip')"
|
:label="t('harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip')"
|
||||||
/>
|
/>
|
||||||
|
<<<<<<< HEAD
|
||||||
<Banner
|
<Banner
|
||||||
v-if="showFilesystemCacheWarning"
|
v-if="showFilesystemCacheWarning"
|
||||||
color="warning"
|
color="warning"
|
||||||
@@ -299,23 +338,8 @@ export default {
|
|||||||
color="warning"
|
color="warning"
|
||||||
:label="t('harvester.virtualMachine.volume.performance.busTip')"
|
:label="t('harvester.virtualMachine.volume.performance.busTip')"
|
||||||
/>
|
/>
|
||||||
|
=======
|
||||||
|
>>>>>>> 12c28c0 (feat: align the storage performance options with the Rancher machine pool form (#1212))
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
|
||||||
|
|||||||
+42
-46
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import InfoBox from '@shell/components/InfoBox';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
@@ -11,7 +10,7 @@ export default {
|
|||||||
name: 'VMPerformanceOptions',
|
name: 'VMPerformanceOptions',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
InfoBox, LabeledSelect, LabeledInput, Checkbox, Banner
|
LabeledSelect, LabeledInput, Checkbox, Banner
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['update:blockMultiQueue', 'update:ioThreadsPolicy', 'update:ioThreadCount'],
|
emits: ['update:blockMultiQueue', 'update:ioThreadsPolicy', 'update:ioThreadCount'],
|
||||||
@@ -50,12 +49,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
|
||||||
// These are specialist tuning options, so keep the panel collapsed unless the
|
|
||||||
// VM already has something configured.
|
|
||||||
return { expanded: !!(this.blockMultiQueue || this.ioThreadsPolicy) };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isView() {
|
isView() {
|
||||||
return this.mode === _VIEW;
|
return this.mode === _VIEW;
|
||||||
@@ -87,31 +80,18 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<InfoBox
|
<div
|
||||||
v-if="storagePerformanceEnabled && (!isView || blockMultiQueue || !!ioThreadsPolicy)"
|
v-if="storagePerformanceEnabled && (!isView || blockMultiQueue || !!ioThreadsPolicy)"
|
||||||
class="vm-perf mt-10"
|
class="vm-perf"
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="!isView"
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm role-link expand-toggle"
|
|
||||||
:aria-expanded="expanded"
|
|
||||||
:aria-label="expanded ? t('harvester.virtualMachine.volume.diskPerformance.collapse') : t('harvester.virtualMachine.volume.diskPerformance.expand')"
|
|
||||||
@click.prevent="expanded = !expanded"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="icon"
|
|
||||||
:class="expanded ? 'icon-chevron-down' : 'icon-chevron-right'"
|
|
||||||
/>
|
|
||||||
{{ t('harvester.virtualMachine.volume.diskPerformance.title') }}
|
|
||||||
</button>
|
|
||||||
<h3
|
|
||||||
v-else
|
|
||||||
class="mb-5"
|
|
||||||
>
|
>
|
||||||
|
<h3>
|
||||||
{{ t('harvester.virtualMachine.volume.diskPerformance.title') }}
|
{{ t('harvester.virtualMachine.volume.diskPerformance.title') }}
|
||||||
</h3>
|
</h3>
|
||||||
|
<p class="text-muted mb-10">
|
||||||
|
{{ t('harvester.virtualMachine.volume.diskPerformance.description') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
<template v-if="expanded || isView">
|
<template v-if="expanded || isView">
|
||||||
<p class="text-muted mb-15 mt-10">
|
<p class="text-muted mb-15 mt-10">
|
||||||
{{ t('harvester.virtualMachine.volume.diskPerformance.description') }}
|
{{ t('harvester.virtualMachine.volume.diskPerformance.description') }}
|
||||||
@@ -136,9 +116,28 @@ export default {
|
|||||||
v-if="!hasVirtioDisk && !isView"
|
v-if="!hasVirtioDisk && !isView"
|
||||||
color="info"
|
color="info"
|
||||||
:label="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip')"
|
:label="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip')"
|
||||||
|
=======
|
||||||
|
<div
|
||||||
|
class="mb-10"
|
||||||
|
data-testid="input-vm-block-multi-queue"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
:value="blockMultiQueue"
|
||||||
|
:label="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.label')"
|
||||||
|
:tooltip="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.tip')"
|
||||||
|
:mode="mode"
|
||||||
|
:disabled="!hasVirtioDisk"
|
||||||
|
@update:value="$emit('update:blockMultiQueue', $event)"
|
||||||
|
>>>>>>> 12c28c0 (feat: align the storage performance options with the Rancher machine pool form (#1212))
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Banner
|
||||||
|
v-if="!hasVirtioDisk && !isView"
|
||||||
|
color="info"
|
||||||
|
label-key="harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="row mb-10">
|
<div class="row mb-20">
|
||||||
<div
|
<div
|
||||||
class="col span-6"
|
class="col span-6"
|
||||||
data-testid="input-vm-iothreads-policy"
|
data-testid="input-vm-iothreads-policy"
|
||||||
@@ -152,14 +151,24 @@ export default {
|
|||||||
@update:value="$emit('update:ioThreadsPolicy', $event)"
|
@update:value="$emit('update:ioThreadsPolicy', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<Banner
|
||||||
|
v-if="hasDedicatedIothread && !ioThreadsPolicy"
|
||||||
|
color="info"
|
||||||
|
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</InfoBox>
|
||||||
|
=======
|
||||||
<div
|
<div
|
||||||
v-if="ioThreadsPolicy === 'supplementalPool'"
|
v-if="ioThreadsPolicy === 'supplementalPool'"
|
||||||
class="col span-6"
|
class="col span-6"
|
||||||
data-testid="input-vm-iothread-count"
|
data-testid="input-vm-iothread-count"
|
||||||
>
|
>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type="number"
|
type="integer"
|
||||||
min="1"
|
min="1"
|
||||||
|
:required="!isView"
|
||||||
:value="ioThreadCount"
|
:value="ioThreadCount"
|
||||||
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadCount.label')"
|
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadCount.label')"
|
||||||
:tooltip="t('harvester.virtualMachine.volume.diskPerformance.ioThreadCount.tip')"
|
:tooltip="t('harvester.virtualMachine.volume.diskPerformance.ioThreadCount.tip')"
|
||||||
@@ -171,21 +180,8 @@ export default {
|
|||||||
<Banner
|
<Banner
|
||||||
v-if="hasDedicatedIothread && !ioThreadsPolicy"
|
v-if="hasDedicatedIothread && !ioThreadsPolicy"
|
||||||
color="info"
|
color="info"
|
||||||
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip')"
|
label-key="harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
>>>>>>> 12c28c0 (feat: align the storage performance options with the Rancher machine pool form (#1212))
|
||||||
</template>
|
</template>
|
||||||
</InfoBox>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.vm-perf {
|
|
||||||
.expand-toggle {
|
|
||||||
padding: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
+162
@@ -1,3 +1,6 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { load } from 'js-yaml';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { _EDIT } from '@shell/config/query-params';
|
import { _EDIT } from '@shell/config/query-params';
|
||||||
import DiskPerformanceOptions from '../DiskPerformanceOptions.vue';
|
import DiskPerformanceOptions from '../DiskPerformanceOptions.vue';
|
||||||
@@ -22,6 +25,12 @@ const hardDisk = (overrides = {}) => ({
|
|||||||
type: 'disk', bus: 'virtio', cache: '', io: '', dedicatedIOThread: false, ...overrides
|
type: 'disk', bus: 'virtio', cache: '', io: '', dedicatedIOThread: false, ...overrides
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const nativeTip = (wrapper) => wrapper.findAllComponents(Banner)
|
||||||
|
.find((b) => b.props('labelKey') === 'harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip');
|
||||||
|
|
||||||
|
const filesystemWarning = (wrapper) => wrapper.findAllComponents(Banner)
|
||||||
|
.find((b) => b.props('labelKey') === 'harvester.virtualMachine.volume.performance.cacheMode.filesystemWarning');
|
||||||
|
|
||||||
describe('component: DiskPerformanceOptions', () => {
|
describe('component: DiskPerformanceOptions', () => {
|
||||||
it('is hidden when the highPerformanceStorage feature is disabled', () => {
|
it('is hidden when the highPerformanceStorage feature is disabled', () => {
|
||||||
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk(), { featureEnabled: false }));
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk(), { featureEnabled: false }));
|
||||||
@@ -35,6 +44,102 @@ describe('component: DiskPerformanceOptions', () => {
|
|||||||
expect(wrapper.find('.disk-performance').exists()).toBe(true);
|
expect(wrapper.find('.disk-performance').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows the profile without a toggle, and the individual fields only for Custom', () => {
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk()));
|
||||||
|
|
||||||
|
expect(wrapper.find('.expand-toggle').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('[data-testid="input-disk-perf-profile"]').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('[data-testid="input-disk-cache"]').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('[data-testid="input-disk-dedicated-iothread"]').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the individual fields for a disk with custom options', () => {
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ cache: 'writeback', io: 'threads' })));
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-testid="input-disk-cache"]').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('[data-testid="input-disk-io"]').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('[data-testid="input-disk-dedicated-iothread"]').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is hidden for a CD-ROM', () => {
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ type: 'cd-rom' })));
|
||||||
|
|
||||||
|
expect(wrapper.find('.disk-performance').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('names the bus in the bus tip once the disk uses an option', () => {
|
||||||
|
const plain = mount(DiskPerformanceOptions, mountOptions(hardDisk({ bus: 'sata' })));
|
||||||
|
const tuned = mount(DiskPerformanceOptions, mountOptions(hardDisk({ bus: 'sata', cache: 'writeback' })));
|
||||||
|
|
||||||
|
expect(plain.find('[data-testid="disk-perf-bus-tip"]').exists()).toBe(false);
|
||||||
|
expect(tuned.find('[data-testid="disk-perf-bus-tip"]').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('says in the bus tip that a dedicated I/O thread needs the virtio bus', () => {
|
||||||
|
const translations = load(fs.readFileSync(path.join(__dirname, '../../../../l10n/en-us.yaml'), 'utf8'));
|
||||||
|
const { busTip } = translations.harvester.virtualMachine.volume.performance;
|
||||||
|
|
||||||
|
expect(busTip).toContain('{bus}');
|
||||||
|
expect(busTip).toContain('A dedicated I/O thread needs the virtio bus');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the bus tip and the Filesystem warning inside a live region that is there before they appear', async() => {
|
||||||
|
const disk = { bus: 'sata', volumeMode: 'Filesystem' };
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk(disk)));
|
||||||
|
const status = wrapper.find('[data-testid="disk-perf-warning-status"]');
|
||||||
|
|
||||||
|
expect(status.attributes('role')).toBe('status');
|
||||||
|
expect(status.find('[data-testid="disk-perf-bus-tip"]').exists()).toBe(false);
|
||||||
|
expect(filesystemWarning(wrapper)).toBeUndefined();
|
||||||
|
|
||||||
|
await wrapper.setProps({ value: hardDisk({ ...disk, cache: 'none' }) });
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-testid="disk-perf-warning-status"]').element).toBe(status.element);
|
||||||
|
expect(status.find('[data-testid="disk-perf-bus-tip"]').exists()).toBe(true);
|
||||||
|
expect(status.element.contains(filesystemWarning(wrapper)?.element)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the Native I/O tip inside a live region that is there before Native I/O is picked', async() => {
|
||||||
|
const value = hardDisk({ cache: 'writeback', io: 'threads' });
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
|
||||||
|
const status = wrapper.find('[data-testid="disk-perf-native-status"]');
|
||||||
|
|
||||||
|
expect(status.attributes('role')).toBe('status');
|
||||||
|
expect(nativeTip(wrapper)).toBeUndefined();
|
||||||
|
|
||||||
|
wrapper.vm.onIoChange('native');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-testid="disk-perf-native-status"]').element).toBe(status.element);
|
||||||
|
expect(status.element.contains(nativeTip(wrapper)?.element)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the live region and the Native I/O tip when picking Native I/O switches Custom to High Performance', async() => {
|
||||||
|
const value = hardDisk({
|
||||||
|
cache: 'writeback', io: 'threads', dedicatedIOThread: true
|
||||||
|
});
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
|
||||||
|
const status = wrapper.find('[data-testid="disk-perf-native-status"]');
|
||||||
|
|
||||||
|
expect(wrapper.vm.profile).toBe('custom');
|
||||||
|
|
||||||
|
wrapper.vm.onIoChange('native');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.profile).toBe('highPerformance');
|
||||||
|
expect(wrapper.find('[data-testid="input-disk-cache"]').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('[data-testid="disk-perf-native-status"]').element).toBe(status.element);
|
||||||
|
expect(status.element.contains(nativeTip(wrapper)?.element)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the Filesystem warning as a label key so its quotes are not double-escaped', () => {
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ cache: 'none', volumeMode: 'Filesystem' })));
|
||||||
|
const banner = filesystemWarning(wrapper);
|
||||||
|
|
||||||
|
expect(banner).toBeDefined();
|
||||||
|
expect(banner?.props('label')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
it('applies the High Performance preset and emits update', () => {
|
it('applies the High Performance preset and emits update', () => {
|
||||||
const value = hardDisk();
|
const value = hardDisk();
|
||||||
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
|
||||||
@@ -122,4 +227,61 @@ describe('component: DiskPerformanceOptions', () => {
|
|||||||
expect(disabledByValue['writeback']).toBe(true);
|
expect(disabledByValue['writeback']).toBe(true);
|
||||||
expect(disabledByValue['writethrough']).toBe(true);
|
expect(disabledByValue['writethrough']).toBe(true);
|
||||||
});
|
});
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['Default', {
|
||||||
|
cache: '', io: '', dedicatedIOThread: false
|
||||||
|
}],
|
||||||
|
['High Performance', {
|
||||||
|
cache: 'none', io: 'native', dedicatedIOThread: true
|
||||||
|
}],
|
||||||
|
])('stays on Custom when the options match the %s preset', async(_label, opts) => {
|
||||||
|
const value = hardDisk({ cache: 'writeback' });
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
|
||||||
|
|
||||||
|
wrapper.vm.onProfileChange('custom');
|
||||||
|
await wrapper.setProps({ value: { ...value, ...opts } });
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.profile).toBe('custom');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects the profile from an existing disk', async() => {
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk()));
|
||||||
|
|
||||||
|
expect(wrapper.vm.profile).toBe('default');
|
||||||
|
|
||||||
|
await wrapper.setProps({
|
||||||
|
value: hardDisk({
|
||||||
|
cache: 'none', io: 'native', dedicatedIOThread: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.profile).toBe('highPerformance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates an automatically detected Custom profile when preset values are loaded', async() => {
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ cache: 'writeback' })));
|
||||||
|
|
||||||
|
expect(wrapper.vm.profile).toBe('custom');
|
||||||
|
|
||||||
|
await wrapper.setProps({
|
||||||
|
value: hardDisk({
|
||||||
|
cache: 'none', io: 'native', dedicatedIOThread: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.profile).toBe('highPerformance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the Native I/O hint as a label key so its quotes are not double-escaped', () => {
|
||||||
|
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ io: 'native' })));
|
||||||
|
const banner = nativeTip(wrapper);
|
||||||
|
|
||||||
|
expect(banner).toBeDefined();
|
||||||
|
expect(banner?.props('label')).toBeFalsy();
|
||||||
|
});
|
||||||
|
>>>>>>> 12c28c0 (feat: align the storage performance options with the Rancher machine pool form (#1212))
|
||||||
});
|
});
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import VMPerformanceOptions from '../VMPerformanceOptions.vue';
|
||||||
|
|
||||||
|
const AUTO_ENABLED_TIP = 'harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip';
|
||||||
|
const NO_VIRTIO_TIP = 'harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip';
|
||||||
|
|
||||||
|
function mountOptions(props = {}, { featureEnabled = true } = {}) {
|
||||||
|
return mount(VMPerformanceOptions, {
|
||||||
|
props: { mode: _CREATE, ...props },
|
||||||
|
global: {
|
||||||
|
mocks: {
|
||||||
|
$store: {
|
||||||
|
getters: {
|
||||||
|
'harvester-common/getFeatureEnabled': () => featureEnabled,
|
||||||
|
'i18n/t': (key) => key,
|
||||||
|
'i18n/exists': () => true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
t: (key) => key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bannerFor(wrapper, labelKey) {
|
||||||
|
return wrapper.findAllComponents(Banner).find((b) => b.props('labelKey') === labelKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('component: VMPerformanceOptions', () => {
|
||||||
|
it('should be hidden when the highPerformanceStorage feature is disabled', () => {
|
||||||
|
const wrapper = mountOptions({}, { featureEnabled: false });
|
||||||
|
|
||||||
|
expect(wrapper.find('.vm-perf').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the section with its title and fields, without a toggle', () => {
|
||||||
|
const wrapper = mountOptions();
|
||||||
|
|
||||||
|
expect(wrapper.find('h3').text()).toBe('harvester.virtualMachine.volume.diskPerformance.title');
|
||||||
|
expect(wrapper.find('.expand-toggle').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('[data-testid="input-vm-block-multi-queue"]').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('[data-testid="input-vm-iothreads-policy"]').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('[data-testid="input-vm-iothread-count"]').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only accept whole numbers as the supplemental pool thread count', () => {
|
||||||
|
const wrapper = mountOptions({ ioThreadsPolicy: 'supplementalPool', ioThreadCount: 4 });
|
||||||
|
const input = wrapper.find('[data-testid="input-vm-iothread-count"] input');
|
||||||
|
|
||||||
|
expect(input.attributes('inputmode')).toBe('numeric');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[_CREATE, true],
|
||||||
|
[_VIEW, false],
|
||||||
|
])('in %s mode, should mark the supplemental pool thread count as required: %p', (mode, required) => {
|
||||||
|
const wrapper = mountOptions({
|
||||||
|
mode, ioThreadsPolicy: 'supplementalPool', ioThreadCount: 4
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-testid="input-vm-iothread-count"] .required').exists()).toBe(required);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[{}, false],
|
||||||
|
[{ blockMultiQueue: true }, true],
|
||||||
|
[{ ioThreadsPolicy: 'auto' }, true],
|
||||||
|
])('in view mode with %p, should show the section: %p', (props, shown) => {
|
||||||
|
const wrapper = mountOptions({ ...props, mode: _VIEW });
|
||||||
|
|
||||||
|
expect(wrapper.find('.vm-perf').exists()).toBe(shown);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear Block Multi-Queue when the last virtio disk goes away', async() => {
|
||||||
|
const wrapper = mountOptions({ blockMultiQueue: true, hasVirtioDisk: true });
|
||||||
|
|
||||||
|
await wrapper.setProps({ hasVirtioDisk: false });
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:blockMultiQueue')).toStrictEqual([[false]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass the I/O threads policy hint as a label key so its quotes are not double-escaped', () => {
|
||||||
|
const wrapper = mountOptions({ hasDedicatedIothread: true });
|
||||||
|
const banner = bannerFor(wrapper, AUTO_ENABLED_TIP);
|
||||||
|
|
||||||
|
expect(banner).toBeDefined();
|
||||||
|
expect(banner.props('label')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show the I/O threads policy hint when a policy is set', () => {
|
||||||
|
const wrapper = mountOptions({ hasDedicatedIothread: true, ioThreadsPolicy: 'shared' });
|
||||||
|
|
||||||
|
expect(bannerFor(wrapper, AUTO_ENABLED_TIP)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass the no virtio disk hint as a label key', () => {
|
||||||
|
const wrapper = mountOptions({ hasVirtioDisk: false });
|
||||||
|
const banner = bannerFor(wrapper, NO_VIRTIO_TIP);
|
||||||
|
|
||||||
|
expect(banner).toBeDefined();
|
||||||
|
expect(banner.props('label')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,6 @@ import UnitInput from '@shell/components/form/UnitInput';
|
|||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||||
import VMPerformanceOptions from './VMPerformanceOptions';
|
|
||||||
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
|
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
|
||||||
import { PVC, STORAGE_CLASS } from '@shell/config/types';
|
import { PVC, STORAGE_CLASS } from '@shell/config/types';
|
||||||
import { clone } from '@shell/utils/object';
|
import { clone } from '@shell/utils/object';
|
||||||
@@ -24,10 +23,10 @@ import { EMPTY_IMAGE } from '../../../utils/vm';
|
|||||||
const { READ_WRITE_MANY } = ACCESS_MODE;
|
const { READ_WRITE_MANY } = ACCESS_MODE;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['update:value', 'update:blockMultiQueue', 'update:ioThreadsPolicy', 'update:ioThreadCount'],
|
emits: ['update:value'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard, VMPerformanceOptions
|
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -78,22 +77,6 @@ export default {
|
|||||||
resourceType: {
|
resourceType: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
|
||||||
|
|
||||||
// KubeVirt high-performance VM-level features
|
|
||||||
blockMultiQueue: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
|
|
||||||
ioThreadsPolicy: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
ioThreadCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 2
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -164,14 +147,6 @@ export default {
|
|||||||
isLHV2VolExpansionFeatureEnabled() {
|
isLHV2VolExpansionFeatureEnabled() {
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
|
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
|
||||||
},
|
},
|
||||||
|
|
||||||
hasVirtioDisk() {
|
|
||||||
return this.rows.some((R) => R.type === 'disk' && R.bus === 'virtio');
|
|
||||||
},
|
|
||||||
|
|
||||||
hasDedicatedIOThread() {
|
|
||||||
return this.rows.some((R) => R.dedicatedIOThread);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -533,18 +508,6 @@ export default {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VMPerformanceOptions
|
|
||||||
:block-multi-queue="blockMultiQueue"
|
|
||||||
:io-threads-policy="ioThreadsPolicy"
|
|
||||||
:io-thread-count="ioThreadCount"
|
|
||||||
:has-virtio-disk="hasVirtioDisk"
|
|
||||||
:has-dedicated-iothread="hasDedicatedIOThread"
|
|
||||||
:mode="mode"
|
|
||||||
@update:block-multi-queue="$emit('update:blockMultiQueue', $event)"
|
|
||||||
@update:io-threads-policy="$emit('update:ioThreadsPolicy', $event)"
|
|
||||||
@update:io-thread-count="$emit('update:ioThreadCount', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ModalWithCard
|
<ModalWithCard
|
||||||
v-if="isOpen"
|
v-if="isOpen"
|
||||||
name="deleteTip"
|
name="deleteTip"
|
||||||
|
|||||||
+7
@@ -51,4 +51,11 @@ describe('component: HarvesterEditContainer', () => {
|
|||||||
|
|
||||||
expect(wrapper.emitted('update')).toHaveLength(1);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+26
@@ -133,4 +133,30 @@ describe('component: HarvesterEditVMImage', () => {
|
|||||||
|
|
||||||
expect(wrapper.emitted('update')).toHaveLength(1);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -132,11 +132,11 @@ export default {
|
|||||||
</InputOrDisplay>
|
</InputOrDisplay>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-20">
|
|
||||||
<div
|
<div
|
||||||
v-if="value.volumeBackups"
|
v-if="value.volumeBackups"
|
||||||
class="col span-6"
|
class="row mb-20"
|
||||||
>
|
>
|
||||||
|
<div class="col span-6">
|
||||||
<InputOrDisplay
|
<InputOrDisplay
|
||||||
:name="t('harvester.virtualMachine.volume.readyToUse')"
|
:name="t('harvester.virtualMachine.volume.readyToUse')"
|
||||||
:value="value.volumeBackups.readyToUse"
|
:value="value.volumeBackups.readyToUse"
|
||||||
|
|||||||
@@ -461,11 +461,11 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-20">
|
|
||||||
<div
|
<div
|
||||||
v-if="value.volumeBackups && isView"
|
v-if="value.volumeBackups && isView"
|
||||||
class="col span-3"
|
class="row mb-20"
|
||||||
>
|
>
|
||||||
|
<div class="col span-3">
|
||||||
<LabelValue
|
<LabelValue
|
||||||
:name="t('harvester.virtualMachine.volume.readyToUse')"
|
:name="t('harvester.virtualMachine.volume.readyToUse')"
|
||||||
:value="readyToUse"
|
:value="readyToUse"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import CpuMemory from './VirtualMachineCpuMemory';
|
|||||||
import CpuModel from './VirtualMachineCpuModel';
|
import CpuModel from './VirtualMachineCpuModel';
|
||||||
import Network from './VirtualMachineNetwork';
|
import Network from './VirtualMachineNetwork';
|
||||||
import Volume from './VirtualMachineVolume';
|
import Volume from './VirtualMachineVolume';
|
||||||
|
import VMPerformanceOptions from './VirtualMachineVolume/VMPerformanceOptions';
|
||||||
import SSHKey from './VirtualMachineSSHKey';
|
import SSHKey from './VirtualMachineSSHKey';
|
||||||
import Reserved from './VirtualMachineReserved';
|
import Reserved from './VirtualMachineReserved';
|
||||||
import Filesystem from './VirtualMachineFilesystem';
|
import Filesystem from './VirtualMachineFilesystem';
|
||||||
@@ -55,6 +56,7 @@ export default {
|
|||||||
LabeledSelect,
|
LabeledSelect,
|
||||||
NameNsDescription,
|
NameNsDescription,
|
||||||
Volume,
|
Volume,
|
||||||
|
VMPerformanceOptions,
|
||||||
SSHKey,
|
SSHKey,
|
||||||
Network,
|
Network,
|
||||||
CpuMemory,
|
CpuMemory,
|
||||||
@@ -872,9 +874,6 @@ export default {
|
|||||||
>
|
>
|
||||||
<Volume
|
<Volume
|
||||||
v-model:value="diskRows"
|
v-model:value="diskRows"
|
||||||
v-model:block-multi-queue="blockMultiQueue"
|
|
||||||
v-model:io-threads-policy="ioThreadsPolicy"
|
|
||||||
v-model:io-thread-count="ioThreadCount"
|
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:custom-volume-mode="customVolumeMode"
|
:custom-volume-mode="customVolumeMode"
|
||||||
:namespace="value.metadata.namespace"
|
:namespace="value.metadata.namespace"
|
||||||
@@ -1146,6 +1145,16 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<VMPerformanceOptions
|
||||||
|
v-model:block-multi-queue="blockMultiQueue"
|
||||||
|
v-model:io-threads-policy="ioThreadsPolicy"
|
||||||
|
v-model:io-thread-count="ioThreadCount"
|
||||||
|
:has-virtio-disk="hasVirtioDisk"
|
||||||
|
:has-dedicated-iothread="hasDedicatedIOThread"
|
||||||
|
:mode="mode"
|
||||||
|
class="mb-20"
|
||||||
|
/>
|
||||||
|
|
||||||
<CloudConfig
|
<CloudConfig
|
||||||
ref="yamlEditor"
|
ref="yamlEditor"
|
||||||
:user-script="userScript"
|
:user-script="userScript"
|
||||||
|
|||||||
@@ -803,19 +803,16 @@ harvester:
|
|||||||
saveVolume: Update Volume
|
saveVolume: Update Volume
|
||||||
encryption: Encryption
|
encryption: Encryption
|
||||||
performance:
|
performance:
|
||||||
title: Storage Performance Options
|
|
||||||
expand: Show high-performance options
|
|
||||||
collapse: Hide high-performance options
|
|
||||||
profile:
|
profile:
|
||||||
label: Performance Profile
|
label: Storage Performance Profile
|
||||||
tip: "\"High Performance\" applies a tuned preset (no host cache, native AIO, a dedicated I/O thread on the virtio bus). Choose \"Custom\" to set each option yourself."
|
tip: '"High Performance" switches the disk to the virtio bus, disables the host cache, uses native AIO and gives the disk a dedicated I/O thread. Choose "Custom" to set each option yourself.'
|
||||||
default: Default (Balanced)
|
default: Default
|
||||||
highPerformance: High Performance
|
highPerformance: High Performance
|
||||||
custom: Custom
|
custom: Custom
|
||||||
highProfileTip: This disk will use the virtio bus with host cache disabled, native AIO and a dedicated I/O thread.
|
highProfileTip: This disk will use the virtio bus, no host cache, native AIO and a dedicated I/O thread.
|
||||||
cacheMode:
|
cacheMode:
|
||||||
label: Cache Mode
|
label: Cache Mode
|
||||||
tip: "How the host caches disk I/O. \"None\" (host cache disabled) is best for high-throughput guests on block storage; \"WriteBack\" favours performance; \"WriteThrough\" favours durability."
|
tip: 'How the host caches disk I/O. "None" is best for high-throughput guests on block storage, "WriteBack" favors performance and "WriteThrough" favors durability.'
|
||||||
default: Default (hypervisor)
|
default: Default (hypervisor)
|
||||||
none: None (no host cache)
|
none: None (no host cache)
|
||||||
writeback: WriteBack
|
writeback: WriteBack
|
||||||
@@ -823,35 +820,33 @@ harvester:
|
|||||||
filesystemWarning: "\"None\" (O_DIRECT) requires storage that supports direct I/O. It is recommended only for Block volume mode; on Filesystem volumes the VM may fail to start."
|
filesystemWarning: "\"None\" (O_DIRECT) requires storage that supports direct I/O. It is recommended only for Block volume mode; on Filesystem volumes the VM may fail to start."
|
||||||
ioMode:
|
ioMode:
|
||||||
label: I/O Mode
|
label: I/O Mode
|
||||||
tip: "How QEMU submits I/O. \"Native\" (Linux AIO) offers the lowest overhead but requires host cache set to \"None\"; \"Threads\" uses a host thread pool."
|
tip: 'How QEMU submits I/O. "Native" (Linux AIO) has the lowest overhead and needs cache mode "None". "Threads" uses a host thread pool.'
|
||||||
default: Default (hypervisor)
|
default: Default (hypervisor)
|
||||||
native: Native (AIO)
|
native: Native (AIO)
|
||||||
threads: Threads
|
threads: Threads
|
||||||
nativeRequiresNoCacheTip: Native I/O mode requires cache mode "None"; it has been set for you.
|
nativeRequiresNoCacheTip: Native I/O mode needs the host cache disabled, so cache mode is set to None.
|
||||||
dedicatedIOThread:
|
dedicatedIOThread:
|
||||||
label: Dedicated I/O Thread
|
label: Dedicated I/O Thread
|
||||||
tip: Allocate an exclusive libvirt I/O thread to this disk instead of sharing one. Useful for disks with heavy I/O. Enabling this on any disk turns on the VM's I/O threads policy.
|
tip: Give this disk its own I/O thread instead of sharing one. Useful for disks with heavy I/O.
|
||||||
busTip: High-performance features work best on the virtio bus.
|
busTip: 'This disk uses the {bus} bus. A dedicated I/O thread needs the virtio bus.'
|
||||||
diskPerformance:
|
diskPerformance:
|
||||||
title: High Performance (I/O Threads and Multi-Queue)
|
title: Storage Performance
|
||||||
description: These settings apply to all volumes in the Virtual Machine and complement the per-volume options above.
|
description: These options apply to all volumes of the virtual machine.
|
||||||
expand: Show VM-wide high-performance options
|
|
||||||
collapse: Hide VM-wide high-performance options
|
|
||||||
blockMultiQueue:
|
blockMultiQueue:
|
||||||
label: Virtio Block Multi-Queue
|
label: Virtio Block Multi-Queue
|
||||||
tip: Map disk I/O to multiple queues so it is processed across multiple CPUs. Applies to virtio disks and requires a fixed CPU allocation ([[Harvester]] always sets one).
|
tip: Spread disk I/O over one queue per vCPU so that it is processed on multiple CPUs.
|
||||||
noVirtioTip: Add at least one disk on the virtio bus to enable Block Multi-Queue.
|
noVirtioTip: Add at least one disk on the virtio bus to enable Block Multi-Queue.
|
||||||
ioThreadsPolicy:
|
ioThreadsPolicy:
|
||||||
label: I/O Threads Policy
|
label: I/O Threads Policy
|
||||||
tip: "How libvirt allocates I/O threads. \"Shared\" uses one thread for all disks; \"Auto\" pools threads round-robin (up to ~2x vCPUs); \"Supplemental Pool\" lets you pick a fixed thread count and adds matching CPUs to the pod."
|
tip: '"Shared" uses one I/O thread for all disks. "Auto" spreads disks over a pool of threads. "Supplemental Pool" uses a fixed number of threads.'
|
||||||
default: "Disabled (default)"
|
default: "Disabled (default)"
|
||||||
shared: Shared
|
shared: Shared
|
||||||
auto: Auto
|
auto: Auto
|
||||||
supplementalPool: Supplemental Pool
|
supplementalPool: Supplemental Pool
|
||||||
autoEnabledTip: A disk requests a dedicated I/O thread, so the policy is enabled (defaults to "Shared").
|
autoEnabledTip: A volume asks for a dedicated I/O thread, so the Shared policy will be used.
|
||||||
ioThreadCount:
|
ioThreadCount:
|
||||||
label: I/O Thread Count
|
label: I/O Thread Count
|
||||||
tip: Number of I/O threads for the supplemental pool. 4-8 is a common range depending on workload.
|
tip: Number of I/O threads in the supplemental pool.
|
||||||
shareable:
|
shareable:
|
||||||
label: Shareable
|
label: Shareable
|
||||||
tip: Allow multiple virtual machines to attach and write to this volume simultaneously.
|
tip: Allow multiple virtual machines to attach and write to this volume simultaneously.
|
||||||
|
|||||||
@@ -290,6 +290,14 @@ export default {
|
|||||||
return this.osType === 'windows';
|
return this.osType === 'windows';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasVirtioDisk() {
|
||||||
|
return (this.diskRows || []).some((R) => R.type === 'disk' && R.bus === 'virtio');
|
||||||
|
},
|
||||||
|
|
||||||
|
hasDedicatedIOThread() {
|
||||||
|
return (this.diskRows || []).some((R) => R.dedicatedIOThread);
|
||||||
|
},
|
||||||
|
|
||||||
needNewSecret() {
|
needNewSecret() {
|
||||||
// When creating a template it is always necessary to create a new secret.
|
// When creating a template it is always necessary to create a new secret.
|
||||||
return this.isCreate || this.showYaml ? false : this.resourceType === HCI.VM_VERSION;
|
return this.isCreate || this.showYaml ? false : this.resourceType === HCI.VM_VERSION;
|
||||||
|
|||||||
Reference in New Issue
Block a user