Add volumeMode dropdown for third-party storage (#197)

Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
Andy Lee 2025-03-12 14:25:37 +08:00 committed by GitHub
parent 374b904191
commit 90c923b480
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 92 additions and 6 deletions

View File

@ -50,6 +50,7 @@ export const HCI = {
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io', STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental', ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error', VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
VOLUME_FOR_VM: 'harvesterhci.io/volumeForVirtualMachine',
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm', KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx', KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
NODE_MANUFACTURER: 'manufacturer', NODE_MANUFACTURER: 'manufacturer',

View File

@ -7,3 +7,8 @@ export const NETWORK_TYPE = {
L2VLAN: 'L2VlanNetwork', L2VLAN: 'L2VlanNetwork',
UNTAGGED: 'UntaggedNetwork' UNTAGGED: 'UntaggedNetwork'
}; };
export const VOLUME_MODE = {
BLOCK: 'Block',
FILE_SYSTEM: 'Filesystem'
};

View File

@ -22,6 +22,7 @@ import { HCI, VOLUME_SNAPSHOT } from '../types';
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass'; import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim'; import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
import { GIBIBYTE } from '../utils/unit'; import { GIBIBYTE } from '../utils/unit';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
export default { export default {
name: 'HarvesterVolume', name: 'HarvesterVolume',
@ -73,7 +74,8 @@ export default {
data() { data() {
if (this.mode === _CREATE) { if (this.mode === _CREATE) {
this.value.spec.volumeMode = 'Block'; // default volumeMode to Block
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
this.value.spec.accessModes = ['ReadWriteMany']; this.value.spec.accessModes = ['ReadWriteMany'];
} }
@ -126,6 +128,10 @@ export default {
return InterfaceOption; return InterfaceOption;
}, },
volumeModeOptions() {
return Object.values(VOLUME_MODE);
},
imageOption() { imageOption() {
return sortBy( return sortBy(
this.images this.images
@ -179,6 +185,25 @@ export default {
return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS); return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
}, },
isLonghornStorageClass() {
const selectedSC = this.storageClasses.find((sc) => sc.name === this.value?.spec?.storageClassName) || {};
return selectedSC && selectedSC.isLonghorn;
},
showVolumeMode() {
// we won't let user choose volumeMode if source = vmimage
if (!this.value.thirdPartyStorageFeatureEnabled || this.isVMImage || !!this.value?.spec?.storageClassName === false) {
return false;
}
if (this.isLonghornStorageClass) {
return false;
}
return true;
},
storageClassOptions() { storageClassOptions() {
return this.storageClasses.filter((s) => !s.parameters?.backingImage).map((s) => { return this.storageClasses.filter((s) => !s.parameters?.backingImage).map((s) => {
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
@ -237,7 +262,43 @@ export default {
} }
}, },
watch: {
'source'(neu) {
if (neu === 'url') {
this.setBlockVolumeMode();
this.deleteVolumeForVmAnnotation();
}
},
'value.spec.storageClassName'() {
if (this.isLonghornStorageClass) {
this.setBlockVolumeMode();
this.deleteVolumeForVmAnnotation();
}
},
'value.spec.volumeMode'(neu) {
if (neu === VOLUME_MODE.FILE_SYSTEM) {
this.setVolumeForVmAnnotation();
} else if (neu === VOLUME_MODE.BLOCK ) {
this.deleteVolumeForVmAnnotation();
}
}
},
methods: { methods: {
setBlockVolumeMode() {
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
},
setVolumeForVmAnnotation() {
this.value.setAnnotation(HCI_ANNOTATIONS.VOLUME_FOR_VM, 'true');
},
deleteVolumeForVmAnnotation() {
if (this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_FOR_VM]) {
delete this.value.metadata.annotations[HCI_ANNOTATIONS.VOLUME_FOR_VM];
}
},
getAccessMode() { getAccessMode() {
if (!this.longhornV2LVMSupport) { if (!this.longhornV2LVMSupport) {
return ['ReadWriteMany']; return ['ReadWriteMany'];
@ -359,7 +420,7 @@ export default {
/> />
<LabeledSelect <LabeledSelect
v-if="source === 'blank'" v-if="isBlank"
v-model:value="value.spec.storageClassName" v-model:value="value.spec.storageClassName"
:options="storageClassOptions" :options="storageClassOptions"
:label="t('harvester.storage.storageClass.label')" :label="t('harvester.storage.storageClass.label')"
@ -369,6 +430,18 @@ export default {
@update:value="update" @update:value="update"
/> />
<LabeledSelect
v-if="showVolumeMode"
v-model:value="value.spec.volumeMode"
:label="t('harvester.volume.volumeMode')"
:options="volumeModeOptions"
required
:disabled="!isCreate"
:mode="mode"
class="mb-20"
@update:value="update"
/>
<UnitInput <UnitInput
v-model:value="storage" v-model:value="storage"
:label="t('harvester.volume.size')" :label="t('harvester.volume.size')"

View File

@ -17,6 +17,7 @@ import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
import { SOURCE_TYPE } from '../../../config/harvester-map'; import { SOURCE_TYPE } from '../../../config/harvester-map';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester'; import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
import { HCI } from '../../../types'; import { HCI } from '../../../types';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
export default { export default {
emits: ['update:value'], emits: ['update:value'],
@ -62,7 +63,7 @@ export default {
customVolumeMode: { customVolumeMode: {
type: String, type: String,
default: 'Block' default: VOLUME_MODE.Block
}, },
customAccessMode: { customAccessMode: {

View File

@ -791,6 +791,7 @@ harvester:
datasource: Data Source datasource: Data Source
details: Details details: Details
size: Size size: Size
volumeMode: Volume Mode
source: Source source: Source
kind: Kind kind: Kind
sourceOptions: sourceOptions:

View File

@ -26,6 +26,7 @@ import { HCI } from '../../types';
import { parseVolumeClaimTemplates } from '../../utils/vm'; import { parseVolumeClaimTemplates } from '../../utils/vm';
import impl, { QGA_JSON, USB_TABLET } from './impl'; import impl, { QGA_JSON, USB_TABLET } from './impl';
import { GIBIBYTE } from '../../utils/unit'; import { GIBIBYTE } from '../../utils/unit';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine'; const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
@ -261,7 +262,7 @@ export default {
}, },
customVolumeMode() { customVolumeMode() {
return this.storageClassSetting.volumeMode || 'Block'; return this.storageClassSetting.volumeMode || VOLUME_MODE.BLOCK;
}, },
customAccessMode() { customAccessMode() {
@ -463,7 +464,7 @@ export default {
type, type,
storageClassName: '', storageClassName: '',
image: this.imageId, image: this.imageId,
volumeMode: 'Block', volumeMode: VOLUME_MODE.BLOCK,
isEncrypted, isEncrypted,
volumeBackups, volumeBackups,
}); });
@ -522,7 +523,7 @@ export default {
accessMode = pvcResource?.spec?.accessModes?.[0] || 'ReadWriteMany'; accessMode = pvcResource?.spec?.accessModes?.[0] || 'ReadWriteMany';
size = pvcResource?.spec?.resources?.requests?.storage || '10Gi'; size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
storageClassName = pvcResource?.spec?.storageClassName; storageClassName = pvcResource?.spec?.storageClassName;
volumeMode = pvcResource?.spec?.volumeMode || 'Block'; volumeMode = pvcResource?.spec?.volumeMode || VOLUME_MODE.BLOCK;
volumeName = pvcResource?.metadata?.name || ''; volumeName = pvcResource?.metadata?.name || '';
} }

View File

@ -62,6 +62,10 @@ export default class HciStorageClass extends StorageClass {
return this.parameters?.encrypted === 'true'; return this.parameters?.encrypted === 'true';
} }
get isLonghorn() {
return this.provisioner === LONGHORN_DRIVER;
}
get isLonghornV1() { get isLonghornV1() {
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V1; return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V1;
} }