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 17, 2023
1 parent d768d0b commit 4be4ca7
Show file tree
Hide file tree
Showing 7 changed files with 247 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,10 @@
"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",
"Optimize": "Optimize",
"Configure cluster for Regional-DR?": "Configure cluster for Regional-DR?",
"Optimize the cluster for a Regional-DR setup by migrating OSDs. Migration may take some time depending on several factors. To learn more about OSD migration best practices and its consequences, refer to the documentation.": "Optimize the cluster for a Regional-DR setup by migrating OSDs. Migration may take some time depending on several factors. To learn more about OSD migration best practices and its consequences, refer to the documentation.",
"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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const DetailsCard: React.FC = () => {
: t('Disabled');

const ocsName = getName(resourcesObj['ocs'].data?.[0]);
//const disasterRecoveryStatus = getDRsetupStatus();

const [csv, csvLoaded, csvError] = useFetchCsv({
specName: !isODF ? OCS_OPERATOR : ODF_OPERATOR,
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 * as migrationStatus from './constants/osd-migration';
import OptimizeModal from './OptimizeModal';

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

// Mocking getMigrationStatus
jest.mock('./constants/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 getMigrationStatus to return 'Completed' for this test
migrationStatus.getMigrationStatus.mockReturnValue('Completed');

render(<OptimizeModal />);

// 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 getMigrationStatus to return 'Completed' for this test
migrationStatus.getMigrationStatus.mockReturnValue('Pending');

render(<OptimizeModal />);

// 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('Configure 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();
});
});
});
109 changes: 109 additions & 0 deletions packages/ocs/dashboards/persistent-internal/OptimizeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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 { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import {
K8sResourceKind,
k8sPatch,
} from '@openshift-console/dynamic-plugin-sdk';
import {
Modal,
ModalVariant,
Button,
ModalBoxHeader,
ModalBoxBody,
Title,
} from '@patternfly/react-core';
import { MigrationStatus, getMigrationStatus } from './constants/osd-migration';

const OptimizeModal: React.FC<OptimizeModalProps> = ({ cephData }) => {
const { t } = useCustomTranslation();
const dRSetupStatus = getMigrationStatus(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 = () => {
if (cephData && cephData.length > 0) {
const ceph = cephData[0];
const patch = [
{
op: 'add',
path: `/metadata/annotations/${DISASTER_RECOVERY_TARGET_ANNOTATION}`,
value: 'true',
},
];

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

return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<p>{t(dRSetupStatus)}</p>
{dRSetupStatus === MigrationStatus.Pending && (
<a onClick={openModal}>{t('Optimize')}</a>
)}
</div>
<div>
<Modal
variant={ModalVariant.medium}
title={t('Configure 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(
'Optimize the cluster for a Regional-DR setup by migrating OSDs. Migration may take some time depending on several factors. To learn more about OSD 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>
);
};

export default OptimizeModal;

type OptimizeModalProps = {
cephData?: K8sResourceKind[];
};
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';

export enum MigrationStatus {
InProgress = 'In Progress',
Pending = 'Pending',
Completed = 'Completed',
Unknown = '',
}

export const getMigrationStatus = (cephData) => {
if (cephData && cephData.length > 0) {
const ceph = cephData[0];
const bluestoreCount = ceph?.status?.storage?.osd?.storeType?.['bluestore'];
const bluestoreRdrCount =
ceph?.status?.storage?.osd?.storeType?.['bluestore-rdr'];

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

if (bluestoreCount > 0) {
if (bluestoreRdrCount > 0 || isDisasterRecoveryTarget) {
return MigrationStatus.InProgress;
} else {
return MigrationStatus.Pending;
}
} else if (bluestoreRdrCount > 0) {
return MigrationStatus.Completed;
}

return MigrationStatus.Unknown;
}
return MigrationStatus.Unknown;
};
22 changes: 21 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 @@ -22,15 +27,22 @@ import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core';
import { CEPH_NS } from '../../constants';
import { StorageClusterModel } from '../../models';
import { getNetworkEncryption } from '../../utils';
import OptimizeModal from './OptimizeModal';

const DetailsCard: React.FC = () => {
const { t } = useCustomTranslation();
const [cephData, cephLoaded, cephLoadError] = useK8sList<CephClusterKind>(
CephClusterModel,
CEPH_NS
);

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

const [csv, csvLoaded, csvError] = useFetchCsv({
specName: ODF_OPERATOR,
namespace: CEPH_STORAGE_NAMESPACE,
Expand Down Expand Up @@ -100,6 +112,14 @@ const DetailsCard: React.FC = () => {
>
{inTransitEncryptionStatus}
</DetailItem>
<DetailItem
key="dr_setup_state"
title={t('Disaster recovery optimisation')}
isLoading={!cephLoaded}
error={cephLoadError}
>
<OptimizeModal cephData={cephData} />
</DetailItem>
</DetailsBody>
</CardBody>
</Card>
Expand Down
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 4be4ca7

Please sign in to comment.