chore: disable vmstate-persistence and longhorn-static StorageClasses (#377)

* chore: disable vmstate-persistence and longhorn-static StorageClasses

Signed-off-by: Nick Chung <nick.chung@suse.com>

* chore: allow internal storage class deletions in image and volumn

Signed-off-by: Nick Chung <nick.chung@suse.com>

* chore: remove deletion tooltips in image and volume pages

Signed-off-by: Nick Chung <nick.chung@suse.com>

* chore: rollback style changes of image and volume lists

Signed-off-by: Nick Chung <nick.chung@suse.com>

---------

Signed-off-by: Nick Chung <nick.chung@suse.com>
This commit is contained in:
Nick Chung 2025-07-16 18:21:06 +08:00 committed by GitHub
parent 193d4536e3
commit b775ce5f50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 114 additions and 7 deletions

View File

@ -17,4 +17,9 @@ export const VOLUME_MODE = {
export const NETWORK_PROTOCOL = { export const NETWORK_PROTOCOL = {
IPv4: 'IPv4', IPv4: 'IPv4',
IPv6: 'IPv6', IPv6: 'IPv6',
}
export const INTERNAL_STORAGE_CLASS = {
VMSTATE_PERSISTENCE: 'vmstate-persistence',
LONGHORN_STATIC: 'longhorn-static',
}; };

View File

@ -10,6 +10,7 @@ import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect'; import LabeledSelect from '@shell/components/form/LabeledSelect';
import { NAMESPACE, STORAGE_CLASS } from '@shell/config/types'; import { NAMESPACE, STORAGE_CLASS } from '@shell/config/types';
import { allHash } from '@shell/utils/promise'; import { allHash } from '@shell/utils/promise';
import { isInternalStorageClass } from '../utils/storage-class';
export default { export default {
name: 'HarvesterExportImageDialog', name: 'HarvesterExportImageDialog',
@ -105,11 +106,17 @@ export default {
} }
const options = storages.filter((s) => !s.parameters?.backingImage).map((s) => { 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 { return {
label, label,
value: s.name, value: s.name,
disabled: isInternal,
}; };
}) || []; }) || [];

View File

@ -17,6 +17,7 @@ import { VM_IMAGE_FILE_FORMAT } from '../validators/vm-image';
import { OS } from '../mixins/harvester-vm'; import { OS } from '../mixins/harvester-vm';
import { HCI } from '../types'; import { HCI } from '../types';
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass'; import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
import { isInternalStorageClass } from '../utils/storage-class';
const ENCRYPT = 'encrypt'; const ENCRYPT = 'encrypt';
const DECRYPT = 'decrypt'; const DECRYPT = 'decrypt';
@ -168,11 +169,18 @@ export default {
return filteredStorages return filteredStorages
.map((s) => { .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 { return {
label, label,
value: s.name, value: s.name,
disabled
}; };
}) || []; }) || [];
}, },

View File

@ -23,6 +23,7 @@ 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'; import { VOLUME_MODE } from '@pkg/harvester/config/types';
import { isInternalStorageClass } from '../utils/storage-class';
export default { export default {
name: 'HarvesterVolume', name: 'HarvesterVolume',
@ -206,11 +207,18 @@ export default {
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; 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 { return {
label, label,
value: s.name, value: s.name,
disabled
}; };
}) || []; }) || [];
}, },

View File

@ -13,6 +13,7 @@ import { ucFirst } from '@shell/utils/string';
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 { isInternalStorageClass } from '../../../../utils/storage-class';
import { VOLUME_MODE } from '@pkg/harvester/config/types'; import { VOLUME_MODE } from '@pkg/harvester/config/types';
export default { export default {
@ -118,11 +119,18 @@ export default {
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; 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 { return {
label, label,
value: s.name, value: s.name,
disabled
}; };
}) || []; }) || [];
}, },

View File

@ -1369,6 +1369,10 @@ harvester:
allowedTopologies: allowedTopologies:
title: Allowed Topologies title: Allowed Topologies
tooltip: Allowed Topologies helps scheduling virtual machines on hosts which match all of below expressions. 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: vlanConfig:
title: Network Configuration title: Network Configuration

View File

@ -83,6 +83,34 @@ export default {
:rows="rows" :rows="rows"
:schema="schema" :schema="schema"
:headers="headers" :headers="headers"
/> >
<template #cell:name="{ row }">
<td>
<div>
<router-link
v-if="row?.detailLocation"
:to="row.detailLocation"
>
{{ row.nameDisplay }}
<i
v-if="row.isInternalStorageClass && typeof row.isInternalStorageClass === 'function' ? row.isInternalStorageClass() : false"
v-clean-tooltip="t('harvester.storage.internal.cannotDeleteOrDefaultTooltip')"
class="icon icon-info text-info"
style="margin-left: 0.4em;"
/>
</router-link>
<span v-else>
{{ row.nameDisplay }}
<i
v-if="row.isInternalStorageClass && typeof row.isInternalStorageClass === 'function' ? row.isInternalStorageClass() : false"
v-clean-tooltip="t('harvester.storage.internal.cannotDeleteOrDefaultTooltip')"
class="icon icon-info text-info"
style="margin-left: 0.4em;"
/>
</span>
</div>
</td>
</template>
</ResourceTable>
</div> </div>
</template> </template>

View File

@ -3,6 +3,8 @@ import ResourceTable from '@shell/components/ResourceTable';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import { defaultTableSortGenerationFn } from '@shell/components/ResourceTable.vue'; import { defaultTableSortGenerationFn } from '@shell/components/ResourceTable.vue';
import FilterLabel from '../components/FilterLabel'; import FilterLabel from '../components/FilterLabel';
import { HCI as HCI_ANNOTATIONS } from '../config/labels-annotations';
import { isInternalStorageClass } from '../utils/storage-class';
export default { export default {
name: 'ListHarvesterImage', name: 'ListHarvesterImage',
@ -53,6 +55,13 @@ export default {
return base; return base;
}, },
isInternalStorageClass(row) {
const name = row?.spec?.targetStorageClassName ||
row?.metadata?.annotations?.[HCI_ANNOTATIONS.STORAGE_CLASS];
return isInternalStorageClass(name);
},
} }
}; };
</script> </script>

View File

@ -8,6 +8,7 @@ import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
import HarvesterVolumeState from '../formatters/HarvesterVolumeState'; 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 { INTERNAL_STORAGE_CLASS } from '../config/types';
const schema = { const schema = {
id: HCI.VOLUME, id: HCI.VOLUME,
@ -130,6 +131,10 @@ export default {
getVMName(row) { getVMName(row) {
return row.attachVM?.metadata?.name || ''; return row.attachVM?.metadata?.name || '';
},
isInternalStorageClass(storageClassName) {
return this.$store.getters['type-map/labelFor'](INTERNAL_STORAGE_CLASS, storageClassName);
} }
}, },
@ -170,7 +175,7 @@ export default {
</router-link> </router-link>
</div> </div>
</template> </template>
<template #col:name="{row}"> <template #col:name="{ row }">
<td> <td>
<span> <span>
<router-link <router-link

View File

@ -4,6 +4,7 @@ import { HCI } from '../../types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester'; import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { LONGHORN_DRIVER } from '@shell/config/types'; import { LONGHORN_DRIVER } from '@shell/config/types';
import { DATA_ENGINE_V1, DATA_ENGINE_V2 } from '../../models/harvester/persistentvolumeclaim'; import { DATA_ENGINE_V1, DATA_ENGINE_V2 } from '../../models/harvester/persistentvolumeclaim';
import { isInternalStorageClass } from '../../utils/storage-class';
export const LVM_DRIVER = 'lvm.driver.harvesterhci.io'; export const LVM_DRIVER = 'lvm.driver.harvesterhci.io';
@ -85,4 +86,22 @@ export default class HciStorageClass extends StorageClass {
get thirdPartyStorageFeatureEnabled() { get thirdPartyStorageFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage'); return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
} }
isInternalStorageClass() {
return isInternalStorageClass(this.metadata?.name);
}
get availableActions() {
let out = super.availableActions || [];
out = out.map((action) => {
if ((action.action === 'setDefault' || action.action === 'setAsDefault' || action.action === 'promptRemove') && this.isInternalStorageClass()) {
return { ...action, enabled: false };
}
return action;
});
return out;
}
} }

View File

@ -0,0 +1,6 @@
import { INTERNAL_STORAGE_CLASS } from '../config/types';
export function isInternalStorageClass(name) {
return name === INTERNAL_STORAGE_CLASS.VMSTATE_PERSISTENCE ||
name === INTERNAL_STORAGE_CLASS.LONGHORN_STATIC;
}