Revert "feat: support filesystem disk for VM (#898)" (#909)

This reverts commit b34d618c7c0ab0447b26e2300c8141aefbb20427.

Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
Andy Lee 2026-06-02 11:21:40 +08:00 committed by GitHub
parent b34d618c7c
commit 0908c7fc6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 2271 additions and 443 deletions

View File

@ -66,12 +66,10 @@ const FEATURE_FLAGS = {
'instanceManagerResourcesSetting', 'instanceManagerResourcesSetting',
'rwxNetworkSetting', 'rwxNetworkSetting',
'createPVCWithDataVolume', 'createPVCWithDataVolume',
'clusterPodSecurityStandardSetting', 'clusterPodSecurityStandardSetting'
], ],
'v1.8.1': [], 'v1.8.1': [],
'v1.9.0': [ 'v1.9.0': [],
'supportFilesystem',
],
}; };
const generateFeatureFlags = () => { const generateFeatureFlags = () => {

View File

@ -46,9 +46,3 @@ export const CDI_POPULATOR_KIND = {
VOLUME_IMPORT_SOURCE: 'VolumeImportSource', VOLUME_IMPORT_SOURCE: 'VolumeImportSource',
VOLUME_CLONE_SOURCE: 'VolumeCloneSource', VOLUME_CLONE_SOURCE: 'VolumeCloneSource',
}; };
export const FILESYSTEM_SOURCE_TYPE = {
CONFIGMAP: 'configmap',
SECRET: 'secret',
SERVICEACCOUNT: 'serviceaccount',
};

View File

@ -26,7 +26,6 @@ import CpuMemory from './kubevirt.io.virtualmachine/VirtualMachineCpuMemory';
import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel'; import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel';
import CloudConfig from './kubevirt.io.virtualmachine/VirtualMachineCloudConfig'; import CloudConfig from './kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
import SSHKey from './kubevirt.io.virtualmachine/VirtualMachineSSHKey'; import SSHKey from './kubevirt.io.virtualmachine/VirtualMachineSSHKey';
import Filesystem from './kubevirt.io.virtualmachine/VirtualMachineFilesystem';
export default { export default {
name: 'HarvesterEditVMTemplate', name: 'HarvesterEditVMTemplate',
@ -52,7 +51,6 @@ export default {
UnitInput, UnitInput,
Banner, Banner,
KeyValue, KeyValue,
Filesystem,
}, },
mixins: [CreateEditView, VM_MIXIN], mixins: [CreateEditView, VM_MIXIN],
@ -97,10 +95,6 @@ export default {
secretNamePrefix() { secretNamePrefix() {
return this.templateValue?.metadata?.name; return this.templateValue?.metadata?.name;
}, },
filesystemEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('supportFilesystem');
},
}, },
watch: { watch: {
@ -160,7 +154,6 @@ export default {
mounted() { mounted() {
this.imageId = this.diskRows[0]?.image || ''; this.imageId = this.diskRows[0]?.image || '';
this['filesystemRows'] = this.getFilesystemRows(this.value);
}, },
methods: { methods: {
@ -356,19 +349,6 @@ export default {
</template> </template>
</Tab> </Tab>
<Tab
v-if="filesystemEnabled"
name="filesystem"
:label="t('harvester.tab.filesystem')"
:weight="-8"
>
<Filesystem
v-model:value="filesystemRows"
:mode="mode"
:namespace="templateValue.metadata.namespace"
/>
</Tab>
<Tab <Tab
name="labels" name="labels"
:label="t('generic.labels')" :label="t('generic.labels')"

View File

@ -1,310 +0,0 @@
<script>
import { mapGetters } from 'vuex';
import { Banner } from '@components/Banner';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput';
import { CONFIG_MAP, SECRET, SERVICE_ACCOUNT } from '@shell/config/types';
import { _VIEW } from '@shell/config/query-params';
import CopyToClipboard from '@shell/components/CopyToClipboard';
import MessageLink from '@shell/components/MessageLink';
import { FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types';
const MAX_FILESYSTEMS = 3;
const { CONFIGMAP: FS_TYPE_CONFIGMAP, SECRET: FS_TYPE_SECRET, SERVICEACCOUNT: FS_TYPE_SERVICEACCOUNT } = FILESYSTEM_SOURCE_TYPE;
const DEFAULT_VOLUME_NAMES = {
[FS_TYPE_CONFIGMAP]: 'appconfigfs',
[FS_TYPE_SECRET]: 'appsecretfs',
[FS_TYPE_SERVICEACCOUNT]: 'appserviceaccountfs',
};
const FS_TYPE_OPTIONS = [
{ label: 'ConfigMap', value: FS_TYPE_CONFIGMAP },
{ label: 'Secret', value: FS_TYPE_SECRET },
{ label: 'ServiceAccount', value: FS_TYPE_SERVICEACCOUNT },
];
function emptyRow() {
return {
fsType: FS_TYPE_CONFIGMAP,
volumeName: DEFAULT_VOLUME_NAMES[FS_TYPE_CONFIGMAP],
resourceName: '',
};
}
export default {
name: 'VirtualMachineFilesystem',
emits: ['update:value'],
components: {
Banner,
LabeledSelect,
LabeledInput,
CopyToClipboard,
MessageLink,
},
props: {
mode: {
type: String,
default: 'create',
},
namespace: {
type: String,
default: '',
},
value: {
type: Array,
default: () => [],
},
},
data() {
return { rows: this.value.length > 0 ? this.value.map((r) => ({ ...r })) : [emptyRow()] };
},
watch: {
value(newVal) {
if (newVal) {
const incoming = JSON.stringify(newVal);
const current = JSON.stringify(this.rows);
if (incoming !== current) {
this.rows = newVal.map((r) => ({ ...r }));
}
}
},
rows: {
deep: true,
handler(val) {
this.$emit('update:value', val.map((r) => ({ ...r })));
},
},
},
computed: {
...mapGetters({ t: 'i18n/t' }),
inStore() {
return this.$store.getters['currentProduct'].inStore;
},
configMaps() {
return this.$store.getters[`${ this.inStore }/all`](CONFIG_MAP)
.filter((cm) => !this.namespace || cm.metadata.namespace === this.namespace)
.map((cm) => ({ label: cm.metadata.name, value: cm.metadata.name }));
},
secrets() {
return this.$store.getters[`${ this.inStore }/all`](SECRET)
.filter((s) => !this.namespace || s.metadata.namespace === this.namespace)
.map((s) => ({ label: s.metadata.name, value: s.metadata.name }));
},
serviceAccounts() {
return this.$store.getters[`${ this.inStore }/all`](SERVICE_ACCOUNT)
.filter((sa) => !this.namespace || sa.metadata.namespace === this.namespace)
.map((sa) => ({ label: sa.metadata.name, value: sa.metadata.name }));
},
canAddRow() {
return this.rows.length < MAX_FILESYSTEMS;
},
isView() {
return this.mode === _VIEW;
},
completedRows() {
return this.rows.filter((r) => r.fsType && r.volumeName && r.resourceName);
},
allMountCommands() {
return this.completedRows.map((r) => this.mountCommands(r)).join('\n');
},
},
methods: {
fsTypeOptions(currentIndex) {
const usedTypes = this.rows
.filter((_, i) => i !== currentIndex)
.map((r) => r.fsType);
return FS_TYPE_OPTIONS.filter((opt) => !usedTypes.includes(opt.value));
},
resourceOptions(fsType) {
if (fsType === FS_TYPE_CONFIGMAP) return this.configMaps;
if (fsType === FS_TYPE_SECRET) return this.secrets;
if (fsType === FS_TYPE_SERVICEACCOUNT) return this.serviceAccounts;
return [];
},
onFsTypeChange(row, newType) {
row.fsType = newType;
row.volumeName = DEFAULT_VOLUME_NAMES[newType] || '';
row.resourceName = '';
},
addRow() {
if (this.canAddRow) {
const usedTypes = this.rows.map((r) => r.fsType);
const nextType = FS_TYPE_OPTIONS.find((opt) => !usedTypes.includes(opt.value))?.value || FS_TYPE_CONFIGMAP;
this.rows.push({
fsType: nextType,
volumeName: DEFAULT_VOLUME_NAMES[nextType] || '',
resourceName: '',
});
}
},
removeRow(index) {
this.rows.splice(index, 1);
},
mountCommands(row) {
const vol = row.volumeName || '<volume-name>';
return `- mkdir -p /mnt/${ vol }\n- mount -t virtiofs ${ vol } /mnt/${ vol }`;
},
},
};
</script>
<template>
<div class="vm-filesystem">
<p class="mb-20">
{{ t('harvester.virtualMachine.filesystem.description') }}
</p>
<div
v-for="(row, index) in rows"
:key="index"
class="filesystem-row mb-15"
>
<div class="row">
<div class="col span-3">
<LabeledSelect
:value="row.fsType"
:label="t('harvester.virtualMachine.filesystem.type')"
:options="fsTypeOptions(index)"
:mode="mode"
required
@update:value="onFsTypeChange(row, $event)"
/>
</div>
<div class="col span-3">
<LabeledInput
v-model:value="row.volumeName"
:label="t('harvester.virtualMachine.filesystem.volume')"
:mode="mode"
required
/>
</div>
<div class="col span-5">
<LabeledSelect
v-model:value="row.resourceName"
:label="t('harvester.virtualMachine.filesystem.resource')"
:options="resourceOptions(row.fsType)"
:mode="mode"
required
/>
</div>
<div
v-if="!isView"
class="col span-1 remove-col"
>
<button
type="button"
class="btn role-link remove-btn"
@click="removeRow(index)"
>
{{ t('generic.remove') }}
</button>
</div>
</div>
</div>
<Banner
v-if="completedRows.length > 0"
color="warning"
class="mt-10"
>
<div>
<MessageLink
:to="{ hash: '#advanced' }"
prefix-label="harvester.virtualMachine.filesystem.mountBannerHint"
middle-label="harvester.virtualMachine.filesystem.mountBannerHintLink"
suffix-label="harvester.virtualMachine.filesystem.mountBannerHintSuffix"
/>
<div class="pre-wrapper mt-10">
<pre class="mt-5 mb-0">{{ allMountCommands }}</pre>
<CopyToClipboard
:text="allMountCommands"
:show-label="false"
class="icon-btn"
action-color="bg-transparent"
/>
</div>
</div>
</Banner>
<button
v-if="!isView && canAddRow"
type="button"
class="btn role-tertiary add"
@click="addRow"
>
{{ t('harvester.virtualMachine.filesystem.add') }}
</button>
</div>
</template>
<style lang="scss" scoped>
.vm-filesystem {
padding: 10px 0;
}
.filesystem-row {
border-bottom: 1px solid var(--border);
padding-bottom: 15px;
}
.remove-col {
display: flex;
align-items: center;
justify-content: center;
}
.remove-btn {
padding: 0;
}
.pre-wrapper {
position: relative;
pre {
padding-right: 36px;
}
.icon-btn {
position: absolute;
top: 0px;
right: 5px;
padding: 2px 4px;
line-height: 1;
&:active {
background-color: var(--success) !important;
}
}
}
</style>

View File

@ -37,7 +37,6 @@ import Network from './VirtualMachineNetwork';
import Volume from './VirtualMachineVolume'; import Volume from './VirtualMachineVolume';
import SSHKey from './VirtualMachineSSHKey'; import SSHKey from './VirtualMachineSSHKey';
import Reserved from './VirtualMachineReserved'; import Reserved from './VirtualMachineReserved';
import Filesystem from './VirtualMachineFilesystem';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import MessageLink from '@shell/components/MessageLink'; import MessageLink from '@shell/components/MessageLink';
@ -73,7 +72,6 @@ export default {
Banner, Banner,
MessageLink, MessageLink,
UsbDevices, UsbDevices,
Filesystem,
}, },
mixins: [CreateEditView, VM_MIXIN], mixins: [CreateEditView, VM_MIXIN],
@ -220,9 +218,6 @@ export default {
usbPassthroughEnabled() { usbPassthroughEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough'); return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough');
}, },
filesystemEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('supportFilesystem');
},
}, },
watch: { watch: {
@ -326,7 +321,6 @@ export default {
const diskRows = this.getDiskRows(this.value); const diskRows = this.getDiskRows(this.value);
this['diskRows'] = diskRows; this['diskRows'] = diskRows;
this['filesystemRows'] = this.getFilesystemRows(this.value);
const templateId = this.$route.query.templateId; const templateId = this.$route.query.templateId;
const templateVersionId = this.$route.query.versionId; const templateVersionId = this.$route.query.versionId;
@ -789,23 +783,10 @@ export default {
/> />
</Tab> </Tab>
<Tab
v-if="filesystemEnabled"
name="filesystem"
:label="t('harvester.tab.filesystem')"
:weight="-9"
>
<Filesystem
v-model:value="filesystemRows"
:mode="mode"
:namespace="value.metadata.namespace"
/>
</Tab>
<Tab <Tab
name="labels" name="labels"
:label="t('generic.labels')" :label="t('generic.labels')"
:weight="-10" :weight="-9"
> >
<Banner color="info"> <Banner color="info">
<t k="harvester.virtualMachine.labels.banner" /> <t k="harvester.virtualMachine.labels.banner" />
@ -824,7 +805,7 @@ export default {
<Tab <Tab
name="instanceLabel" name="instanceLabel"
:label="t('harvester.tab.instanceLabel')" :label="t('harvester.tab.instanceLabel')"
:weight="-11" :weight="-10"
> >
<Banner color="info"> <Banner color="info">
<t k="harvester.virtualMachine.instanceLabels.banner" /> <t k="harvester.virtualMachine.instanceLabels.banner" />
@ -845,7 +826,7 @@ export default {
<Tab <Tab
name="annotations" name="annotations"
:label="t('harvester.tab.annotations')" :label="t('harvester.tab.annotations')"
:weight="-12" :weight="-11"
> >
<Banner color="info"> <Banner color="info">
<t k="harvester.virtualMachine.annotations.banner" /> <t k="harvester.virtualMachine.annotations.banner" />
@ -866,7 +847,7 @@ export default {
<Tab <Tab
name="advanced" name="advanced"
:label="t('harvester.tab.advanced')" :label="t('harvester.tab.advanced')"
:weight="-13" :weight="-12"
> >
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-6"> <div class="col span-6">

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ import { base64Decode } from '@shell/utils/crypto';
import { formatSi, parseSi } from '@shell/utils/units'; import { formatSi, parseSi } from '@shell/utils/units';
import { _CLONE, _CREATE, _VIEW } from '@shell/config/query-params'; import { _CLONE, _CREATE, _VIEW } from '@shell/config/query-params';
import { import {
PV, PVC, STORAGE_CLASS, NODE, SECRET, CONFIG_MAP, SERVICE_ACCOUNT, NETWORK_ATTACHMENT, NAMESPACE, LONGHORN PV, PVC, STORAGE_CLASS, NODE, SECRET, CONFIG_MAP, NETWORK_ATTACHMENT, NAMESPACE, LONGHORN
} from '@shell/config/types'; } from '@shell/config/types';
import { HOSTNAME } from '@shell/config/labels-annotations'; import { HOSTNAME } from '@shell/config/labels-annotations';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations'; import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
@ -25,7 +25,7 @@ import { HCI } from '../../types';
import { parseVolumeClaimTemplates, EMPTY_IMAGE } from '../../utils/vm'; import { parseVolumeClaimTemplates, EMPTY_IMAGE } 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, FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types'; 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';
@ -102,8 +102,6 @@ export default {
vmims: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMIM }), vmims: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMIM }),
vms: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VM }), vms: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VM }),
secrets: this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }), secrets: this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }),
configMaps: this.$store.dispatch(`${ inStore }/findAll`, { type: CONFIG_MAP }),
serviceAccounts: this.$store.dispatch(`${ inStore }/findAll`, { type: SERVICE_ACCOUNT }),
addons: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }), addons: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }),
longhornV2Engine: this.$store.dispatch(`${ inStore }/find`, { type: LONGHORN.SETTINGS, id: LONGHORN_V2_DATA_ENGINE }), longhornV2Engine: this.$store.dispatch(`${ inStore }/find`, { type: LONGHORN.SETTINGS, id: LONGHORN_V2_DATA_ENGINE }),
}; };
@ -158,7 +156,6 @@ export default {
imageId: '', imageId: '',
diskRows: [], diskRows: [],
networkRows: [], networkRows: [],
filesystemRows: [],
machineType: '', machineType: '',
machineTypes: [], machineTypes: [],
secretName: '', secretName: '',
@ -443,7 +440,6 @@ export default {
this['imageId'] = imageId; this['imageId'] = imageId;
this['diskRows'] = diskRows; this['diskRows'] = diskRows;
this['filesystemRows'] = this.getFilesystemRows(vm);
this.refreshYamlEditor(); this.refreshYamlEditor();
}, },
@ -645,80 +641,6 @@ export default {
this.parseAccessCredentials(); this.parseAccessCredentials();
this.parseNetworkRows(this.networkRows); this.parseNetworkRows(this.networkRows);
this.parseDiskRows(this.diskRows); this.parseDiskRows(this.diskRows);
this.parseFilesystemRows();
},
getFilesystemRows(vm) {
const _filesystems = vm.spec.template.spec.domain.devices?.filesystems || [];
const _volumes = vm.spec.template.spec.volumes || [];
return _filesystems.map((fs) => {
const volume = _volumes.find((v) => v.name === fs.name);
let fsType = FILESYSTEM_SOURCE_TYPE.CONFIGMAP;
let resourceName = '';
if (volume?.configMap) {
fsType = FILESYSTEM_SOURCE_TYPE.CONFIGMAP;
resourceName = volume.configMap.name;
} else if (volume?.secret) {
fsType = FILESYSTEM_SOURCE_TYPE.SECRET;
resourceName = volume.secret.secretName;
} else if (volume?.serviceAccount) {
fsType = FILESYSTEM_SOURCE_TYPE.SERVICEACCOUNT;
resourceName = volume.serviceAccount.serviceAccountName;
}
return {
fsType,
volumeName: fs.name,
resourceName,
};
});
},
parseFilesystemRows() {
const completedRows = this.filesystemRows.filter(
(r) => r.fsType && r.volumeName && r.resourceName
);
const filesystems = completedRows.map((r) => ({
name: r.volumeName,
virtiofs: {},
}));
const fsVolumes = completedRows.map((r) => {
if (r.fsType === FILESYSTEM_SOURCE_TYPE.CONFIGMAP) {
return {
name: r.volumeName,
configMap: { name: r.resourceName },
};
} else if (r.fsType === FILESYSTEM_SOURCE_TYPE.SECRET) {
return {
name: r.volumeName,
secret: { secretName: r.resourceName },
};
} else if (r.fsType === FILESYSTEM_SOURCE_TYPE.SERVICEACCOUNT) {
return {
name: r.volumeName,
serviceAccount: { serviceAccountName: r.resourceName },
};
}
return null;
}).filter(Boolean);
if (filesystems.length > 0) {
this.spec.template.spec.domain.devices['filesystems'] = filesystems;
} else {
delete this.spec.template.spec.domain.devices['filesystems'];
}
if (fsVolumes.length > 0) {
if (!this.spec.template.spec.volumes) {
this.spec.template.spec['volumes'] = [];
}
this.spec.template.spec.volumes.push(...fsVolumes);
}
}, },
parseOther() { parseOther() {