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 7, 2023
1 parent 046ef75 commit 8a4a717
Show file tree
Hide file tree
Showing 18 changed files with 757 additions and 46 deletions.
16 changes: 14 additions & 2 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,15 @@
"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",
"Client version is out of date": "Client version is out of date",
"Due to the misatch in the client and provider version. This provider cluster cannot be upgraded.": "Due to the misatch in the client and provider version. This provider cluster cannot be upgraded.",
"Get onboarding token": "Get onboarding token",
"Version Match": "Version Match",
"Client onboarding token": "Client onboarding token",
"Raw Capacity": "Raw Capacity",
"Add Capacity": "Add Capacity",
"External": "External",
Expand Down Expand Up @@ -1159,6 +1171,7 @@
"Receive bandwidth": "Receive bandwidth",
"Node details": "Node details",
"Instance type": "Instance type",
"Rack": "Rack",
"External ID": "External ID",
"Node addresses": "Node addresses",
"Machine": "Machine",
Expand Down Expand Up @@ -1205,6 +1218,5 @@
"Cannot change resource name (original: \"{{name}}\", updated: \"{{newName}}\").": "Cannot change resource name (original: \"{{name}}\", updated: \"{{newName}}\").",
"Cannot change resource namespace (original: \"{{namespace}}\", updated: \"{{newNamespace}}\").": "Cannot change resource namespace (original: \"{{namespace}}\", updated: \"{{newNamespace}}\").",
"Cannot change resource kind (original: \"{{original}}\", updated: \"{{updated}}\").": "Cannot change resource kind (original: \"{{original}}\", updated: \"{{updated}}\").",
"Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\").": "Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\").",
"Rack": "Rack"
"Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\").": "Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\")."
}
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 @@ -7,7 +7,11 @@ import Tabs, { TabPage } from '@odf/shared/utils/Tabs';
import { useFlag } from '@openshift-console/dynamic-plugin-sdk';
import { RouteComponentProps } from 'react-router';
import { match as Match } from 'react-router-dom';
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 @@ -44,10 +48,16 @@ const ODFSystemDashboard: React.FC<ODFSystemDashboardPageProps> = ({
]);
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 @@ -60,7 +70,7 @@ const ODFSystemDashboard: React.FC<ODFSystemDashboardPageProps> = ({
if (isBlockPoolAdded && isExternal) {
setPages((p) => p.filter((page) => page.href !== blockPoolHref));
}
}, [isExternal, isCephAvailable, pages, setPages, t]);
}, [isExternal, isCephAvailable, isProviderMode, pages, setPages, t]);

const title = match.params.systemName;
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';
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
71 changes: 71 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,71 @@
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';

export const getClientHealthState = (
client: StorageConsumerKind
): HealthState => {
const state = client.status.state;
let health = HealthState.UNKNOWN;
switch (state) {
case StorageConsumerState.Ready:
health = HealthState.OK;
break;
case StorageConsumerState.Configuring:
case StorageConsumerState.Deleting:
health = HealthState.LOADING;
break;
case StorageConsumerState.Disabled:
case StorageConsumerState.Failed:
health = HealthState.ERROR;
break;
default:
health = HealthState.UNKNOWN;
}
return health;
};

export const getAggregateClientHealthState = (
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;
if (totalClientsCount === healthyClientsCount) {
return HealthState.OK;
}
if (totalClientsCount > healthyClientsCount && healthyClientsCount > 0) {
return HealthState.ERROR;
}
if (totalClientsCount === 0) {
return HealthState.NOT_AVAILABLE;
}
return HealthState.UNKNOWN;
};

export const getClientText = (clients: StorageConsumerKind[], t: TFunction) => {
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;
if (totalClientsCount === 0) {
return t('0 connected');
} else {
return t('{{connected}} / {{total}} connected', {
connected: healthyClientsCount,
total: totalClientsCount,
});
}
};
Loading

0 comments on commit 8a4a717

Please sign in to comment.