Merge pull request #145 from a110605/issue_7572

External/third-party storage UI change
This commit is contained in:
Andy Lee 2025-02-19 17:04:50 +08:00 committed by GitHub
commit 7c2317fae9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 128 additions and 15 deletions

View File

@ -51,7 +51,8 @@ const featuresV150 = [
'tpmPersistentState', 'tpmPersistentState',
'efiPersistentState', 'efiPersistentState',
'untaggedNetworkSetting', 'untaggedNetworkSetting',
'skipSingleReplicaDetachedVol' 'skipSingleReplicaDetachedVol',
'thirdPartyStorage'
]; ];
export const RELEASE_FEATURES = { export const RELEASE_FEATURES = {

View File

@ -31,7 +31,8 @@ import {
FINGERPRINT, FINGERPRINT,
IMAGE_PROGRESS, IMAGE_PROGRESS,
SNAPSHOT_TARGET_VOLUME, SNAPSHOT_TARGET_VOLUME,
IMAGE_VIRTUAL_SIZE IMAGE_VIRTUAL_SIZE,
IMAGE_STORAGE_CLASS
} from './table-headers'; } from './table-headers';
const TEMPLATE = HCI.VM_VERSION; const TEMPLATE = HCI.VM_VERSION;
@ -221,6 +222,7 @@ export function init($plugin, store) {
STATE, STATE,
NAME_COL, NAME_COL,
NAMESPACE_COL, NAMESPACE_COL,
IMAGE_STORAGE_CLASS,
IMAGE_PROGRESS, IMAGE_PROGRESS,
IMAGE_DOWNLOAD_SIZE, IMAGE_DOWNLOAD_SIZE,
IMAGE_VIRTUAL_SIZE, IMAGE_VIRTUAL_SIZE,

View File

@ -15,6 +15,7 @@ export const HCI = {
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName', TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
CREATOR: 'harvesterhci.io/creator', CREATOR: 'harvesterhci.io/creator',
OS: 'harvesterhci.io/os', OS: 'harvesterhci.io/os',
GOLDEN_IMAGE: 'harvesterhci.io/goldenImage',
NETWORK_TYPE: 'network.harvesterhci.io/type', NETWORK_TYPE: 'network.harvesterhci.io/type',
VM_NAME: 'harvesterhci.io/vmName', VM_NAME: 'harvesterhci.io/vmName',
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix', VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',

View File

@ -88,3 +88,13 @@ export const MACHINE_POOLS = {
align: 'center', align: 'center',
width: 100, width: 100,
}; };
// The STORAGE_CLASS column in VM image list page
export const IMAGE_STORAGE_CLASS = {
name: 'imageStorageClass',
labelKey: 'harvester.tableHeaders.storageClass',
sort: 'imageStorageClass',
value: 'imageStorageClass',
align: 'left',
width: 200,
};

View File

@ -61,9 +61,12 @@ export default {
images: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE }), images: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE }),
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }), storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
}); });
this['storageClassName'] = this.storageClassName || this.defaultStorageClassName(); this['storageClassName'] = this.storageClassName || this.defaultStorageClassName();
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE); this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
this.storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
const { securityParameters } = this.value.spec; const { securityParameters } = this.value.spec;
// edit and view mode should show the source image // edit and view mode should show the source image
@ -98,14 +101,15 @@ export default {
} }
return { return {
selectedImage: null, selectedImage: null,
images: [], storageClasses: [],
url: this.value.spec.url, images: [],
files: [], url: this.value.spec.url,
resource: '', files: [],
headers: {}, resource: '',
fileUrl: '', headers: {},
file: '', fileUrl: '',
file: '',
}; };
}, },
@ -158,9 +162,10 @@ export default {
storageClassOptions() { storageClassOptions() {
const storages = this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT ? this.encryptedStorageClasses : this.nonEncryptedStorageClasses; const storages = this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT ? this.encryptedStorageClasses : this.nonEncryptedStorageClasses;
const filteredStorages = this.value.thirdPartyStorageFeatureEnabled ? storages.filter((s) => !s.parameters?.backingImage) : storages
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) ;
return storages return filteredStorages
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) // Lvm storage is not supported.
.map((s) => { .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;
@ -178,6 +183,9 @@ export default {
set(nue) { set(nue) {
this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue; this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue;
if (this.value.thirdPartyStorageFeatureEnabled) {
this.value.spec.targetStorageClassName = nue;
}
} }
}, },
sourceImageOptions() { sourceImageOptions() {
@ -256,6 +264,13 @@ export default {
} else { // URL / FILE / DECRYPT should use default storage class } else { // URL / FILE / DECRYPT should use default storage class
this.storageClassName = this.defaultStorageClassName(); this.storageClassName = this.defaultStorageClassName();
} }
},
'storageClassName'(neu) {
const storageClass = this.storages.find((s) => s.id === neu);
if (storageClass && this.value.thirdPartyStorageFeatureEnabled) {
this.value.spec.backend = storageClass.isLonghornV1 ? 'backingimage' : 'cdi';
}
} }
}, },

View File

@ -109,6 +109,10 @@ export default {
return false; return false;
} }
if (pvc.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] === 'true') {
return false;
}
if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) { if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) {
isBeingUsed = false; isBeingUsed = false;
} else if (pvc.attachVM) { } else if (pvc.attachVM) {

View File

@ -90,7 +90,7 @@ export default {
imagesOption() { imagesOption() {
return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => { return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => {
return { return {
label: `${ I.metadata.namespace }/${ I.spec.displayName }`, label: this.imageOptionLabel(I),
value: I.id value: I.id
}; };
}); });
@ -116,8 +116,42 @@ export default {
}); });
}, },
thirdPartyStorageEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
},
isLonghornV2() { isLonghornV2() {
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2; return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
},
selectedImage() {
return this.$store.getters['harvester/all'](HCI.IMAGE)?.find( (I) => this.value.image === I.id);
},
imageVirtualSize() {
return this.selectedImage?.virtualSize;
},
diskSize() {
const size = this.value?.size || '0';
return `${ size.replace('Gi', '') } GB`;
},
imageVirtualSizeInByte() {
return Math.max(this.selectedImage?.status?.size, this.selectedImage?.status?.virtualSize);
},
diskSizeInByte() {
return parseSi(this.value?.size || '0');
},
showDiskTooSmallError() {
if (!this.thirdPartyStorageEnabled ) {
return false;
}
return this.imageVirtualSizeInByte > this.diskSizeInByte;
} }
}, },
@ -158,7 +192,17 @@ export default {
}, },
methods: { methods: {
imageOptionLabel(image) {
let label = `${ image.metadata.namespace }/${ image.spec.displayName }`;
if (this.thirdPartyStorageEnabled) {
label += ` (${ image.imageStorageClass } / ${ image.virtualSize })`;
}
return label;
},
update() { update() {
this.value.hasDiskError = this.showDiskTooSmallError;
this.$emit('update'); this.$emit('update');
}, },
@ -341,5 +385,10 @@ export default {
class="mb-20" class="mb-20"
:label="value.volumeBackups.error.message" :label="value.volumeBackups.error.message"
/> />
<Banner
v-if="!isView && showDiskTooSmallError"
color="error"
:label="t('harvester.virtualMachine.volume.vmImageVolumeTip', {diskSize: diskSize, imageVirtualSize: imageVirtualSize})"
/>
</div> </div>
</template> </template>

View File

@ -195,7 +195,12 @@ export default {
return true; return true;
}, },
isValidationPassed() {
// check if any disk hasDiskError is true
const hasError = this.diskRows.some((disk) => disk.hasDiskError === true);
return !hasError;
},
showCpuPinningBanner() { showCpuPinningBanner() {
if (!this.value.cpuPinningFeatureEnabled) { if (!this.value.cpuPinningFeatureEnabled) {
return false; return false;
@ -489,6 +494,7 @@ export default {
:resource="value" :resource="value"
:cancel-event="true" :cancel-event="true"
:mode="mode" :mode="mode"
:validation-passed="isValidationPassed"
:can-yaml="isSingle ? true : false" :can-yaml="isSingle ? true : false"
:errors="errors" :errors="errors"
:generate-yaml="generateYaml" :generate-yaml="generateYaml"

View File

@ -630,6 +630,7 @@ harvester:
setFirst: Set as root volume setFirst: Set as root volume
saveVolume: Update Volume saveVolume: Update Volume
encryption: Encryption encryption: Encryption
vmImageVolumeTip: Disk size ({diskSize}) should greater than selected image virtual size ({imageVirtualSize})
lockTooltip: lockTooltip:
all: All volumes are encrypted. all: All volumes are encrypted.
partial: Some volumes are encrypted. partial: Some volumes are encrypted.

View File

@ -9,6 +9,7 @@ import HarvesterVolumeState from '../formatters/HarvesterVolumeState';
import { allSettled } from '../utils/promise'; import { allSettled } from '../utils/promise';
import { HCI, VOLUME_SNAPSHOT } from '../types'; import { HCI, VOLUME_SNAPSHOT } from '../types';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
const schema = { const schema = {
id: HCI.VOLUME, id: HCI.VOLUME,
@ -58,8 +59,10 @@ export default {
if (!pvcSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) { if (!pvcSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false }); this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
} }
// we only show the non golden image PVCs in the list
const pvcs = hash.pvcs.filter((pvc) => pvc.metadata.annotations[HCI_ANNOTATIONS.GOLDEN_IMAGE] !== 'true');
this.rows = hash.pvcs; this.rows = pvcs;
}, },
data() { data() {
@ -92,7 +95,8 @@ export default {
{ {
name: 'storageClass', name: 'storageClass',
labelKey: 'tableHeaders.storageClass', labelKey: 'tableHeaders.storageClass',
value: 'spec.storageClassName' value: 'spec.storageClassName',
sort: 'spec.storageClassName'
}, },
{ {
name: 'AttachedVM', name: 'AttachedVM',

View File

@ -62,6 +62,10 @@ export default class HciStorageClass extends StorageClass {
return this.parameters?.encrypted === 'true'; return this.parameters?.encrypted === 'true';
} }
get isLonghornV1() {
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V1;
}
get isLonghornV2() { get isLonghornV2() {
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2; return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2;
} }
@ -73,4 +77,8 @@ export default class HciStorageClass extends StorageClass {
get volumeEncryptionFeatureEnabled() { get volumeEncryptionFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption'); return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
} }
get thirdPartyStorageFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
}
} }

View File

@ -199,6 +199,10 @@ export default class HciVmImage extends HarvesterResource {
return `${ this.metadata.namespace }/${ this.spec.displayName }`; return `${ this.metadata.namespace }/${ this.spec.displayName }`;
} }
get imageStorageClass() {
return this?.metadata?.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] || '';
}
get imageMessage() { get imageMessage() {
if (this.uploadError) { if (this.uploadError) {
return ucFirst(this.uploadError); return ucFirst(this.uploadError);
@ -385,6 +389,10 @@ export default class HciVmImage extends HarvesterResource {
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption'); return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
} }
get thirdPartyStorageFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
}
download() { download() {
window.location.href = this.links.download; window.location.href = this.links.download;
} }

View File

@ -1183,6 +1183,10 @@ export default class VirtVm extends HarvesterResource {
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState'); return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
} }
get thirdPartyStorageFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
}
setInstanceLabels(val) { setInstanceLabels(val) {
if ( !this.spec?.template?.metadata?.labels ) { if ( !this.spec?.template?.metadata?.labels ) {
set(this, 'spec.template.metadata.labels', {}); set(this, 'spec.template.metadata.labels', {});