diff --git a/pkg/harvester/config/types.js b/pkg/harvester/config/types.js index b336ca4a..460780c9 100644 --- a/pkg/harvester/config/types.js +++ b/pkg/harvester/config/types.js @@ -17,4 +17,9 @@ export const VOLUME_MODE = { export const NETWORK_PROTOCOL = { IPv4: 'IPv4', IPv6: 'IPv6', +} + +export const INTERNAL_STORAGE_CLASS = { + VMSTATE_PERSISTENCE: 'vmstate-persistence', + LONGHORN_STATIC: 'longhorn-static', }; diff --git a/pkg/harvester/dialog/HarvesterExportImageDialog.vue b/pkg/harvester/dialog/HarvesterExportImageDialog.vue index fac32d18..4375fb40 100644 --- a/pkg/harvester/dialog/HarvesterExportImageDialog.vue +++ b/pkg/harvester/dialog/HarvesterExportImageDialog.vue @@ -10,6 +10,7 @@ import { LabeledInput } from '@components/Form/LabeledInput'; import LabeledSelect from '@shell/components/form/LabeledSelect'; import { NAMESPACE, STORAGE_CLASS } from '@shell/config/types'; import { allHash } from '@shell/utils/promise'; +import { isInternalStorageClass } from '../utils/storage-class'; export default { name: 'HarvesterExportImageDialog', @@ -105,11 +106,17 @@ export default { } const options = storages.filter((s) => !s.parameters?.backingImage).map((s) => { - const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + let label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + const isInternal = isInternalStorageClass(s.metadata?.name); + + if (isInternal) { + label += ` (${ this.t('harvester.storage.internal.label') })`; + } return { label, - value: s.name, + value: s.name, + disabled: isInternal, }; }) || []; diff --git a/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue b/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue index 0bea5572..e2068979 100644 --- a/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue +++ b/pkg/harvester/edit/harvesterhci.io.virtualmachineimage.vue @@ -17,6 +17,7 @@ import { VM_IMAGE_FILE_FORMAT } from '../validators/vm-image'; import { OS } from '../mixins/harvester-vm'; import { HCI } from '../types'; import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass'; +import { isInternalStorageClass } from '../utils/storage-class'; const ENCRYPT = 'encrypt'; const DECRYPT = 'decrypt'; @@ -168,11 +169,18 @@ export default { return filteredStorages .map((s) => { - const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + let label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + let disabled = false; + + if (isInternalStorageClass(s.name)) { + label += ` (${ this.t('harvester.storage.internal.label') })`; + disabled = true; + } return { label, value: s.name, + disabled }; }) || []; }, diff --git a/pkg/harvester/edit/harvesterhci.io.volume.vue b/pkg/harvester/edit/harvesterhci.io.volume.vue index e3b7f26e..e312e89d 100644 --- a/pkg/harvester/edit/harvesterhci.io.volume.vue +++ b/pkg/harvester/edit/harvesterhci.io.volume.vue @@ -23,6 +23,7 @@ import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass'; import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim'; import { GIBIBYTE } from '../utils/unit'; import { VOLUME_MODE } from '@pkg/harvester/config/types'; +import { isInternalStorageClass } from '../utils/storage-class'; export default { name: 'HarvesterVolume', @@ -206,11 +207,18 @@ export default { storageClassOptions() { return this.storageClasses.filter((s) => !s.parameters?.backingImage).map((s) => { - const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + let label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + let disabled = false; + + if (isInternalStorageClass(s.name)) { + label += ` (${ this.t('harvester.storage.internal.label') })`; + disabled = true; + } return { label, value: s.name, + disabled }; }) || []; }, diff --git a/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/volume.vue b/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/volume.vue index 2c67c906..0a4f2cd0 100644 --- a/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/volume.vue +++ b/pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/volume.vue @@ -13,6 +13,7 @@ import { ucFirst } from '@shell/utils/string'; import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storageclass'; import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim'; import { GIBIBYTE } from '../../../../utils/unit'; +import { isInternalStorageClass } from '../../../../utils/storage-class'; import { VOLUME_MODE } from '@pkg/harvester/config/types'; export default { @@ -118,11 +119,18 @@ export default { storageClassOptions() { return this.storageClasses.filter((s) => !s.parameters?.backingImage).map((s) => { - const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + let label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name; + let disabled = false; + + if (isInternalStorageClass(s.name)) { + label += ` (${ this.t('harvester.storage.internal.label') })`; + disabled = true; + } return { label, value: s.name, + disabled }; }) || []; }, diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index 7c08966f..d0e76f6b 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -1369,6 +1369,10 @@ harvester: allowedTopologies: title: Allowed Topologies tooltip: Allowed Topologies helps scheduling virtual machines on hosts which match all of below expressions. + internal: + label: Internal Storage Class + cannotDeleteTooltip: Internal storage class volumes cannot be deleted + cannotDeleteOrDefaultTooltip: Internal storage classes cannot be deleted or set as default vlanConfig: title: Network Configuration diff --git a/pkg/harvester/list/harvesterhci.io.storage.vue b/pkg/harvester/list/harvesterhci.io.storage.vue index a05c5899..33dca1a7 100644 --- a/pkg/harvester/list/harvesterhci.io.storage.vue +++ b/pkg/harvester/list/harvesterhci.io.storage.vue @@ -83,6 +83,34 @@ export default { :rows="rows" :schema="schema" :headers="headers" - /> + > + + diff --git a/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue b/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue index 0654b89d..6478587b 100644 --- a/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue +++ b/pkg/harvester/list/harvesterhci.io.virtualmachineimage.vue @@ -3,6 +3,8 @@ import ResourceTable from '@shell/components/ResourceTable'; import { Banner } from '@components/Banner'; import { defaultTableSortGenerationFn } from '@shell/components/ResourceTable.vue'; import FilterLabel from '../components/FilterLabel'; +import { HCI as HCI_ANNOTATIONS } from '../config/labels-annotations'; +import { isInternalStorageClass } from '../utils/storage-class'; export default { name: 'ListHarvesterImage', @@ -53,6 +55,13 @@ export default { return base; }, + + isInternalStorageClass(row) { + const name = row?.spec?.targetStorageClassName || + row?.metadata?.annotations?.[HCI_ANNOTATIONS.STORAGE_CLASS]; + + return isInternalStorageClass(name); + }, } }; diff --git a/pkg/harvester/list/harvesterhci.io.volume.vue b/pkg/harvester/list/harvesterhci.io.volume.vue index b7b3ebfc..afc975a3 100644 --- a/pkg/harvester/list/harvesterhci.io.volume.vue +++ b/pkg/harvester/list/harvesterhci.io.volume.vue @@ -8,6 +8,7 @@ import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers'; import HarvesterVolumeState from '../formatters/HarvesterVolumeState'; import { allSettled } from '../utils/promise'; import { HCI, VOLUME_SNAPSHOT } from '../types'; +import { INTERNAL_STORAGE_CLASS } from '../config/types'; const schema = { id: HCI.VOLUME, @@ -130,6 +131,10 @@ export default { getVMName(row) { return row.attachVM?.metadata?.name || ''; + }, + + isInternalStorageClass(storageClassName) { + return this.$store.getters['type-map/labelFor'](INTERNAL_STORAGE_CLASS, storageClassName); } }, @@ -170,7 +175,7 @@ export default { -