Skip to content

Commit

Permalink
(feat): Setup create storage system wizard
Browse files Browse the repository at this point in the history
- Adds Backing Storage step
- implements a wizard with [incrementally enabled steps](https://www.patternfly.org/v4/components/wizard/#incrementally-enabled-steps)
- This step creates the Storage System for an external Provider other than RHCS on clicking Next.
- This step otherwise, allow choosing the backing storage for Storage Cluster. The Storage Cluster is hidden from UI but it is created internally by wizard flow. The creation of storage cluster happens in the last step.
- https://issues.redhat.com/browse/ODFE-83

Signed-off-by: Afreen Rahman <afrahman@redhat.com>
  • Loading branch information
Afreen Rahman committed Jul 7, 2021
1 parent fe0f165 commit 92b28f0
Show file tree
Hide file tree
Showing 17 changed files with 715 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@
"Name can contain a max of 43 characters": "Name can contain a max of 43 characters",
"Provider": "Provider",
"Create BackingStore": "Create BackingStore",
"An error occurred": "An error occurred",
"Create StorageSystem": "Create StorageSystem",
"StorageSystem is an entity of OpenShift Data Foundation. It represents all of the required storage and compute resources.": "StorageSystem is an entity of OpenShift Data Foundation. It represents all of the required storage and compute resources.",
"Select external system from list": "Select external system from list",
"Use an existing storage class": "Use an existing storage class",
"Can be used on all platforms except BareMetal. OpenShift Data Foundation will use an infrastructure storage class provided by the hosting platform.": "Can be used on all platforms except BareMetal. OpenShift Data Foundation will use an infrastructure storage class provided by the hosting platform.",
"Create a new storage class using local devices": "Create a new storage class using local devices",
"Can be used on any platform having nodes with local devices. The infrastructure storage class is provided by Local Storage Operator on top of the local devices.": "Can be used on any platform having nodes with local devices. The infrastructure storage class is provided by Local Storage Operator on top of the local devices.",
"Connect a new external storage system": "Connect a new external storage system",
"Can be used to connect an external storage platform to OpenShift Data Foundation.": "Can be used to connect an external storage platform to OpenShift Data Foundation.",
"Capacity and nodes": "Capacity and nodes",
"Security and network": "Security and network",
"Review and create": "Review and create",
"Connection details": "Connection details",
"Create storage class": "Create storage class",
"Create local volume set": "Create local volume set",
"Backing storage": "Backing storage",
"Not available": "Not available",
"Not enough usage data": "Not enough usage data",
"used": "used",
Expand Down Expand Up @@ -422,9 +439,6 @@
"Encryption Level: {{level}}": "Encryption Level: {{level}}",
"Using {{networkLabel}}": "Using {{networkLabel}}",
"Discover disks": "Discover disks",
"Capacity and nodes": "Capacity and nodes",
"Security and network": "Security and network",
"Review and create": "Review and create",
"Internal - Attached devices": "Internal - Attached devices",
"Can be used on any platform where there are attached devices to the nodes, using the Local Storage Operator. The infrastructure StorageClass is provided by Local Storage Operator, on top of the attached drives.": "Can be used on any platform where there are attached devices to the nodes, using the Local Storage Operator. The infrastructure StorageClass is provided by Local Storage Operator, on top of the attached drives.",
"Local Storage Operator not installed": "Local Storage Operator not installed",
Expand Down Expand Up @@ -508,7 +522,6 @@
"Storage pool into which volume data shall be stored": "Storage pool into which volume data shall be stored",
"Error retrieving Parameters": "Error retrieving Parameters",
"my-storage-pool": "my-storage-pool",
"Connection details": "Connection details",
"Change connection details": "Change connection details",
"Vault Enterprise Namespace:": "Vault Enterprise Namespace:",
"Key management service name:": "Key management service name:",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as React from 'react';
import { TFunction } from 'i18next';
import {
WizardFooter,
Button,
WizardContext,
WizardContextType,
Alert,
AlertActionCloseButton,
} from '@patternfly/react-core';
import { k8sCreate } from '@console/internal/module/k8s';
import { WizardCommonProps } from './reducer';
import { createSSPayload } from './payloads';
import { RHCS, StepsId } from '../../constants/create-storage-system';
import { StorageSystemModel } from '../../models';
import './create-storage-system.scss';

export const CreateStorageSystemFooter: React.FC<CreateStorageSystemFooterProps> = ({
t,
dispatch,
state,
}) => {
const { activeStep, onNext, onBack, onClose } = React.useContext<WizardContextType>(
WizardContext,
);
const [inProgress, setInProgress] = React.useState(false);
const [error, setError] = React.useState('');
const [showErrorAlert, setShowErrorAlert] = React.useState(false);

const { externalProvider } = state.backingStorage;

const { id } = activeStep;

const handleCustomNext = async () => {
if (id === StepsId.BackingStorage && externalProvider !== RHCS) {
setInProgress(true);
const payload = createSSPayload(externalProvider);
try {
await k8sCreate(StorageSystemModel, payload);
dispatch({
type: 'currentStep/incrementCount',
});
onNext();
} catch (err) {
setError(err.message);
setShowErrorAlert(true);
} finally {
setInProgress(false);
}
} else {
dispatch({
type: 'currentStep/incrementCount',
});
onNext();
}
};

return (
<>
{showErrorAlert && (
<Alert
className="odf-create-storage-system-footer__alert"
variant="danger"
isInline
actionClose={<AlertActionCloseButton onClose={() => setShowErrorAlert(false)} />}
title={t('ceph-storage-plugin~An error occurred')}
>
{error}
</Alert>
)}
<WizardFooter>
<Button
isLoading={inProgress}
isDisabled={inProgress}
variant="primary"
type="submit"
onClick={handleCustomNext}
>
{id === StepsId.ReviewAndCreate
? t('ceph-storage-plugin~Create')
: t('ceph-storage-plugin~Next')}
</Button>
{/* Disabling the back button for the first step (Backing storage) in wizard */}
<Button variant="secondary" onClick={onBack} isDisabled={id === StepsId.BackingStorage}>
{t('ceph-storage-plugin~Back')}
</Button>
<Button variant="link" onClick={onClose}>
{t('ceph-storage-plugin~Cancel')}
</Button>
</WizardFooter>
</>
);
};

type CreateStorageSystemFooterProps = WizardCommonProps & {
t: TFunction;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import { TFunction } from 'i18next';
import {
TextVariants,
Text,
TextContent,
Breadcrumb,
BreadcrumbItem,
} from '@patternfly/react-core';
import './create-storage-system.scss';

export const CreateStorageSystemHeader: React.FC<CreateStorageSystemHeaderProps> = ({
appName,
url,
t,
}) => (
<div className="odf-create-storage-system__header">
<Breadcrumb className="odf-create-storage-system__breadcrumb">
<BreadcrumbItem to={url.replace('/~new', '')}>{appName}</BreadcrumbItem>
<BreadcrumbItem>{t('ceph-storage-plugin~Create StorageSystem')}</BreadcrumbItem>
</Breadcrumb>
<TextContent>
<Text component={TextVariants.h1}>{t('ceph-storage-plugin~Create StorageSystem')}</Text>
<Text component={TextVariants.small}>
{t(
'ceph-storage-plugin~StorageSystem is an entity of OpenShift Data Foundation. It represents all of the required storage and compute resources.',
)}
</Text>
</TextContent>
</div>
);

type CreateStorageSystemHeaderProps = {
t: TFunction;
appName: string;
url: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.odf-backing-storage__selection--width {
width: 16rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { TFunction } from 'i18next';
import { Form, FormSelect, FormSelectOption, FormSelectProps, Radio } from '@patternfly/react-core';
import { StorageClassDropdown } from '@console/internal/components/utils/storage-class-dropdown';
import { StorageClassResourceKind } from '@console/internal/module/k8s';
import './backing-storage.scss';
import { ExternalProvider } from '../../../../odf-external-providers/types';
import { ODF_EXTERNAL_PROVIDERS } from '../../../../odf-external-providers/external-providers';
import { StorageSystemKind } from '../../../../types';
import { filterSCWithoutNoProv } from '../../../../utils/install';
import { WizardCommonProps, WizardReducer, WizardState, WizardDispatch } from '../../reducer';
import { BackingStorageType } from '../../../../constants/create-storage-system';

const ExternalSystemSelection: React.FC<ExternalSystemSelectionProps> = ({
dispatch,
providersList,
selectedProvider,
t,
}) => {
const handleSelection: FormSelectProps['onChange'] = (value) =>
dispatch({
type: 'backingStorage/setExternalProvider',
payload: value,
});

return (
<FormSelect
aria-label={t('ceph-storage-plugin~Select external system from list')}
value={selectedProvider}
className="odf-backing-storage__selection--width"
onChange={handleSelection}
>
{providersList.map((p) => (
<FormSelectOption key={p.name} value={p.name} label={p.label} />
))}
</FormSelect>
);
};

type ExternalSystemSelectionProps = {
dispatch: React.Dispatch<React.ReducerAction<WizardReducer>>;
selectedProvider: WizardState['backingStorage']['externalProvider'];
providersList: ExternalProvider[];
t: TFunction;
};

const StorageClassSelection: React.FC<StorageClassSelectionProps> = ({ dispatch, selected }) => {
const onStorageClassSelect = (sc: StorageClassResourceKind) =>
dispatch({
type: 'wizard/setStorageClass',
payload: { name: sc?.metadata?.name, provisioner: sc.provisioner },
});
return (
<div className="odf-backing-storage__selection--width">
<StorageClassDropdown
noSelection
onChange={onStorageClassSelect}
selectedKey={selected.name}
filter={filterSCWithoutNoProv}
data-test="storage-class-dropdown"
/>
</div>
);
};

type StorageClassSelectionProps = {
dispatch: WizardDispatch;
selected: WizardState['storageClass'];
};

export const BackingStorage: React.FC<BackingStorageProps> = ({
state,
dispatch,
hasOCS,
existingStorageSystems,
}) => {
const { t } = useTranslation();

const externalProviders = ODF_EXTERNAL_PROVIDERS.filter(
(provider) => !existingStorageSystems.has(provider.kind),
);

React.useEffect(() => {
/*
Allow pre selecting the "external connection" option instead of the "existing" option
if an OCS Storage System is already created and no external system is created.
*/
if (hasOCS && externalProviders.length) {
dispatch({ type: 'backingStorage/setType', payload: BackingStorageType.EXTERNAL });
}
}, [dispatch, externalProviders.length, hasOCS]);

const { type, externalProvider } = state.backingStorage;
const { storageClass } = state;

const showExternalSystemSelection = type === BackingStorageType.EXTERNAL;
const showStorageClassSelection = !hasOCS && type === BackingStorageType.EXISTING;
const RADIO_GROUP_NAME = 'backing-storage-radio-group';

const onRadioSelect = (_, event) => {
dispatch({ type: 'backingStorage/setType', payload: event.target.value });
dispatch({
type: 'currentStep/resetCount',
});
};

return (
<Form>
<Radio
label={t('ceph-storage-plugin~Use an existing storage class')}
description={t(
'ceph-storage-plugin~Can be used on all platforms except BareMetal. OpenShift Data Foundation will use an infrastructure storage class provided by the hosting platform.',
)}
name={RADIO_GROUP_NAME}
value={BackingStorageType.EXISTING}
isChecked={type === BackingStorageType.EXISTING}
onChange={onRadioSelect}
isDisabled={hasOCS}
body={
showStorageClassSelection && (
<StorageClassSelection dispatch={dispatch} selected={storageClass} />
)
}
id={`bs-${BackingStorageType.EXISTING}`}
/>
<Radio
label={t('ceph-storage-plugin~Create a new storage class using local devices')}
description={t(
'ceph-storage-plugin~Can be used on any platform having nodes with local devices. The infrastructure storage class is provided by Local Storage Operator on top of the local devices.',
)}
name={RADIO_GROUP_NAME}
value={BackingStorageType.LOCAL_DEVICES}
isChecked={type === BackingStorageType.LOCAL_DEVICES}
onChange={onRadioSelect}
isDisabled={hasOCS}
id={`bs-${BackingStorageType.LOCAL_DEVICES}`}
/>
<Radio
label={t('ceph-storage-plugin~Connect a new external storage system')}
description={t(
'ceph-storage-plugin~Can be used to connect an external storage platform to OpenShift Data Foundation.',
)}
name={RADIO_GROUP_NAME}
value={BackingStorageType.EXTERNAL}
isChecked={type === BackingStorageType.EXTERNAL}
onChange={onRadioSelect}
isDisabled={externalProviders.length === 0}
body={
showExternalSystemSelection && (
<ExternalSystemSelection
selectedProvider={externalProvider}
dispatch={dispatch}
providersList={externalProviders}
t={t}
/>
)
}
id={`bs-${BackingStorageType.EXTERNAL}`}
/>
</Form>
);
};

type BackingStorageProps = WizardCommonProps & {
hasOCS: boolean;
existingStorageSystems: Set<StorageSystemKind['spec']['kind']>;
};

0 comments on commit 92b28f0

Please sign in to comment.