diff --git a/src/utils/components/DiskModal/DiskModal.tsx b/src/utils/components/DiskModal/DiskModal.tsx index 1bbd1dac6..cfd99b71e 100644 --- a/src/utils/components/DiskModal/DiskModal.tsx +++ b/src/utils/components/DiskModal/DiskModal.tsx @@ -31,6 +31,7 @@ import { DiskFormState, DiskModalProps } from './utils/types'; const DiskModal: FC = ({ createOwnerReference = true, + customize = false, headerText, initialFormData = null, isEditingCreatedDisk = false, @@ -69,7 +70,8 @@ const DiskModal: FC = ({ }} onSubmit={() => handleSubmit((data) => { - if (isEditingCreatedDisk) return editVMDisk(vm, initialFormData, data, onSubmit); + if (isEditingCreatedDisk || customize) + return editVMDisk(vm, initialFormData, data, onSubmit); if (!isEmpty(initialFormData)) return editDisk(initialFormData, data, uploadData, { @@ -111,7 +113,7 @@ const DiskModal: FC = ({ relevantUpload={upload} vm={vm} /> - + = ({ isEditingCreatedDisk }) => { +const DiskSizeInput: FC = ({ customize, isEditingCreatedDisk }) => { const { t } = useKubevirtTranslation(); const { control, watch } = useFormContext(); const diskSource = watch(diskSourceField); - if (SourceTypes.PVC === diskSource || SourceTypes.OTHER === diskSource) { + if (SourceTypes.PVC === diskSource || (SourceTypes.OTHER === diskSource && !customize)) { return null; } diff --git a/src/utils/components/DiskModal/utils/helpers.ts b/src/utils/components/DiskModal/utils/helpers.ts index 36cc8a597..2078176d1 100644 --- a/src/utils/components/DiskModal/utils/helpers.ts +++ b/src/utils/components/DiskModal/utils/helpers.ts @@ -676,6 +676,20 @@ export const editVMDisk = ( delete vmDisks[diskIndexEdited].bootOrder; } + if ( + initialDiskFormState.diskSize !== newDiskFormState.diskSize && + newDiskFormState.diskSource === SourceTypes.OTHER + ) { + const sourceDataVolume = getDataVolumeTemplates(vmDraft)?.find( + (dv) => getName(dv) === volumeEdited?.dataVolume?.name, + ); + + if (sourceDataVolume) + sourceDataVolume.spec.storage = { + resources: { requests: { storage: newDiskFormState.diskSize } }, + }; + } + return vmDraft; }); diff --git a/src/utils/components/DiskModal/utils/types.ts b/src/utils/components/DiskModal/utils/types.ts index 10a708dfe..5eebfe7b9 100644 --- a/src/utils/components/DiskModal/utils/types.ts +++ b/src/utils/components/DiskModal/utils/types.ts @@ -68,6 +68,7 @@ export type DiskFormState = { export type DiskModalProps = { createOwnerReference?: boolean; + customize?: boolean; headerText: string; initialFormData?: DiskFormState; isEditingCreatedDisk?: boolean; diff --git a/src/utils/resources/vm/hooks/disk/useDisksSources.ts b/src/utils/resources/vm/hooks/disk/useDisksSources.ts new file mode 100644 index 000000000..491030394 --- /dev/null +++ b/src/utils/resources/vm/hooks/disk/useDisksSources.ts @@ -0,0 +1,95 @@ +import { useMemo } from 'react'; + +import { V1beta1DataSource } from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models'; +import { IoK8sApiCoreV1PersistentVolume } from '@kubevirt-ui/kubevirt-api/kubernetes'; +import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt'; +import { + DataSourceModelGroupVersionKind, + modelToGroupVersionKind, + PersistentVolumeClaimModel, +} from '@kubevirt-utils/models'; +import { isEmpty } from '@kubevirt-utils/utils/utils'; +import { useK8sWatchResources } from '@openshift-console/dynamic-plugin-sdk'; + +import { getDataVolumeTemplates } from '../../utils'; + +const PersistentVolumeClaimGroupVersionKind = modelToGroupVersionKind(PersistentVolumeClaimModel); + +const useDisksSources = (vm: V1VirtualMachine) => { + const dataSourcesWatch = useMemo( + () => + getDataVolumeTemplates(vm) + ?.map((dataVolume) => dataVolume?.spec?.sourceRef) + .filter((sourceRef) => Boolean(sourceRef)) + .reduce((acc, dataSource) => { + acc[`${dataSource.name}-${dataSource.namespace}`] = { + groupVersionKind: DataSourceModelGroupVersionKind, + name: dataSource.name, + namespace: dataSource.namespace, + }; + + return acc; + }, {}), + [vm], + ); + + const dataSourcesWatchResult = useK8sWatchResources<{ [key: string]: V1beta1DataSource }>( + dataSourcesWatch, + ); + + const dataSources = useMemo( + () => + Object.values(dataSourcesWatchResult || []) + .map((watch) => watch.data) + .filter((data) => !isEmpty(data)), + [dataSourcesWatchResult], + ); + + const pvcWatches = useMemo(() => { + const pvcSources = getDataVolumeTemplates(vm) + ?.map((dataVolume) => dataVolume?.spec?.source?.pvc) + .filter((pvcSource) => !isEmpty(pvcSource)); + + pvcSources.push( + ...dataSources + .map((dataSource) => dataSource?.spec?.source?.pvc) + .filter((pvcSource) => !isEmpty(pvcSource)), + ); + + return pvcSources.reduce((acc, pvcSource) => { + acc[`${pvcSource.name}-${pvcSource.namespace}`] = { + groupVersionKind: PersistentVolumeClaimGroupVersionKind, + name: pvcSource.name, + namespace: pvcSource.namespace, + }; + + return acc; + }, {}); + }, [vm, dataSources]); + + const pvcWatchesResult = useK8sWatchResources<{ [key: string]: IoK8sApiCoreV1PersistentVolume }>( + pvcWatches, + ); + + const pvcs = useMemo( + () => + Object.values(pvcWatchesResult || []) + .map((watch) => watch.data) + .filter((data) => !isEmpty(data)), + [pvcWatchesResult], + ); + + const loaded = [ + ...Object.values(dataSourcesWatchResult), + ...Object.values(pvcWatchesResult), + ].every((watch) => watch.loaded); + + const loadingError = [ + ...Object.values(dataSourcesWatchResult), + ...Object.values(pvcWatchesResult), + ].find((watch) => watch.loadError); + + return { dataSources, loaded, loadingError, pvcs }; +}; + +export default useDisksSources; diff --git a/src/utils/resources/vm/hooks/disk/useDisksTableData.ts b/src/utils/resources/vm/hooks/disk/useDisksTableData.ts index f3697ee5b..168c9d0bc 100644 --- a/src/utils/resources/vm/hooks/disk/useDisksTableData.ts +++ b/src/utils/resources/vm/hooks/disk/useDisksTableData.ts @@ -1,20 +1,26 @@ import { useMemo } from 'react'; import { isRunning } from 'src/views/virtualmachines/utils'; -import { IoK8sApiCoreV1PersistentVolumeClaim } from '@kubevirt-ui/kubevirt-api/kubernetes'; import { V1VirtualMachine, V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { getRunningVMMissingDisksFromVMI, getRunningVMMissingVolumesFromVMI, } from '@kubevirt-utils/components/DiskModal/utils/helpers'; import { ROOTDISK } from '@kubevirt-utils/constants/constants'; -import { PersistentVolumeClaimModel } from '@kubevirt-utils/models'; +import { getName, getNamespace } from '@kubevirt-utils/resources/shared'; import { DiskRawData, DiskRowDataLayout } from '@kubevirt-utils/resources/vm/utils/disk/constants'; -import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; -import { getBootDisk, getDisks, getInstanceTypeMatcher, getVolumes } from '../../utils'; +import { + getBootDisk, + getDataVolumeTemplates, + getDisks, + getInstanceTypeMatcher, + getVolumes, +} from '../../utils'; import { getDiskRowDataLayout } from '../../utils/disk/rowData'; +import useDisksSources from './useDisksSources'; + type UseDisksTableDisks = ( vm: V1VirtualMachine, vmi: V1VirtualMachineInstance, @@ -47,12 +53,7 @@ const useDisksTableData: UseDisksTableDisks = (vm, vmi) => { [vm, vmi, isVMRunning], ); - const [pvcs, loaded, loadingError] = useK8sWatchResource({ - isList: true, - kind: PersistentVolumeClaimModel.kind, - namespace: vm?.metadata?.namespace, - namespaced: true, - }); + const { dataSources, loaded, loadingError, pvcs } = useDisksSources(vm); const disks = useMemo(() => { const isInstanceTypeVM = Boolean(getInstanceTypeMatcher(vm)); @@ -63,16 +64,29 @@ const useDisksTableData: UseDisksTableDisks = (vm, vmi) => { ? { name: ROOTDISK } : vmDisks?.find(({ name }) => name === volume?.name); + const dataVolumeTemplate = volume?.dataVolume?.name + ? getDataVolumeTemplates(vm)?.find((dv) => getName(dv) === volume.dataVolume.name) + : null; + + const sourceRef = dataVolumeTemplate?.spec?.sourceRef; + + const dataSource = dataSources.find( + (ds) => getName(ds) === sourceRef?.name && getNamespace(ds) === sourceRef?.namespace, + ); + const pvc = pvcs?.find( ({ metadata }) => metadata?.name === volume?.persistentVolumeClaim?.claimName || - metadata?.name === volume?.dataVolume?.name, + metadata?.name === volume?.dataVolume?.name || + (dataSource?.spec?.source?.pvc?.name === metadata?.name && + dataSource?.spec?.source?.pvc?.namespace === metadata?.namespace), ); - return { disk, pvc, volume }; + + return { dataVolumeTemplate, disk, pvc, volume }; }); return getDiskRowDataLayout(diskDevices, getBootDisk(vm)); - }, [vmVolumes, vm, vmDisks, pvcs]); + }, [vm, vmVolumes, vmDisks, pvcs, dataSources]); return [disks || [], loaded, loadingError, isVMRunning ? vmi : null]; }; diff --git a/src/utils/resources/vm/utils/disk/rowData.ts b/src/utils/resources/vm/utils/disk/rowData.ts index e89357812..a69bb8432 100644 --- a/src/utils/resources/vm/utils/disk/rowData.ts +++ b/src/utils/resources/vm/utils/disk/rowData.ts @@ -4,7 +4,7 @@ import { DYNAMIC, OTHER, } from '@kubevirt-utils/components/DiskModal/components/utils/constants'; -import { SourceTypes } from '@kubevirt-utils/components/DiskModal/utils/types'; +import { VolumeTypes } from '@kubevirt-utils/components/DiskModal/utils/types'; import { DiskRawData, DiskRowDataLayout } from '@kubevirt-utils/resources/vm/utils/disk/constants'; import { getPrintableDiskDrive, @@ -47,12 +47,15 @@ export const getDiskRowDataLayout = ( diskRowDataObject.source = device?.pvc?.metadata?.name; diskRowDataObject.sourceStatus = device?.pvc?.status?.phase; diskRowDataObject.size = humanizeBinaryBytes( - convertToBaseValue(device?.pvc?.spec?.resources?.requests?.storage), + convertToBaseValue( + device?.dataVolumeTemplate?.spec?.storage?.resources?.requests?.storage || + device?.pvc?.spec?.resources?.requests?.storage, + ), ).string; diskRowDataObject.storageClass = device?.pvc?.spec?.storageClassName; } - if (volumeSource === SourceTypes.EPHEMERAL) { + if (volumeSource === VolumeTypes.CONTAINER_DISK) { diskRowDataObject.source = CONTAINER_EPHERMAL; diskRowDataObject.size = DYNAMIC; } diff --git a/src/views/catalog/CustomizeInstanceType/tabs/configuration/utils/tabs/CustomizeInstanceTypeStorageTab.tsx b/src/views/catalog/CustomizeInstanceType/tabs/configuration/utils/tabs/CustomizeInstanceTypeStorageTab.tsx index 632340ed9..3389f9ba5 100644 --- a/src/views/catalog/CustomizeInstanceType/tabs/configuration/utils/tabs/CustomizeInstanceTypeStorageTab.tsx +++ b/src/views/catalog/CustomizeInstanceType/tabs/configuration/utils/tabs/CustomizeInstanceTypeStorageTab.tsx @@ -42,6 +42,7 @@ const CustomizeInstanceTypeStorageTab = () => { return Promise.resolve(vmModified); }} + customize vm={vm} /> diff --git a/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskList.tsx b/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskList.tsx index 21e1d6136..d55cb0a52 100644 --- a/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskList.tsx +++ b/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskList.tsx @@ -26,12 +26,13 @@ import DiskRow from './DiskRow'; import './disklist.scss'; type DiskListProps = { + customize?: boolean; onDiskUpdate?: (updatedVM: V1VirtualMachine) => Promise; vm: V1VirtualMachine; vmi?: V1VirtualMachineInstance; }; -const DiskList: FC = ({ onDiskUpdate, vm, vmi }) => { +const DiskList: FC = ({ customize = false, onDiskUpdate, vm, vmi }) => { const { t } = useKubevirtTranslation(); const { createModal } = useModal(); const columns = useDiskColumns(); @@ -88,7 +89,7 @@ const DiskList: FC = ({ onDiskUpdate, vm, vmi }) => { loaded={loaded} loadError={loadError} Row={DiskRow} - rowData={{ onSubmit, provisioningPercentages, vm, vmi }} + rowData={{ customize, onSubmit, provisioningPercentages, vm, vmi }} unfilteredData={data} /> diff --git a/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRow.tsx b/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRow.tsx index a2a716fea..a12a26398 100644 --- a/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRow.tsx +++ b/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRow.tsx @@ -17,13 +17,18 @@ const DiskRow: FC< RowProps< DiskRowDataLayout, { + customize?: boolean; onSubmit?: (updatedVM: V1VirtualMachine) => Promise; provisioningPercentages: NameWithPercentages; vm: V1VirtualMachine; vmi?: V1VirtualMachineInstance; } > -> = ({ activeColumnIDs, obj, rowData: { onSubmit, provisioningPercentages, vm, vmi } }) => { +> = ({ + activeColumnIDs, + obj, + rowData: { customize = false, onSubmit, provisioningPercentages, vm, vmi }, +}) => { const { t } = useKubevirtTranslation(); const provisioningPercentage = provisioningPercentages?.[obj?.source]; @@ -94,7 +99,7 @@ const DiskRow: FC< className="dropdown-kebab-pf pf-v5-c-table__action" id="" > - + ); diff --git a/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRowActions.tsx b/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRowActions.tsx index 50fd480d6..9b410abc1 100644 --- a/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRowActions.tsx +++ b/src/views/virtualmachines/details/tabs/configuration/storage/components/tables/disk/DiskRowActions.tsx @@ -19,13 +19,20 @@ import { getEditDiskStates } from './utils/getEditDiskStates'; import { isHotplugVolume, isPVCSource, isPVCStatusBound } from './utils/helpers'; type DiskRowActionsProps = { + customize?: boolean; obj: DiskRowDataLayout; onDiskUpdate?: (updatedVM: V1VirtualMachine) => Promise; vm: V1VirtualMachine; vmi?: V1VirtualMachineInstance; }; -const DiskRowActions: FC = ({ obj, onDiskUpdate, vm, vmi }) => { +const DiskRowActions: FC = ({ + customize = false, + obj, + onDiskUpdate, + vm, + vmi, +}) => { const { t } = useKubevirtTranslation(); const { createModal } = useModal(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); @@ -38,7 +45,7 @@ const DiskRowActions: FC = ({ obj, onDiskUpdate, vm, vmi }) const isHotplug = isHotplugVolume(vm, diskName, vmi); const isEditDisabled = isVMRunning; - const initialFormState = !isEditDisabled && getEditDiskStates(vm, diskName, vmi); + const initialFormState = !isEditDisabled && getEditDiskStates(vm, obj, vmi); const volumes = isVMRunning ? vmi?.spec?.volumes : getVolumes(vm); const volume = volumes?.find(({ name }) => name === diskName); @@ -56,9 +63,10 @@ const DiskRowActions: FC = ({ obj, onDiskUpdate, vm, vmi }) const createEditDiskModal = () => createModal(({ isOpen, onClose }) => ( DiskFormState; -export const getEditDiskStates: UseEditDiskStates = (vm, diskName, vmi) => { +export const getEditDiskStates: UseEditDiskStates = (vm, diskObj, vmi) => { + const diskName = diskObj?.name; const state = produce(getInitialStateDiskForm(), (draftState) => { draftState.isBootSource = getBootDisk(vm)?.name === diskName; @@ -42,12 +44,14 @@ export const getEditDiskStates: UseEditDiskStates = (vm, diskName, vmi) => { const volume = volumes?.find(({ name }) => name === diskName); const volumeSource = getVolumeSource(volume); - if (volumeSource === SourceTypes.EPHEMERAL) { + if (volumeSource === VolumeTypes.CONTAINER_DISK) { setEphemeralURL(volume, draftState); return; } setOtherSource(draftState); + + if (diskObj.size) draftState.diskSize = diskObj.size; }); return { ...state, diskName };