Skip to content

Commit

Permalink
Merge pull request #2201 from avivtur/allow-vm-snapshots-to-be-saved
Browse files Browse the repository at this point in the history
CNV-48184: Snapshot are automatically deleted when their original vm is deleted
  • Loading branch information
openshift-merge-bot[bot] authored Oct 10, 2024
2 parents 676bca4 + e843979 commit 4c27178
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 74 deletions.
1 change: 0 additions & 1 deletion locales/en/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
"Affinity rules": "Affinity rules",
"Alerts": "Alerts",
"Alerts ({{alertsQuantity}})": "Alerts ({{alertsQuantity}})",
"All snapshots of this VirtualMachine will be deleted as well.": "All snapshots of this VirtualMachine will be deleted as well.",
"All sources selected": "All sources selected",
"All templates": "All templates",
"Allocating resources": "Allocating resources",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import VirtualMachineModel, {
} from '@kubevirt-ui/kubevirt-api/console/models/VirtualMachineModel';
import { V1beta1DataVolume } from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models';
import { IoK8sApiCoreV1PersistentVolumeClaim } from '@kubevirt-ui/kubevirt-api/kubernetes';
import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt';
import {
V1beta1VirtualMachineSnapshot,
V1VirtualMachine,
} from '@kubevirt-ui/kubevirt-api/kubevirt';
import ConfirmActionMessage from '@kubevirt-utils/components/ConfirmActionMessage/ConfirmActionMessage';
import { GracePeriodInput } from '@kubevirt-utils/components/GracePeriodInput/GracePeriodInput';
import TabModal from '@kubevirt-utils/components/TabModal/TabModal';
Expand All @@ -19,7 +22,11 @@ import { ButtonVariant, Stack, StackItem } from '@patternfly/react-core';

import DeleteOwnedResourcesMessage from './components/DeleteOwnedResourcesMessage';
import useDeleteVMResources from './hooks/useDeleteVMResources';
import { removeDataVolumeTemplatesToVM, updateVolumeResources } from './utils/helpers';
import {
removeDataVolumeTemplatesToVM,
updateSnapshotResources,
updateVolumeResources,
} from './utils/helpers';
import { DEFAULT_GRACE_PERIOD } from './constants';

type DeleteVMModalProps = {
Expand All @@ -40,6 +47,8 @@ const DeleteVMModal: FC<DeleteVMModalProps> = ({ isOpen, onClose, vm }) => {
(IoK8sApiCoreV1PersistentVolumeClaim | V1beta1DataVolume)[]
>([]);

const [snapshotsToSave, setSnapshotsToSave] = useState<V1beta1VirtualMachineSnapshot[]>([]);

const { dataVolumes, loaded, pvcs, snapshots } = useDeleteVMResources(vm);
const lastNamespacePath = useLastNamespacePath();

Expand All @@ -53,6 +62,8 @@ const DeleteVMModal: FC<DeleteVMModalProps> = ({ isOpen, onClose, vm }) => {

await Promise.allSettled(updateVolumeResources(volumesToSave, vmOwnerRef));

await Promise.allSettled(updateSnapshotResources(snapshotsToSave, vmOwnerRef));

await k8sDelete({
json: gracePeriodCheckbox
? { apiVersion: 'v1', gracePeriodSeconds, kind: 'DeleteOptions' }
Expand Down Expand Up @@ -87,8 +98,10 @@ const DeleteVMModal: FC<DeleteVMModalProps> = ({ isOpen, onClose, vm }) => {
dataVolumes={dataVolumes}
loaded={loaded}
pvcs={pvcs}
setSnapshotsToSave={setSnapshotsToSave}
setVolumesToSave={setVolumesToSave}
snapshots={snapshots}
snapshotsToSave={snapshotsToSave}
volumesToSave={volumesToSave}
/>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,34 @@ import { V1beta1VirtualMachineSnapshot } from '@kubevirt-ui/kubevirt-api/kubevir
import Loading from '@kubevirt-utils/components/Loading/Loading';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { getName } from '@kubevirt-utils/resources/shared';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { Bullseye, StackItem } from '@patternfly/react-core';

import { findPVCOwner } from '../utils/helpers';

import DeleteVolumeCheckbox from './DeleteVolumeCheckbox';
import DeleteResourceCheckbox from './DeleteResourceCheckbox';

type DeleteOwnedResourcesMessageProps = {
dataVolumes: V1beta1DataVolume[];
loaded: boolean;
pvcs: IoK8sApiCoreV1PersistentVolumeClaim[];
setSnapshotsToSave: Dispatch<SetStateAction<V1beta1VirtualMachineSnapshot[]>>;
setVolumesToSave: Dispatch<
SetStateAction<(IoK8sApiCoreV1PersistentVolumeClaim | V1beta1DataVolume)[]>
>;
snapshots: V1beta1VirtualMachineSnapshot[];
snapshotsToSave: V1beta1VirtualMachineSnapshot[];
volumesToSave: (IoK8sApiCoreV1PersistentVolumeClaim | V1beta1DataVolume)[];
};

const DeleteOwnedResourcesMessage: FC<DeleteOwnedResourcesMessageProps> = ({
dataVolumes,
loaded,
pvcs,
setSnapshotsToSave,
setVolumesToSave,
snapshots,
snapshotsToSave,
volumesToSave,
}) => {
const { t } = useKubevirtTranslation();
Expand All @@ -44,7 +49,6 @@ const DeleteOwnedResourcesMessage: FC<DeleteOwnedResourcesMessageProps> = ({
const pvcsWithNoDataVolumes = pvcs?.filter((pvc) => !findPVCOwner(pvc, dataVolumes)) || [];

const diskCount = dataVolumes?.length + pvcsWithNoDataVolumes?.length || 0;
const hasSnapshots = snapshots?.length > 0;

return (
<>
Expand All @@ -57,20 +61,23 @@ const DeleteOwnedResourcesMessage: FC<DeleteOwnedResourcesMessageProps> = ({
)}

{[...(dataVolumes || []), ...pvcsWithNoDataVolumes].map((resource) => (
<DeleteVolumeCheckbox
<DeleteResourceCheckbox
key={`${resource.kind}-${getName(resource)}`}
resource={resource}
setVolumesToSave={setVolumesToSave}
volumesToSave={volumesToSave}
resourcesToSave={volumesToSave}
setResourcesToSave={setVolumesToSave}
/>
))}

{hasSnapshots && (
<StackItem>
<strong>{t('Warning')}: </strong>
{t('All snapshots of this VirtualMachine will be deleted as well.')}
</StackItem>
)}
{!isEmpty(snapshots) &&
snapshots.map((snapshot) => (
<DeleteResourceCheckbox
key={`${snapshot.kind}-${getName(snapshot)}`}
resource={snapshot}
resourcesToSave={snapshotsToSave}
setResourcesToSave={setSnapshotsToSave}
/>
))}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { Dispatch, FC, SetStateAction } from 'react';

import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { getName } from '@kubevirt-utils/resources/shared';
import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';
import { Checkbox, StackItem } from '@patternfly/react-core';

import { sameResource } from '../utils/helpers';

type DeleteResourceCheckboxProps = {
resource: K8sResourceCommon;

resourcesToSave: K8sResourceCommon[];
setResourcesToSave: Dispatch<SetStateAction<K8sResourceCommon[]>>;
};

const DeleteResourceCheckbox: FC<DeleteResourceCheckboxProps> = ({
resource,
resourcesToSave,
setResourcesToSave,
}) => {
const { t } = useKubevirtTranslation();
const resourceName = getName(resource);

const saveResource = () => setResourcesToSave((prevVolumes) => [...prevVolumes, resource]);

const deleteResource = () =>
setResourcesToSave((prevVolumes) =>
prevVolumes.filter((volume) => !sameResource(volume, resource)),
);

return (
<StackItem>
<Checkbox
label={t('Delete disk {{resourceName}} ({{kindAbbr}})', {
kindAbbr: resource.kind,
resourceName,
})}
id={`${resource.kind}-${resourceName}`}
isChecked={!resourcesToSave.find((volume) => sameResource(volume, resource))}
onChange={(_, checked: boolean) => (checked ? deleteResource() : saveResource())}
/>
</StackItem>
);
};

export default DeleteResourceCheckbox;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { PersistentVolumeClaimModel } from '@kubevirt-ui/kubevirt-api/console';
import DataVolumeModel from '@kubevirt-ui/kubevirt-api/console/models/DataVolumeModel';
import VirtualMachineModel from '@kubevirt-ui/kubevirt-api/console/models/VirtualMachineModel';
import VirtualMachineSnapshotModel from '@kubevirt-ui/kubevirt-api/console/models/VirtualMachineSnapshotModel';
import { V1beta1DataVolume } from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models';
import { IoK8sApiCoreV1PersistentVolumeClaim } from '@kubevirt-ui/kubevirt-api/kubernetes/models';
import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt';
import {
V1beta1VirtualMachineSnapshot,
V1VirtualMachine,
} from '@kubevirt-ui/kubevirt-api/kubevirt';
import { compareOwnerReferences, getName, getNamespace } from '@kubevirt-utils/resources/shared';
import { getDataVolumeTemplates } from '@kubevirt-utils/resources/vm';
import { isEmpty } from '@kubevirt-utils/utils/utils';
Expand Down Expand Up @@ -34,6 +38,28 @@ export const updateVolumeResources = (
});
};

export const updateSnapshotResources = (
resources: V1beta1VirtualMachineSnapshot[],
vmOwnerRef: OwnerReference,
) => {
return (resources || []).map((resource) => {
const resourceFilteredOwnerReference = resource?.metadata?.ownerReferences?.filter(
(resourceRef) => !compareOwnerReferences(resourceRef, vmOwnerRef),
);
return k8sPatch({
data: [
{
op: 'replace',
path: '/metadata/ownerReferences',
value: resourceFilteredOwnerReference,
},
],
model: VirtualMachineSnapshotModel,
resource,
});
});
};

export const findPVCOwner = (
pvc: IoK8sApiCoreV1PersistentVolumeClaim,
resources: K8sResourceCommon[],
Expand Down Expand Up @@ -66,10 +92,7 @@ export const removeDataVolumeTemplatesToVM = (
});
};

export const sameVolume = (
volumeA: IoK8sApiCoreV1PersistentVolumeClaim | V1beta1DataVolume,
volumeB: IoK8sApiCoreV1PersistentVolumeClaim | V1beta1DataVolume,
) =>
export const sameResource = (volumeA: K8sResourceCommon, volumeB: K8sResourceCommon) =>
volumeA.kind === volumeB.kind &&
getName(volumeA) === getName(volumeB) &&
getNamespace(volumeA) === getNamespace(volumeB);

0 comments on commit 4c27178

Please sign in to comment.