Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RHSTOR-4130: Cross storage class clone /restore #13549

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,6 +1,5 @@
import * as React from 'react';
import {
Form,
FormGroup,
FormHelperText,
HelperText,
Expand Down Expand Up @@ -34,6 +33,7 @@ import {
} from '@console/internal/components/utils';
import { useK8sGet } from '@console/internal/components/utils/k8s-get-hook';
import { HandlePromiseProps } from '@console/internal/components/utils/promise-component';
import { StorageClassDropdown } from '@console/internal/components/utils/storage-class-dropdown';
import {
NamespaceModel,
PersistentVolumeClaimModel,
Expand All @@ -46,7 +46,7 @@ import {
StorageClassResourceKind,
} from '@console/internal/module/k8s';
import { RedExclamationCircleIcon, isCephProvisioner } from '@console/shared';
import { getRequestedPVCSize } from '@console/shared/src/selectors';
import { getName, getRequestedPVCSize, onlyPvcSCs } from '@console/shared/src/selectors';
import { getPVCAccessModes, AccessModeSelector } from '../../access-modes/access-mode';

import './_clone-pvc-modal.scss';
Expand All @@ -65,6 +65,12 @@ const ClonePVCModal = withHandlePromise((props: ClonePVCModalProps) => {
const [requestedUnit, setRequestedUnit] = React.useState(defaultSize[1] || 'Ti');
const [validSize, setValidSize] = React.useState(true);
const pvcAccessMode = getPVCAccessModes(resource, 'title');
const [pvcSC, setPVCStorageClass] = React.useState('');
TimothyAsirJeyasing marked this conversation as resolved.
Show resolved Hide resolved
const [updatedProvisioner, setUpdatedProvisioner] = React.useState('');
TimothyAsirJeyasing marked this conversation as resolved.
Show resolved Hide resolved
const handleStorageClass = (updatedStorageClass: StorageClassResourceKind) => {
setPVCStorageClass(getName(updatedStorageClass) || '');
setUpdatedProvisioner(updatedStorageClass?.provisioner);
};

const [scResource, scResourceLoaded, scResourceLoadError] = useK8sGet<StorageClassResourceKind>(
StorageClassModel,
Expand Down Expand Up @@ -103,7 +109,7 @@ const ClonePVCModal = withHandlePromise((props: ClonePVCModalProps) => {
namespace: resource.metadata.namespace,
},
spec: {
storageClassName: resource.spec.storageClassName,
storageClassName: pvcSC,
dataSource: {
name: pvcName,
kind: PersistentVolumeClaimModel.kind,
Expand All @@ -126,125 +132,139 @@ const ClonePVCModal = withHandlePromise((props: ClonePVCModalProps) => {
};

return (
<Form onSubmit={submit}>
<div className="modal-content">
<ModalTitle>{t('console-app~Clone')}</ModalTitle>
<ModalBody>
<FormGroup
label={t('console-app~Name')}
isRequired
fieldId="clone-pvc-modal__name"
className="co-clone-pvc-modal__form--space"
>
<TextInput
type="text"
className="co-clone-pvc-modal__name--margin"
value={clonePVCName}
onChange={(_event, value) => setClonePVCName(value)}
aria-label={t('console-app~Clone PVC')}
/>
</FormGroup>
<AccessModeSelector
onChange={setCloneAccessMode}
className="co-clone-pvc-modal__form--space"
pvcResource={resource}
provisioner={scResource?.provisioner}
loaded={scResourceLoaded}
loadError={scResourceLoadError}
filterByVolumeMode
<form onSubmit={submit} name="form" className="modal-content">
<ModalTitle>{t('console-app~Clone')}</ModalTitle>
<ModalBody>
<FormGroup
label={t('console-app~Name')}
isRequired
fieldId="clone-pvc-modal__name"
className="co-clone-pvc-modal__form--space"
>
<TextInput
type="text"
className="co-clone-pvc-modal__name--margin"
data-test="pvc-name"
value={clonePVCName}
onChange={(_event, value) => setClonePVCName(value)}
aria-label={t('console-app~Clone PVC')}
/>
<FormGroup
label={t('console-app~Size')}
isRequired
fieldId="clone-pvc-modal__size"
className="co-clone-pvc-modal__form--space"
>
{scResourceLoaded ? (
<RequestSizeInput
name="requestSize"
testID="input-request-size"
onChange={requestedSizeInputChange}
defaultRequestSizeUnit={requestedUnit}
defaultRequestSizeValue={requestedSize}
dropdownUnits={dropdownUnits}
isInputDisabled={scResourceLoadError || isCephProvisioner(scResource?.provisioner)}
required
/>
) : (
<div className="skeleton-text" />
)}
</FormGroup>
<AccessModeSelector
onChange={setCloneAccessMode}
className="co-clone-pvc-modal__form--space"
pvcResource={resource}
provisioner={updatedProvisioner}
loaded={scResourceLoaded}
loadError={scResourceLoadError}
filterByVolumeMode
/>
<FormGroup
label={t('console-app~Size')}
isRequired
fieldId="clone-pvc-modal__size"
className="co-clone-pvc-modal__form--space"
>
{scResourceLoaded ? (
<RequestSizeInput
name="requestSize"
testID="input-request-size"
onChange={requestedSizeInputChange}
defaultRequestSizeUnit={requestedUnit}
defaultRequestSizeValue={requestedSize}
dropdownUnits={dropdownUnits}
isInputDisabled={scResourceLoadError || isCephProvisioner(scResource?.provisioner)}
required
/>
) : (
<div className="skeleton-text" />
)}

{!validSize && (
<FormHelperText>
<HelperText>
<HelperTextItem variant="error" icon={<RedExclamationCircleIcon />}>
{t(
'console-app~Size should be equal or greater than the requested size of PVC.',
)}
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
<div className="co-clone-pvc-modal__details">
<p className="text-muted">{t('console-app~PVC details')}</p>
<div className="co-clone-pvc-modal__details-section">
{!validSize && (
<FormHelperText>
<HelperText>
<HelperTextItem variant="error" icon={<RedExclamationCircleIcon />}>
{t('console-app~Size should be equal or greater than the requested size of PVC.')}
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
<FormGroup
fieldId="clone-pvc-modal__storage-class"
className="co-clone-pvc-modal__form--space"
>
{!scResourceLoaded ? (
<div className="skeleton-text" />
) : (
<StorageClassDropdown
onChange={handleStorageClass}
filter={(scObj: StorageClassResourceKind) =>
onlyPvcSCs(scObj, scResourceLoadError, scResource)
}
id="clone-storage-class"
data-test="storage-class-dropdown"
required
selectedKey={getName(scResource)}
/>
)}
</FormGroup>
<div className="co-clone-pvc-modal__details">
<p className="text-muted">{t('console-app~PVC details')}</p>
<div className="co-clone-pvc-modal__details-section">
<div>
<div>
<div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~Namespace')}</p>
<p>
<ResourceIcon kind={NamespaceModel.kind} />
{resource.metadata.namespace}
</p>
</div>
<div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~StorageClass')}</p>
<p>
<ResourceIcon kind={StorageClassModel.kind} />
{resource.spec?.storageClassName || '-'}
</p>
</div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~Namespace')}</p>
<p>
<ResourceIcon kind={NamespaceModel.kind} />
{resource.metadata.namespace}
</p>
</div>
<div>
<div>
<p className="co-clone-pvc-modal__pvc-details">
{t('console-app~Requested capacity')}
</p>
<p>{pvcRequestedSize}</p>
</div>
<div>
<p className="co-clone-pvc-modal__pvc-details">
{t('console-app~Used capacity')}
</p>
<div>
{!loading && !error && pvcUsedCapacity}
{loading && <LoadingInline />}
{!loading && error && '-'}
</div>
</div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~StorageClass')}</p>
<p>
<ResourceIcon kind={StorageClassModel.kind} />
{pvcSC || '-'}
</p>
</div>
</div>
<div>
<div>
<p className="co-clone-pvc-modal__pvc-details">
{t('console-app~Requested capacity')}
</p>
<p>{pvcRequestedSize}</p>
</div>
<div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~Used capacity')}</p>
<div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~Access mode')}</p>
<p>{pvcAccessMode.join(', ') || '-'}</p>
</div>
<div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~Volume mode')}</p>
<p>{resource.spec.volumeMode}</p>
{!loading && !error && pvcUsedCapacity}
{loading && <LoadingInline />}
{!loading && error && '-'}
</div>
</div>
</div>
<div>
<div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~Access mode')}</p>
<p>{pvcAccessMode.join(', ') || '-'}</p>
</div>
<div>
<p className="co-clone-pvc-modal__pvc-details">{t('console-app~Volume mode')}</p>
<p>{resource.spec.volumeMode}</p>
</div>
</div>
</div>
</ModalBody>
<ModalSubmitFooter
inProgress={inProgress}
submitDisabled={!validSize || !resource.spec?.storageClassName}
errorMessage={errorMessage}
submitText={t('console-app~Clone')}
cancel={cancel}
/>
</div>
</Form>
</div>
</ModalBody>
<ModalSubmitFooter
inProgress={inProgress}
submitDisabled={!validSize || !pvcSC}
errorMessage={errorMessage}
submitText={t('console-app~Clone')}
cancel={cancel}
/>
</form>
);
});

Expand Down
Expand Up @@ -40,14 +40,13 @@ import {
NamespaceModel,
PersistentVolumeClaimModel,
VolumeSnapshotModel,
VolumeSnapshotClassModel,
StorageClassModel,
} from '@console/internal/models';
import {
k8sCreate,
VolumeSnapshotKind,
StorageClassResourceKind,
PersistentVolumeClaimKind,
VolumeSnapshotClassKind,
} from '@console/internal/module/k8s';
import {
getName,
Expand All @@ -56,6 +55,7 @@ import {
isCephProvisioner,
getAnnotations,
RedExclamationCircleIcon,
onlyPvcSCs,
} from '@console/shared';
import { AccessModeSelector } from '../../access-modes/access-mode';

Expand Down Expand Up @@ -85,21 +85,13 @@ const RestorePVCModal = withHandlePromise<RestorePVCModalProps>(
PersistentVolumeClaimKind
>(PersistentVolumeClaimModel, resource?.spec?.source?.persistentVolumeClaimName, namespace);

const [
snapshotClassResource,
snapshotClassResourceLoaded,
snapshotClassResourceLoadError,
] = useK8sGet<VolumeSnapshotClassKind>(
VolumeSnapshotClassModel,
resource?.spec?.volumeSnapshotClassName,
const pvcStorageClassName = pvcResource?.spec?.storageClassName;
const [scResource, scResourceLoaded, scResourceLoadError] = useK8sGet<StorageClassResourceKind>(
StorageClassModel,
pvcStorageClassName,
);

const [volumeMode, setVolumeMode] = React.useState('');
const onlyPvcSCs = (scObj: StorageClassResourceKind) =>
!snapshotClassResourceLoadError
? scObj.provisioner.includes(snapshotClassResource?.driver)
: true;

const requestedSizeInputChange = ({ value, unit }) => {
setRequestedSize(value);
setRequestedUnit(unit);
Expand Down Expand Up @@ -178,12 +170,14 @@ const RestorePVCModal = withHandlePromise<RestorePVCModalProps>(
/>
</FormGroup>
<FormGroup fieldId="restore-storage-class" className="co-restore-pvc-modal__input">
{!snapshotClassResourceLoaded ? (
{!pvcStorageClassName || !scResourceLoaded ? (
<div className="skeleton-text" />
) : (
<StorageClassDropdown
onChange={handleStorageClass}
filter={onlyPvcSCs}
filter={(scObj: StorageClassResourceKind) =>
onlyPvcSCs(scObj, scResourceLoadError, scResource)
}
id="restore-storage-class"
required
selectedKey={volumeSnapshotAnnotations?.[snapshotPVCStorageClassAnnotation]}
Expand Down Expand Up @@ -217,16 +211,14 @@ const RestorePVCModal = withHandlePromise<RestorePVCModalProps>(
fieldId="pvc-size"
className="co-restore-pvc-modal__input co-restore-pvc-modal__ocs-size"
>
{snapshotClassResourceLoaded ? (
{!!pvcStorageClassName && scResourceLoaded ? (
<RequestSizeInput
name="requestSize"
onChange={requestedSizeInputChange}
defaultRequestSizeUnit={requestedUnit}
defaultRequestSizeValue={requestedSize}
dropdownUnits={dropdownUnits}
isInputDisabled={
snapshotClassResourceLoadError || isCephProvisioner(snapshotClassResource?.driver)
}
isInputDisabled={scResourceLoadError || isCephProvisioner(scResource?.provisioner)}
required
/>
) : (
Expand Down
12 changes: 11 additions & 1 deletion frontend/packages/console-shared/src/selectors/storage.ts
@@ -1,4 +1,14 @@
import { K8sResourceKind } from '@console/internal/module/k8s';
import { K8sResourceKind, StorageClassResourceKind } from '@console/internal/module/k8s';

export const getRequestedPVCSize = (pvc: K8sResourceKind): string =>
pvc?.spec?.resources?.requests?.storage;

export const onlyPvcSCs = (
scObj: StorageClassResourceKind,
scResourceLoadError: boolean,
scResource: StorageClassResourceKind | null,
) =>
!scResourceLoadError
? scObj.provisioner.includes(scResource?.provisioner) &&
scObj.parameters?.encrypted === scResource?.parameters?.encrypted
: true;