Skip to content

Commit

Permalink
Adds support for HCI Phase 2<RHSTOR-4885>
Browse files Browse the repository at this point in the history
Adds list page for Clients
Adds Modal for generating onboarding token for clients
Updates ODF dashboard to add Client Status
Adds flags for Provider Mode
Disables StorageClass and BlockPool creation in Provider mode

Signed-off-by: Bipul Adhikari <[email protected]>
  • Loading branch information
bipuladh committed Dec 12, 2023
1 parent 79af2c7 commit 7b8040b
Show file tree
Hide file tree
Showing 19 changed files with 734 additions and 44 deletions.
14 changes: 14 additions & 0 deletions locales/en/plugin__odf-console.json
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,9 @@
"Storage Systems": "Storage Systems",
"External object provider used capacity": "External object provider used capacity",
"Performance Card": "Performance Card",
"Storage Clients": "Storage Clients",
"0 connected": "0 connected",
"{{connected}} / {{total}} connected": "{{connected}} / {{total}} connected",
"System raw capacity": "System raw capacity",
"No data available": "No data available",
"Provider details": "Provider details",
Expand All @@ -942,6 +945,17 @@
"NamespaceStore details": "NamespaceStore details",
"Target Blob Container": "Target Blob Container",
"Num Volumes": "Num Volumes",
"Cluster ID": "Cluster ID",
"Openshift version": "Openshift version",
"Data Foundation version": "Data Foundation version",
"Last heartbeat": "Last heartbeat",
"ago": "ago",
"Client version is out of date": "Client version is out of date",
"Due to the mismatch in the client and provider version this provider cluster cannot be upgraded.": "Due to the mismatch in the client and provider version this provider cluster cannot be upgraded.",
"Storage clients": "Storage clients",
"Generate client onboarding token": "Generate client onboarding token",
"Data Foundation version sync": "Data Foundation version sync",
"Client onboarding token": "Client onboarding token",
"Raw Capacity": "Raw Capacity",
"Add Capacity": "Add Capacity",
"External": "External",
Expand Down
16 changes: 13 additions & 3 deletions packages/ocs/dashboards/odf-system-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { referenceForModel } from '@odf/shared/utils';
import Tabs, { TabPage } from '@odf/shared/utils/Tabs';
import { useFlag } from '@openshift-console/dynamic-plugin-sdk';
import { useParams } from 'react-router-dom-v5-compat';
import { CEPH_FLAG, OCS_INDEPENDENT_FLAG } from '../../odf/features';
import {
CEPH_FLAG,
OCS_INDEPENDENT_FLAG,
PROVIDER_MODE,
} from '../../odf/features';
import { BlockPoolListPage } from '../block-pool/BlockPoolListPage';
import { CephBlockPoolModel } from '../models';
import OCSSystemDashboard from './ocs-system-dashboard';
Expand Down Expand Up @@ -36,10 +40,16 @@ const ODFSystemDashboard: React.FC<{}> = ({}) => {
]);
const isCephAvailable = useFlag(CEPH_FLAG);
const isExternal = useFlag(OCS_INDEPENDENT_FLAG);
const isProviderMode = useFlag(PROVIDER_MODE);

React.useEffect(() => {
const isBlockPoolAdded = pages.find((page) => page.href === blockPoolHref);
if (isCephAvailable && !isBlockPoolAdded && !isExternal) {
if (
isCephAvailable &&
!isBlockPoolAdded &&
!isExternal &&
!isProviderMode
) {
setPages((p) => [
...p,
{
Expand All @@ -52,7 +62,7 @@ const ODFSystemDashboard: React.FC<{}> = ({}) => {
if (isBlockPoolAdded && isExternal) {
setPages((p) => p.filter((page) => page.href !== blockPoolHref));
}
}, [isExternal, isCephAvailable, pages, setPages, t]);
}, [isExternal, isCephAvailable, isProviderMode, pages, setPages, t]);

const arePagesLoaded = pages.length > 0;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.odf-storageSystemPopup__item--margin {
.odf-status-card__popup--margin {
// Overriding PF default margin for FlexItem
margin-bottom: 0 !important;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react';
import Status, { StatusPopupSection } from '@odf/shared/popup/status-popup';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { HealthState } from '@openshift-console/dynamic-plugin-sdk';
import { Link } from 'react-router-dom-v5-compat';
import { Flex, FlexItem } from '@patternfly/react-core';
Expand All @@ -9,17 +8,19 @@ import {
ExclamationCircleIcon,
ExclamationTriangleIcon,
} from '@patternfly/react-icons';
import './storage-system-popup.scss';
import './status-card-popover.scss';

type SystemHealthMap = {
systemName: string;
export type ResourceHealthMap = {
itemName: string;
healthState: HealthState;
link: string;
link?: string;
extraTexts?: string[];
};

type StorageSystemPopopProps = {
systemHealthMap: SystemHealthMap[];
type StatusCardPopoverProps = {
resourceHealthMap: ResourceHealthMap[];
firstColumnName: string;
secondColumnName: string;
};

const healthStateToIcon = {
Expand All @@ -34,27 +35,32 @@ const healthStateToIcon = {
),
};

const StorageSystemPopup: React.FC<StorageSystemPopopProps> = ({
systemHealthMap,
const StatusCardPopover: React.FC<StatusCardPopoverProps> = ({
resourceHealthMap,
firstColumnName,
secondColumnName,
}) => {
const { t } = useCustomTranslation();
return (
<StatusPopupSection
firstColumn={t('Storage System')}
secondColumn={t('Health')}
firstColumn={firstColumnName}
secondColumn={secondColumnName}
>
{systemHealthMap.map((system) => (
{resourceHealthMap.map((resource) => (
<Status
key={system.systemName}
icon={healthStateToIcon[system.healthState]}
key={resource.itemName}
icon={healthStateToIcon[resource.healthState]}
>
<Flex direction={{ default: 'column' }}>
<FlexItem className="odf-storageSystemPopup__item--margin">
<Link to={system.link}>{system.systemName}</Link>
<FlexItem className="odf-status-card__popup--margin">
{resource.link ? (
<Link to={resource.link}>{resource.itemName}</Link>
) : (
<>{resource.itemName}</>
)}
</FlexItem>
{!!system.extraTexts && (
{!!resource.extraTexts && (
<FlexItem>
{system.extraTexts.map((extraText, i) => (
{resource.extraTexts.map((extraText, i) => (
<div className="text-muted" key={i}>
{extraText}
</div>
Expand All @@ -68,4 +74,4 @@ const StorageSystemPopup: React.FC<StorageSystemPopopProps> = ({
);
};

export default StorageSystemPopup;
export default StatusCardPopover;
52 changes: 48 additions & 4 deletions packages/odf/components/odf-dashboard/status-card/status-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { useSafeK8sWatchResource } from '@odf/core/hooks';
import { K8sResourceObj } from '@odf/core/types';
import { useGetOCSHealth } from '@odf/ocs/hooks';
import { StorageConsumerKind } from '@odf/shared';
import { ODF_OPERATOR } from '@odf/shared/constants';
import HealthItem from '@odf/shared/dashboards/status-card/HealthItem';
import { healthStateMap } from '@odf/shared/dashboards/status-card/states';
Expand All @@ -21,8 +22,13 @@ import {
referenceForGroupVersionKind,
getOperatorHealthState,
} from '@odf/shared/utils';
import { HealthState } from '@openshift-console/dynamic-plugin-sdk';
import {
HealthState,
useFlag,
useK8sWatchResource,
} from '@openshift-console/dynamic-plugin-sdk';
import { HealthBody } from '@openshift-console/dynamic-plugin-sdk-internal';
import { useHistory } from 'react-router';
import {
Gallery,
GalleryItem,
Expand All @@ -32,9 +38,12 @@ import {
CardHeader,
CardTitle,
} from '@patternfly/react-core';
import { PROVIDER_MODE } from '../../../features';
import { StorageConsumerModel } from '../../../models';
import { getVendorDashboardLinkFromMetrics } from '../../utils';
import { StorageDashboard, STATUS_QUERIES } from '../queries';
import StorageSystemPopup from './storage-system-popup';
import StatusCardPopover from './status-card-popover';
import { getAggregateClientHealthState, getClientText } from './utils';
import './status-card.scss';

const operatorResource: K8sResourceObj = (ns) => ({
Expand Down Expand Up @@ -129,6 +138,23 @@ export const StatusCard: React.FC = () => {
csvLoadError
);

const isProviderMode = useFlag(PROVIDER_MODE);

const [clients, clientsLoaded, clientsLoadError] = useK8sWatchResource<
StorageConsumerKind[]
>({
kind: referenceForModel(StorageConsumerModel),
isList: true,
});

const clientAggregateHealth = getAggregateClientHealthState(clients);

const history = useHistory();

const redirectToListPage = React.useCallback(() => {
history.push('/odf/storage-clients');
}, [history]);

return (
<Card className="odfDashboard-card--height">
<CardHeader>
Expand All @@ -149,7 +175,11 @@ export const StatusCard: React.FC = () => {
title={pluralize(healthySystems.length, 'Storage System')}
state={HealthState.OK}
>
<StorageSystemPopup systemHealthMap={healthySystems} />
<StatusCardPopover
resourceHealthMap={healthySystems}
firstColumnName={t('Storage System')}
secondColumnName={t('Health')}
/>
</HealthItem>
</GalleryItem>
)}
Expand All @@ -160,10 +190,24 @@ export const StatusCard: React.FC = () => {
state={HealthState.ERROR}
maxWidth="35rem"
>
<StorageSystemPopup systemHealthMap={unHealthySystems} />
<StatusCardPopover
resourceHealthMap={unHealthySystems}
firstColumnName={t('Storage System')}
secondColumnName={t('Health')}
/>
</HealthItem>
</GalleryItem>
)}
{isProviderMode && clientsLoaded && !clientsLoadError && (
<GalleryItem>
<HealthItem
title={t('Storage Clients')}
state={clientAggregateHealth}
onClick={redirectToListPage}
details={getClientText(clients, t)}
/>
</GalleryItem>
)}
</Gallery>
</HealthBody>
</CardBody>
Expand Down
48 changes: 48 additions & 0 deletions packages/odf/components/odf-dashboard/status-card/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { StorageConsumerKind, StorageConsumerState } from '@odf/shared';
import { getTimeDifferenceInSeconds } from '@odf/shared/details-page/datetime';
import { HealthState } from '@openshift-console/dynamic-plugin-sdk';
import { TFunction } from 'i18next';

const getHealthAndTotalClientCounts = (clients: StorageConsumerKind[]) => {
const connectedClients = clients.filter(
(client) => client.status.state === StorageConsumerState.Ready
);

const healthyClients = connectedClients.filter(
(client) => getTimeDifferenceInSeconds(client.status.lastHeartbeat) < 120
);
const healthyClientsCount = healthyClients.length;
const totalClientsCount = clients.length;

return [healthyClientsCount, totalClientsCount];
};

export const getAggregateClientHealthState = (
clients: StorageConsumerKind[] = []
) => {
const [healthyClientsCount, totalClientsCount] =
getHealthAndTotalClientCounts(clients);
if (totalClientsCount === healthyClientsCount && totalClientsCount > 0) {
return HealthState.OK;
}
if (totalClientsCount > healthyClientsCount) {
return HealthState.ERROR;
}
if (totalClientsCount === 0) {
return HealthState.NOT_AVAILABLE;
}
return HealthState.UNKNOWN;
};

export const getClientText = (clients: StorageConsumerKind[], t: TFunction) => {
const [healthyClientsCount, totalClientsCount] =
getHealthAndTotalClientCounts(clients);
if (totalClientsCount === 0) {
return t('0 connected');
} else {
return t('{{connected}} / {{total}} connected', {
connected: healthyClientsCount,
total: totalClientsCount,
});
}
};
Loading

0 comments on commit 7b8040b

Please sign in to comment.