Skip to content

Commit

Permalink
Add messages to node's status card
Browse files Browse the repository at this point in the history
  • Loading branch information
rawagner committed Apr 17, 2020
1 parent 5f713fd commit 41c042b
Show file tree
Hide file tree
Showing 22 changed files with 925 additions and 424 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ const ControlPlanePopup: React.FC<PrometheusHealthPopupProps> = ({ responses })
icon = healthStateMapping[health.state].icon;
}
return (
<Status key={titles[index]} title={titles[index]} value={health.message} icon={icon} />
<Status key={titles[index]} value={health.message} icon={icon}>
{titles[index]}
</Status>
);
})}
</StatusPopupSection>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import * as React from 'react';
import * as _ from 'lodash';
import { PopoverPosition } from '@patternfly/react-core';
import AlertsBody from '@console/shared/src/components/dashboard/status-card/AlertsBody';
import { NodeDashboardContext } from '@console/app/src/components/nodes/node-dashboard/NodeDashboardContext';
import { StatusItem } from '@console/shared/src/components/dashboard/status-card/AlertItem';
import {
LIMIT_STATE,
LimitRequested,
} from '@console/shared/src/components/dashboard/utilization-card/UtilizationItem';
import {
getUtilizationQueries,
getResourceQutoaQueries,
NodeQueries,
} from '@console/app/src/components/nodes/node-dashboard/queries';
import {
getNodeAddresses,
getNodeMachineNameAndNamespace,
} from '@console/shared/src/selectors/node';
import { usePrometheusQuery } from '@console/shared/src/components/dashboard/utilization-card/prometheus-hook';
import {
humanizeCpuCores,
humanizeBinaryBytes,
Humanize,
} from '@console/internal/components/utils';
import {
CPUPopover,
PopoverProps,
MemoryPopover,
} from '@console/app/src/components/nodes/node-dashboard/UtilizationCard';
import {
YellowResourcesAlmostFullIcon,
RedResourcesFullIcon,
YellowExclamationTriangleIcon,
} from '@console/shared/src/components/status/icons';
import { DashboardCardPopupLink } from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardLink';
import {
referenceForModel,
MachineKind,
MachineHealthCheckKind,
} from '@console/internal/module/k8s';
import { MachineModel } from '@console/internal/models';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';

import * as msg from './messages';
import { getMachineHealth, HealthChecksPopup, machineHealthChecksResource } from './NodeHealth';

const LimitLink: React.FC<LimitLinkProps> = ({
humanize,
currentKey,
totalKey,
limitKey,
requestedKey,
limitState,
requestedState,
Popover,
}) => {
const { obj } = React.useContext(NodeDashboardContext);
const nodeName = obj.metadata.name;
const nodeIp = getNodeAddresses(obj).find((addr) => addr.type === 'InternalIP')?.address;
const [queries, resourceQuotaQueries] = React.useMemo(
() => [getUtilizationQueries(nodeName, nodeIp), getResourceQutoaQueries(nodeName)],
[nodeIp, nodeName],
);
const [current, currentError, currentValue] = usePrometheusQuery(queries[currentKey], humanize);
const [total, totalError, totalValue] = usePrometheusQuery(queries[totalKey], humanize);
const [limit, limitError] = usePrometheusQuery(resourceQuotaQueries[limitKey], humanize);
const [requested, requestedError] = usePrometheusQuery(
resourceQuotaQueries[requestedKey],
humanize,
);

const available =
currentValue && totalValue ? humanize(totalValue - currentValue).string : 'Not available';

return (
<Popover
title="See breakdown"
nodeName={nodeName}
nodeIp={nodeIp}
current={currentError ? 'Not available' : current.string}
total={totalError ? 'Not available' : total.string}
limit={limitError ? 'Not available' : limit.string}
requested={requestedError ? 'Not available' : requested.string}
available={available}
limitState={limitState}
requestedState={requestedState}
position={PopoverPosition.right}
/>
);
};

const getMessage: GetMessage = (
limitState,
{ limReqErr, limReqWarn, limErr, limWarn, reqWarn },
) => {
const { limit, requested } = limitState || {};
if (!limitState || (limit === LIMIT_STATE.OK && requested === LIMIT_STATE.OK)) {
return null;
}
if (limit === LIMIT_STATE.ERROR) {
return {
Icon: RedResourcesFullIcon,
message: requested === LIMIT_STATE.OK ? limErr : limReqErr,
};
}
if (limit === LIMIT_STATE.WARN) {
return {
Icon: YellowResourcesAlmostFullIcon,
message: requested === LIMIT_STATE.OK ? limWarn : limReqWarn,
};
}
return {
Icon: YellowResourcesAlmostFullIcon,
message: reqWarn,
};
};

const HealthChecksLink: React.FC = () => {
const { obj } = React.useContext(NodeDashboardContext);
const { name, namespace } = getNodeMachineNameAndNamespace(obj);
const machineResource = React.useMemo(
() => ({
kind: referenceForModel(MachineModel),
name,
namespace,
}),
[name, namespace],
);
const machine = useK8sWatchResource<MachineKind>(machineResource);
const healthChecks = useK8sWatchResource<MachineHealthCheckKind[]>(machineHealthChecksResource);
const healthState = getMachineHealth(obj, machine, healthChecks);
return (
<DashboardCardPopupLink
linkTitle="See details"
popupTitle="Health Checks"
className="co-status-card__popup"
>
<HealthChecksPopup
conditions={healthState.conditions}
machineHealthChecks={healthState.matchingHC}
/>
</DashboardCardPopupLink>
);
};

const NodeAlerts: React.FC = () => {
const { cpuLimit, memoryLimit, healthCheck } = React.useContext(NodeDashboardContext);

const cpuMessage = getMessage(cpuLimit, {
limReqErr: msg.CPU_LIMIT_REQ_ERROR,
limErr: msg.CPU_LIMIT_ERROR,
limReqWarn: msg.CPU_LIMIT_REQ_WARN,
limWarn: msg.CPU_LIMIT_WARN,
reqWarn: msg.CPU_REQ_WARN,
});
const memoryMessage = getMessage(memoryLimit, {
limReqErr: msg.MEM_LIMIT_REQ_ERROR,
limErr: msg.MEM_LIMIT_ERROR,
limReqWarn: msg.MEM_LIMIT_REQ_WARN,
limWarn: msg.MEM_LIMIT_WARN,
reqWarn: msg.MEM_REQ_WARN,
});

return (
<AlertsBody
isLoading={!cpuLimit && !memoryLimit && _.isNil(healthCheck)}
emptyMessage="No node messages"
>
{!!healthCheck && (
<StatusItem Icon={YellowExclamationTriangleIcon} message={msg.CONDITIONS_WARNING}>
<HealthChecksLink />
</StatusItem>
)}
{!!cpuMessage && (
<StatusItem Icon={cpuMessage.Icon} message={cpuMessage.message}>
<LimitLink
humanize={humanizeCpuCores}
currentKey={NodeQueries.CPU_USAGE}
totalKey={NodeQueries.CPU_TOTAL}
limitKey={NodeQueries.POD_RESOURCE_LIMIT_CPU}
requestedKey={NodeQueries.POD_RESOURCE_REQUEST_CPU}
limitState={cpuLimit?.limit}
requestedState={cpuLimit?.requested}
Popover={CPUPopover}
/>
</StatusItem>
)}
{!!memoryMessage && (
<StatusItem Icon={memoryMessage.Icon} message={memoryMessage.message}>
<LimitLink
humanize={humanizeBinaryBytes}
currentKey={NodeQueries.MEMORY_USAGE}
totalKey={NodeQueries.MEMORY_TOTAL}
limitKey={NodeQueries.POD_RESOURCE_LIMIT_MEMORY}
requestedKey={NodeQueries.POD_RESOURCE_REQUEST_MEMORY}
limitState={memoryLimit?.limit}
requestedState={memoryLimit?.requested}
Popover={MemoryPopover}
/>
</StatusItem>
)}
</AlertsBody>
);
};

export default NodeAlerts;

type GetMessage = (
state: LimitRequested,
messages: {
limReqErr: string;
limErr: string;
limReqWarn: string;
limWarn: string;
reqWarn: string;
},
) => {
Icon: React.ComponentType;
message: string;
};

type LimitLinkProps = {
humanize: Humanize;
currentKey: string;
totalKey: string;
limitKey: string;
requestedKey: string;
limitState: LIMIT_STATE;
requestedState: LIMIT_STATE;
Popover: React.ComponentType<PopoverProps>;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from 'react';
import * as _ from 'lodash';
import Dashboard from '@console/shared/src/components/dashboard/Dashboard';
import DashboardGrid from '@console/shared/src/components/dashboard/DashboardGrid';
import { NodeKind } from '@console/internal/module/k8s';
import { LimitRequested } from '@console/shared/src/components/dashboard/utilization-card/UtilizationItem';

import { NodeDashboardContext } from './NodeDashboardContext';
import InventoryCard from './InventoryCard';
Expand All @@ -14,9 +16,91 @@ const leftCards = [{ Card: DetailsCard }, { Card: InventoryCard }];
const mainCards = [{ Card: StatusCard }, { Card: UtilizationCard }];
const rightCards = [{ Card: ActivityCard }];

export enum ActionType {
CPU_LIMIT = 'CPU_LIMIT',
MEMORY_LIMIT = 'MEMORY_LIMIT',
HEALTH_CHECK = 'HEALTH_CHECK',
OBJ = 'OBJ',
}

export const initialState = (obj: NodeKind): NodeDashboardState => ({
obj,
cpuLimit: undefined,
memoryLimit: undefined,
healthCheck: undefined,
});

export const reducer = (state: NodeDashboardState, action: NodeDashboardAction) => {
switch (action.type) {
case ActionType.CPU_LIMIT: {
if (_.isEqual(action.payload, state.cpuLimit)) {
return state;
}
return {
...state,
cpuLimit: action.payload,
};
}
case ActionType.MEMORY_LIMIT: {
if (_.isEqual(action.payload, state.memoryLimit)) {
return state;
}
return {
...state,
memoryLimit: action.payload,
};
}
case ActionType.HEALTH_CHECK: {
if (action.payload === state.healthCheck) {
return state;
}
return {
...state,
healthCheck: action.payload,
};
}
case ActionType.OBJ: {
if (action.payload === state.obj) {
return state;
}
return {
...state,
obj: action.payload,
};
}
default:
return state;
}
};

const NodeDashboard: React.FC<NodeDashboardProps> = ({ obj }) => {
const [state, dispatch] = React.useReducer(reducer, initialState(obj));

if (obj !== state.obj) {
dispatch({ type: ActionType.OBJ, payload: obj });
}

const setCPULimit = React.useCallback(
(payload: LimitRequested) => dispatch({ type: ActionType.CPU_LIMIT, payload }),
[],
);
const setMemoryLimit = React.useCallback(
(payload: LimitRequested) => dispatch({ type: ActionType.MEMORY_LIMIT, payload }),
[],
);
const setHealthCheck = React.useCallback(
(payload: boolean) => dispatch({ type: ActionType.HEALTH_CHECK, payload }),
[],
);

const context = {
obj,
cpuLimit: state.cpuLimit,
memoryLimit: state.memoryLimit,
healthCheck: state.healthCheck,
setCPULimit,
setMemoryLimit,
setHealthCheck,
};

return (
Expand All @@ -33,3 +117,16 @@ export default NodeDashboard;
type NodeDashboardProps = {
obj: NodeKind;
};

type NodeDashboardState = {
obj: NodeKind;
cpuLimit: LimitRequested;
memoryLimit: LimitRequested;
healthCheck: boolean;
};

type NodeDashboardAction =
| { type: ActionType.CPU_LIMIT; payload: LimitRequested }
| { type: ActionType.MEMORY_LIMIT; payload: LimitRequested }
| { type: ActionType.HEALTH_CHECK; payload: boolean }
| { type: ActionType.OBJ; payload: NodeKind };
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import * as React from 'react';
import { NodeKind } from '@console/internal/module/k8s';
import { LimitRequested } from '@console/shared/src/components/dashboard/utilization-card/UtilizationItem';

export const NodeDashboardContext = React.createContext<NodeDashboardContext>({});
export const NodeDashboardContext = React.createContext<NodeDashboardContext>({
setCPULimit: () => {},
setMemoryLimit: () => {},
setHealthCheck: () => {},
});

type NodeDashboardContext = {
obj?: NodeKind;
cpuLimit?: LimitRequested;
setCPULimit: (state: LimitRequested) => void;
memoryLimit?: LimitRequested;
setMemoryLimit: (state: LimitRequested) => void;
healthCheck?: boolean;
setHealthCheck: (state: boolean) => void;
};

0 comments on commit 41c042b

Please sign in to comment.