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 13, 2021
1 parent cb618e6 commit 2d06224
Show file tree
Hide file tree
Showing 19 changed files with 831 additions and 6 deletions.
14 changes: 14 additions & 0 deletions frontend/packages/ceph-storage-plugin/console-extensions.json
Expand Up @@ -111,5 +111,19 @@
"required": ["MCG"],
"disallowed": ["OCS"]
}
},
{
"type": "console.page/route",
"properties": {
"exact": true,
"path": [
"/k8s/ns/:ns/operators.coreos.com~v1alpha1~ClusterServiceVersion/:appName/odf.openshift.io~v1alpha1~StorageSystem/~new",
"/k8s/ns/:ns/clusterserviceversions/:appName/odf.openshift.io~v1alpha1~StorageSystem/~new"
],
"component": {
"$codeRef": "createStorageSystem"
}
}
}

]
Expand Up @@ -139,6 +139,24 @@
"Name can contain a max of 43 characters": "Name can contain a max of 43 characters",
"Provider": "Provider",
"Create BackingStore": "Create BackingStore",
"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",
"An error has occurred: {{error}}": "An error has occurred: {{error}}",
"An error has occurred": "An error has 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.",
"Not available": "Not available",
"Not enough usage data": "Not enough usage data",
"used": "used",
Expand Down Expand Up @@ -422,9 +440,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 +523,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
5 changes: 3 additions & 2 deletions frontend/packages/ceph-storage-plugin/package.json
Expand Up @@ -25,8 +25,9 @@
]
},
"exposedModules": {
"alert": "./src/utils/alert-action-path.tsx",
"storageProvider": "./src/components/attach-obc/attach-obc-deployment.tsx"
"alert": "src/utils/alert-action-path.tsx",
"storageProvider": "src/components/attach-obc/attach-obc-deployment.tsx",
"createStorageSystem": "src/components/create-storage-system/create-storage-system.tsx"
}
}
}
@@ -0,0 +1,3 @@
.odf-backing-storage__selection--width {
width: 22rem;
}
@@ -0,0 +1,188 @@
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 {
ODF_EXTERNAL_PROVIDERS,
ExternalProvider,
StorageClusterIdentifier,
} 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';
import { ErrorHandler } from '../../error-handler';

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.id} value={p.id} 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'];
};

const formatStorageSystemList = (storageSystems: StorageSystemKind[] = []): StorageSystemSet =>
storageSystems.reduce(
(kinds: StorageSystemSet, ss: StorageSystemKind) => kinds.add(ss.spec.kind),
new Set(),
);

type StorageSystemSet = Set<StorageSystemKind['spec']['kind']>;

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

const formattedSS: StorageSystemSet = formatStorageSystemList(storageSystems);

const hasOCS: boolean = formattedSS.has(StorageClusterIdentifier);

const externalProviders = ODF_EXTERNAL_PROVIDERS.filter(
(provider) => !formattedSS.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 (
<ErrorHandler error={error} loaded={loaded}>
<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>
</ErrorHandler>
);
};

type BackingStorageProps = WizardCommonProps & {
storageSystems: StorageSystemKind[];
error: any;
loaded: boolean;
};
@@ -0,0 +1,114 @@
import { TFunction } from 'i18next';
import { WizardStep } from '@patternfly/react-core';
import { ExternalProviderId } from '../../../odf-external-providers/external-providers';
import { WizardState } from '../reducer';
import { BackingStorageType, StepsId } from '../../../constants/create-storage-system';

export const createSteps = (
t: TFunction,
state: WizardState,
hasStorageCluster: boolean,
): WizardStep[] => {
const { backingStorage, currentStep } = state;

const commonSteps = {
capacityAndNodes: {
id: StepsId.CapacityAndNodes,
name: t('ceph-storage-plugin~Capacity and nodes'),
},
securityAndNetwork: {
id: StepsId.SecurityAndNetwork,
name: t('ceph-storage-plugin~Security and network'),
},
reviewAndCreate: {
id: StepsId.ReviewAndCreate,
name: t('ceph-storage-plugin~Review and create'),
},
};

const rhcsExternalProviderSteps = [
{
id: StepsId.ConnectionDetails,
canJumpTo: currentStep >= 2,
name: t('ceph-storage-plugin~Connection details'),
},
{
id: StepsId.ReviewAndCreate,
canJumpTo: currentStep >= 3,
...commonSteps.reviewAndCreate,
},
];

const nonRhcsExternalProviderStep = {
canJumpTo: currentStep >= 2,
id: StepsId.CreateStorageClass,
name: t('ceph-storage-plugin~Create storage class'),
};

switch (backingStorage.type) {
case BackingStorageType.EXISTING:
return [
{
canJumpTo: currentStep >= 2,
...commonSteps.capacityAndNodes,
},
{
canJumpTo: currentStep >= 3,
...commonSteps.securityAndNetwork,
},
{
canJumpTo: currentStep >= 4,
...commonSteps.reviewAndCreate,
},
];
case BackingStorageType.LOCAL_DEVICES:
return [
{
id: StepsId.CreateLocalVolumeSet,
canJumpTo: currentStep >= 2,
name: t('ceph-storage-plugin~Create local volume set'),
},
{
canJumpTo: currentStep >= 3,
...commonSteps.capacityAndNodes,
},
{
canJumpTo: currentStep >= 4,
id: StepsId.SecurityAndNetwork,
...commonSteps.securityAndNetwork,
},
{
canJumpTo: currentStep >= 5,
id: StepsId.ReviewAndCreate,
...commonSteps.reviewAndCreate,
},
];
case BackingStorageType.EXTERNAL:
if (backingStorage.externalProvider === ExternalProviderId.RHCS) {
return rhcsExternalProviderSteps;
}
if (!hasStorageCluster) {
return [
nonRhcsExternalProviderStep,
{ canJumpTo: currentStep >= 3, ...commonSteps.capacityAndNodes },
{
canJumpTo: currentStep >= 4,
...commonSteps.securityAndNetwork,
},
{
canJumpTo: currentStep >= 5,
...commonSteps.reviewAndCreate,
},
];
}
return [
nonRhcsExternalProviderStep,
{
canJumpTo: currentStep >= 3,
...commonSteps.reviewAndCreate,
},
];
default:
return [];
}
};
@@ -0,0 +1,2 @@
export { BackingStorage } from './backing-storage-step/backing-storage';
export { createSteps } from './create-steps';

0 comments on commit 2d06224

Please sign in to comment.