Skip to content

Commit

Permalink
Provide option to enable DR support
Browse files Browse the repository at this point in the history
Signed-off-by: Timothy Asir Jeyasingh <[email protected]>
  • Loading branch information
TimothyAsirJeyasing committed Nov 21, 2023
1 parent d768d0b commit 72ce5b7
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 1 deletion.
4 changes: 4 additions & 0 deletions locales/en/plugin__odf-console.json
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@
"Only showing PVCs that are being mounted on an active pod": "Only showing PVCs that are being mounted on an active pod",
"This card shows the requested capacity for different Kubernetes resources. The figures shown represent the usable storage, meaning that data replication is not taken into consideration.": "This card shows the requested capacity for different Kubernetes resources. The figures shown represent the usable storage, meaning that data replication is not taken into consideration.",
"Internal": "Internal",
"Disaster recovery optimisation": "Disaster recovery optimisation",
"Raw capacity is the absolute total disk space available to the array subsystem.": "Raw capacity is the absolute total disk space available to the array subsystem.",
"Troubleshoot": "Troubleshoot",
"Active health checks": "Active health checks",
Expand All @@ -460,6 +461,9 @@
"Go To PVC List": "Go To PVC List",
"Save": "Save",
"BlockPool Update Form": "BlockPool Update Form",
"Optimize": "Optimize",
"Optimise cluster for Regional-DR?": "Optimise cluster for Regional-DR?",
"Configure the cluster for a Regional-DR setup by migrating OSDs. Migration may take sometime depending on several factors. To learn more about OSDs migration best practices and its consequences refer to the documentation.": "Configure the cluster for a Regional-DR setup by migrating OSDs. Migration may take sometime depending on several factors. To learn more about OSDs migration best practices and its consequences refer to the documentation.",
"Filesystem name": "Filesystem name",
"Enter filesystem name": "Enter filesystem name",
"CephFS filesystem name into which the volume shall be created": "CephFS filesystem name into which the volume shall be created",
Expand Down
71 changes: 71 additions & 0 deletions packages/ocs/dashboards/persistent-internal/OptimizeModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { screen, render, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { OSDMigrationModal } from '../../modals/osd-migration/osdMigrationModal';
import * as migrationStatus from '../../utils/osd-migration';

jest.mock('@odf/shared/selectors', () => ({
getName: jest.fn().mockReturnValue('test'),
}));

// Mocking getOSDMigrationStatus
jest.mock('../../utils/osd-migration');

jest.mock('@odf/shared/hooks/useK8sList', () => ({
__esModule: true,
useK8sList: () => [
[
{
metadata: {
name: 'test',
},
},
],
true,
undefined,
],
}));

describe('OptimizeModal Component', () => {
test('renders without errors', async () => {
// Mock getOSDMigrationStatus to return 'Completed' for this test
migrationStatus.getOSDMigrationStatus.mockReturnValue('Completed');

render(<OSDMigrationModal />);

// Wait for the component to finish rendering (use async/await)
await waitFor(() => {
expect(screen.queryByText('Optimize')).not.toBeInTheDocument();
});
});

test('renders with migration pending', async () => {
// Mock getOSDMigrationStatus to return 'Completed' for this test
migrationStatus.getOSDMigrationStatus.mockReturnValue('Pending');

render(<OSDMigrationModal />);

// Wait for the component to finish rendering (use async/await)
await waitFor(() => {
expect(screen.queryByText('Optimize')).toBeInTheDocument();
});

fireEvent.click(screen.getByText(/Optimize/i));

// Wait for modal to be visible
await waitFor(() => {
expect(
screen.queryByText('Optimise cluster for Regional-DR?')
).toBeInTheDocument();
});

fireEvent.click(screen.getByText('Close'));

// Wait for modal to be closed
await waitFor(() => {
expect(
screen.queryByText('Configure cluster for Regional-DR?')
).not.toBeInTheDocument();
});
});
});
23 changes: 22 additions & 1 deletion packages/ocs/dashboards/persistent-internal/details-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import { useK8sGet } from '@odf/shared/hooks/k8s-get-hook';
import { useFetchCsv } from '@odf/shared/hooks/use-fetch-csv';
import { useK8sList } from '@odf/shared/hooks/useK8sList';
import {
CephClusterModel,
ClusterServiceVersionModel,
InfrastructureModel,
} from '@odf/shared/models';
import { getName } from '@odf/shared/selectors';
import { K8sResourceKind, StorageClusterKind } from '@odf/shared/types';
import {
CephClusterKind,
K8sResourceKind,
StorageClusterKind,
} from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import {
getInfrastructurePlatform,
Expand All @@ -20,17 +25,25 @@ import { OverviewDetailItem as DetailItem } from '@openshift-console/plugin-shar
import { Link } from 'react-router-dom';
import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core';
import { CEPH_NS } from '../../constants';
import { OSDMigrationModal } from '../../modals/osd-migration/osdMigrationModal';
import { StorageClusterModel } from '../../models';
import { getNetworkEncryption } from '../../utils';

const DetailsCard: React.FC = () => {
const { t } = useCustomTranslation();

const [infrastructure, infrastructureLoaded, infrastructureError] =
useK8sGet<K8sResourceKind>(InfrastructureModel, 'cluster');
const [ocsData, ocsLoaded, ocsError] = useK8sList<StorageClusterKind>(
StorageClusterModel,
CEPH_NS
);

const [cephData, cephLoaded, cephLoadError] = useK8sList<CephClusterKind>(
CephClusterModel,
ocsData?.[0].metadata?.namespace
);

const [csv, csvLoaded, csvError] = useFetchCsv({
specName: ODF_OPERATOR,
namespace: CEPH_STORAGE_NAMESPACE,
Expand Down Expand Up @@ -100,6 +113,14 @@ const DetailsCard: React.FC = () => {
>
{inTransitEncryptionStatus}
</DetailItem>
<DetailItem
key="osd_migration"
title={t('Disaster recovery optimisation')}
isLoading={!cephLoaded}
error={cephLoadError}
>
<OSDMigrationModal cephData={cephData?.[0]} />
</DetailItem>
</DetailsBody>
</CardBody>
</Card>
Expand Down
113 changes: 113 additions & 0 deletions packages/ocs/modals/osd-migration/osdMigrationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as React from 'react';
import { DISASTER_RECOVERY_TARGET_ANNOTATION } from '@odf/core/constants';
import { CephClusterModel } from '@odf/shared/models';
import { getName, getNamespace } from '@odf/shared/selectors';
import { CephClusterKind } from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import {
RedExclamationCircleIcon,
k8sPatch,
} from '@openshift-console/dynamic-plugin-sdk';
import {
Modal,
ModalVariant,
Button,
ModalBoxHeader,
ModalBoxBody,
Title,
} from '@patternfly/react-core';
import {
FAILED,
PENDING,
getOSDMigrationStatus,
} from '../../utils/osd-migration';

export const OSDMigrationModal: React.FC<OSDMigrationModalProps> = ({
cephData,
}) => {
const { t } = useCustomTranslation();
const dRSetupStatus: string = getOSDMigrationStatus(cephData);
const [isOpen, setOpen] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
const openModal = () => setOpen(true);

const closeModal = () => {
setOpen(false);
setErrorMessage(null);
};

const handleOptimize = () => {
const patch = [
{
op: 'add',
path: `/metadata/annotations/${DISASTER_RECOVERY_TARGET_ANNOTATION}`,
value: 'true',
},
];

k8sPatch({
model: CephClusterModel,
resource: {
metadata: {
name: getName(cephData),
namespace: getNamespace(cephData),
},
},
data: patch,
})
.then(() => {
closeModal();
})
.catch((err) => {
setErrorMessage(err.message);
});
};

return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<p>
{dRSetupStatus === FAILED && <RedExclamationCircleIcon />}
{t(dRSetupStatus)}
</p>
{dRSetupStatus === PENDING && (
<a onClick={openModal}>{t('Optimize')}</a>
)}
</div>
<div>
<Modal
variant={ModalVariant.medium}
title={t('Optimise cluster for Regional-DR?')}
isOpen={isOpen}
onClose={closeModal}
actions={[
<Button key="close" variant="secondary" onClick={closeModal}>
Close
</Button>,
<Button key="optimize" variant="primary" onClick={handleOptimize}>
Optimize
</Button>,
]}
>
<p>
{t(
'Configure the cluster for a Regional-DR setup by migrating OSDs. Migration may take sometime depending on several factors. To learn more about OSDs migration best practices and its consequences refer to the documentation.'
)}
</p>
{errorMessage && (
<ModalBoxHeader>
<Title headingLevel="h2" size="lg">
Error
</Title>
</ModalBoxHeader>
)}
<ModalBoxBody>{errorMessage}</ModalBoxBody>
</Modal>
</div>
</div>
);
};

type OSDMigrationModalProps = {
cephData?: CephClusterKind;
};
35 changes: 35 additions & 0 deletions packages/ocs/utils/osd-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DISASTER_RECOVERY_TARGET_ANNOTATION } from '@odf/core/constants';
import { getAnnotations } from '@odf/shared/selectors';
import { CephClusterKind } from '@odf/shared/types';

export const IN_PROGRESS = 'In Progress';
export const PENDING = 'Pending';
export const COMPLETED = 'Completed';
export const FAILED = 'Failed';
export const BLUESTORE_RDR = 'bluestore-rdr';
export const BLUESTORE = 'bluestore';

export const getOSDMigrationStatus = (ceph: CephClusterKind) => {
if (!!ceph) {
const bluestoreCount = ceph?.status?.storage?.osd?.storeType?.[BLUESTORE];
const bluestoreRdrCount =
ceph?.status?.storage?.osd?.storeType?.[BLUESTORE_RDR];

const isDisasterRecoveryTarget =
getAnnotations(ceph)?.[DISASTER_RECOVERY_TARGET_ANNOTATION] === 'true';

if (bluestoreCount > 0) {
if (bluestoreRdrCount > 0 || isDisasterRecoveryTarget) {
return IN_PROGRESS;
} else {
return PENDING;
}
} else if (bluestoreRdrCount > 0) {
return COMPLETED;
}
} else {
return FAILED;
} // TODO Add condition for migration failure

return '';
};
6 changes: 6 additions & 0 deletions packages/shared/src/types/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ type CephDeviceClass = {
export type CephClusterKind = K8sResourceCommon & {
status?: {
storage: {
osd: {
storeType: {
bluestore: number;
'bluestore-rdr': number;
};
};
deviceClasses: CephDeviceClass[];
};
ceph?: {
Expand Down

0 comments on commit 72ce5b7

Please sign in to comment.