Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RGW Support in Object Service Dashboard #6070

Merged
merged 1 commit into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Grid, GridItem } from '@patternfly/react-core';
import { Humanize } from '@console/internal/components/utils';
import { K8sKind } from '@console/internal/module/k8s';
import { addAvailable, StackDataPoint, getLegends } from './utils';
import { BreakdownChart } from './breakdown-chart';
import { BreakdownChart, LabelPadding } from './breakdown-chart';
import { BreakdownChartLoading } from './breakdown-loading';
import { TotalCapacityBody } from './breakdown-capacity';

Expand All @@ -17,6 +17,7 @@ export const BreakdownCardBody: React.FC<BreakdownBodyProps> = ({
isLoading,
hasLoadError,
ocsVersion = '',
labelPadding,
}) => {
if (isLoading && !hasLoadError) {
return <BreakdownChartLoading />;
Expand Down Expand Up @@ -60,6 +61,7 @@ export const BreakdownCardBody: React.FC<BreakdownBodyProps> = ({
legends={legends}
metricModel={metricModel}
ocsVersion={ocsVersion}
labelPadding={labelPadding}
/>
</GridItem>
</Grid>
Expand All @@ -76,4 +78,5 @@ export type BreakdownBodyProps = {
metricModel: K8sKind;
humanize: Humanize;
ocsVersion?: string;
labelPadding?: LabelPadding;
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import './breakdown-card.scss';
export const LinkableLegend: React.FC<LinkableLegendProps> = React.memo(
(props: LinkableLegendProps) => {
const { metricModel, datum, ocsVersion } = props;
let href: string = resourcePathFromModel(metricModel, datum.link, datum.ns);
let href: string = metricModel ? resourcePathFromModel(metricModel, datum.link, datum.ns) : '';
const customLegend = (
<Tooltip content={datum.link} enableFlip>
<ChartLabel
Expand All @@ -34,7 +34,7 @@ export const LinkableLegend: React.FC<LinkableLegendProps> = React.memo(
/>
</Tooltip>
);
if (datum.labelId === OTHER || datum.labelId === CLUSTERWIDE) {
if (datum.labelId === OTHER || datum.labelId === CLUSTERWIDE || !metricModel) {
return customLegend;
}
if (metricModel.kind === BUCKETCLASSKIND) {
Expand All @@ -59,6 +59,7 @@ export const BreakdownChart: React.FC<BreakdownChartProps> = ({
legends,
metricModel,
ocsVersion,
labelPadding,
}) => (
<>
<Chart
Expand All @@ -73,7 +74,19 @@ export const BreakdownChart: React.FC<BreakdownChartProps> = ({
symbolSpacer={7}
gutter={10}
height={50}
style={{ labels: { fontSize: 8 } }}
style={{
labels: Object.assign(
{ fontSize: 10 },
labelPadding
? {
paddingRight: labelPadding.right,
paddingTop: labelPadding.top,
paddingBottom: labelPadding.bottom,
paddingLeft: labelPadding.left,
}
: {},
),
}}
/>
}
height={60}
Expand Down Expand Up @@ -110,6 +123,14 @@ export type BreakdownChartProps = {
legends: any[];
metricModel: K8sKind;
ocsVersion?: string;
labelPadding?: LabelPadding;
};

export type LabelPadding = {
left: number;
right: number;
bottom: number;
top: number;
};

export type LinkableLegendProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,28 @@ export const sortInstantVectorStats = (stats: DataPoint[]): DataPoint[] => {
return stats.length === 6 ? stats.splice(0, 5) : stats;
};

export const getStackChartStats: GetStackStats = (response, humanize) =>
export const getStackChartStats: GetStackStats = (response, humanize, labelNames) =>
response.map((r, i) => {
const capacity = humanize(r.y).string;
return {
// x value needs to be same for single bar stack chart
x: '0',
y: r.y,
name: _.truncate(`${r.x}`, { length: 12 }),
link: `${r.x}`,
color: Colors.LINK,
name: labelNames ? labelNames[i] : _.truncate(`${r.x}`, { length: 12 }),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A question here - Shouldn't you be checking labelNames[i] too here, before applying it in case labelNames[i] is undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need as there a few cases where we pass label names and for those cases, we can put the correct number of items, in the array. I have created a map for those special cases.

link: labelNames ? labelNames[i] : `${r.x}`,
color: labelNames ? Colors.OTHER : Colors.LINK,
fill: COLORMAP[i],
label: capacity,
id: i,
ns: r.metric.namespace,
};
});

type GetStackStats = (response: DataPoint[], humanize: Humanize) => StackDataPoint[];
type GetStackStats = (
response: DataPoint[],
humanize: Humanize,
labelNames?: string[],
) => StackDataPoint[];

export type StackDataPoint = DataPoint<string> & {
name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,10 @@ import { getRequiredKeys, createDownloadFile } from '../independent-mode/utils';
import { OCSServiceModel } from '../../models';
import CreateExternalCluster from '../independent-mode/install';
import { CreateInternalCluster } from './create-form';
import { OCS_SUPPORT_ANNOTATION } from '../../constants';
import { OCS_SUPPORT_ANNOTATION, MODES } from '../../constants';
import { CreateAttachedDevicesCluster } from './attached-devices/install';
import './install-page.scss';

enum MODES {
INTERNAL = 'Internal',
EXTERNAL = 'External',
ATTACHED_DEVICES = 'Internal - Attached Devices',
}

// eslint-disable-next-line no-shadow
const InstallCluster: React.FC<InstallClusterProps> = ({ match }) => {
const {
Expand Down
8 changes: 8 additions & 0 deletions frontend/packages/ceph-storage-plugin/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ export const OCS_DEVICE_REPLICA = Object.freeze({
'2': '2-way',
'3': '3-way',
});
export const RGW_PROVISIONER = 'openshift-storage.ceph.rook.io/bucket';
export const SECOND = 1000;

export enum MODES {
INTERNAL = 'Internal',
EXTERNAL = 'External',
ATTACHED_DEVICES = 'Internal - Attached Devices',
}
22 changes: 21 additions & 1 deletion frontend/packages/ceph-storage-plugin/src/features.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import * as _ from 'lodash';
import { Dispatch } from 'react-redux';
import { K8sResourceKind } from '@console/internal/module/k8s';
import { K8sResourceKind, k8sList, StorageClassResourceKind } from '@console/internal/module/k8s';
import { ClusterServiceVersionModel, SubscriptionModel } from '@console/operator-lifecycle-manager';
import { setFlag } from '@console/internal/actions/features';
import { FeatureDetector } from '@console/plugin-sdk';
import { getAnnotations } from '@console/shared/src/selectors/common';
import { fetchK8s } from '@console/internal/graphql/client';
import { StorageClassModel } from '@console/internal/models';
import { OCSServiceModel } from './models';
import {
OCS_EXTERNAL_CR_NAME,
CEPH_STORAGE_NAMESPACE,
OCS_SUPPORT_ANNOTATION,
ATTACHED_DEVICES_ANNOTATION,
OCS_INTERNAL_CR_NAME,
RGW_PROVISIONER,
SECOND,
} from './constants';

export const OCS_INDEPENDENT_FLAG = 'OCS_INDEPENDENT';
Expand All @@ -25,6 +28,8 @@ export const CEPH_FLAG = 'CEPH';

export const LSO_FLAG = 'LSO';

export const RGW_FLAG = 'RGW';

/* Key and Value should be same value received in CSV */
export const OCS_SUPPORT_FLAGS = {
SNAPSHOT: 'SNAPSHOT',
Expand All @@ -43,6 +48,21 @@ const handleError = (res: any, flags: string[], dispatch: Dispatch, cb: FeatureD
}
};

export const detectRGW: FeatureDetector = async (dispatch) => {
let id = null;
const logicHandler = () =>
k8sList(StorageClassModel).then((data: StorageClassResourceKind[]) => {
const isRGWPresent = data.some((sc) => sc.provisioner === RGW_PROVISIONER);
if (isRGWPresent) {
dispatch(setFlag(RGW_FLAG, true));
clearInterval(id);
} else {
dispatch(setFlag(RGW_FLAG, false));
}
});
id = setInterval(logicHandler, 10 * SECOND);
};

export const detectOCS: FeatureDetector = async (dispatch) => {
try {
const storageCluster = await fetchK8s(
Expand Down
13 changes: 13 additions & 0 deletions frontend/packages/ceph-storage-plugin/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,16 @@ export const CephBlockPoolModel: K8sKind = {
id: 'cephblockpools',
crd: true,
};

export const CephObjectStoreModel: K8sKind = {
label: 'Ceph Object Store',
labelPlural: 'Ceph Object Stores',
apiVersion: 'v1',
apiGroup: 'ceph.rook.io',
plural: 'cephobjectstores',
abbr: 'COS',
namespaced: true,
kind: 'CephObjectStore',
id: 'cephobjectstores',
crd: true,
};
7 changes: 7 additions & 0 deletions frontend/packages/ceph-storage-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import {
detectOCS,
detectOCSSupportedFeatures,
detectRGW,
CEPH_FLAG,
OCS_INDEPENDENT_FLAG,
OCS_SUPPORT_FLAGS,
Expand Down Expand Up @@ -80,6 +81,12 @@ const plugin: Plugin<ConsumedExtensions> = [
detect: detectOCS,
},
},
{
type: 'FeatureFlag/Custom',
properties: {
detect: detectRGW,
},
},
{
type: 'StorageClass/Provisioner',
properties: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@console/plugin-sdk';
import {
HealthState,
healthPriority,
healthStateMapping,
} from '@console/shared/src/components/dashboard/status-card/states';
import { coFetch } from '@console/internal/co-fetch';
import {
Expand Down Expand Up @@ -52,15 +52,15 @@ export const getControlPlaneComponentHealth = (
if (error) {
return {
state: HealthState.NOT_AVAILABLE,
message: healthPriority[HealthState.NOT_AVAILABLE].message,
message: healthStateMapping[HealthState.NOT_AVAILABLE].message,
};
}
if (!response) {
return { state: HealthState.LOADING };
}
const value = response.data?.result?.[0]?.value?.[1];
if (_.isNil(value)) {
return { state: HealthState.UNKNOWN, message: healthPriority[HealthState.UNKNOWN].message };
return { state: HealthState.UNKNOWN, message: healthStateMapping[HealthState.UNKNOWN].message };
}
const perc = humanizePercentage(value);
if (perc.value > 90) {
Expand All @@ -72,10 +72,10 @@ export const getControlPlaneComponentHealth = (
return { state: HealthState.ERROR, message: perc.string };
};

const getWorstStatus = (
export const getWorstStatus = (
componentsHealth: SubsystemHealth[],
): { state: HealthState; message: string; count: number } => {
const withPriority = componentsHealth.map((h) => healthPriority[h.state]);
const withPriority = componentsHealth.map((h) => healthStateMapping[h.state]);
const mostImportantState = Math.max(...withPriority.map(({ priority }) => priority));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe even better and less error prone would be to make sure that healthPriority contains all Health States. Currently its lacking Loading state. Once we add it I think there's no reason to have separate healthStateMapping and healthPriority. We just include the priority into healthStateMapping directly.

export const healthStateMapping: { [key in HealthState]: PriorityHealthState } = {
  [HealthState.OK]: {
    priority: 0,
    health: HealthState.OK,
    icon: <GreenCheckCircleIcon />,
  },
  [HealthState.UNKNOWN]: {
    priority: 1,
    health: HealthState.UNKNOWN,
    icon: <GrayUnknownIcon />,
    message: 'Unknown',
  },
  [HealthState.PROGRESS]: {
    priority: 2,
    health: HealthState.PROGRESS,
    icon: <InProgressIcon />,
    message: 'Pending',
  },
  [HealthState.UPDATING]: {
    priority: 3,
    health: HealthState.UPDATING,
    icon: <BlueSyncIcon />,
    message: 'Updating',
  },
  [HealthState.WARNING]: {
    priority: 4,
    health: HealthState.WARNING,
    icon: <YellowExclamationTriangleIcon />,
    message: 'Degraded',
  },
  [HealthState.ERROR]: {
    priority: 5,
    health: HealthState.ERROR,
    icon: <RedExclamationCircleIcon />,
    message: 'Degraded',
  },
  [HealthState.LOADING]: {
    priority: 6,
    health: HealthState.LOADING,
    icon: <div className="skeleton-health" />,
  },
  [HealthState.NOT_AVAILABLE]: {
    priority: 7,
    health: HealthState.NOT_AVAILABLE,
    icon: <GrayUnknownIcon />,
    message: 'Not available',
  },
};

Can you try that ?

const worstStatuses = withPriority.filter(({ priority }) => priority === mostImportantState);
return {
Expand Down Expand Up @@ -109,15 +109,15 @@ export const getClusterOperatorStatusPriority: GetOperatorStatusPriority<Cluster
) => {
const status = getClusterOperatorStatus(co);
if (status === OperatorStatus.Degraded) {
return { ...healthPriority[HealthState.WARNING], title: status };
return { ...healthStateMapping[HealthState.WARNING], title: status };
}
if (status === OperatorStatus.Unknown) {
return { ...healthPriority[HealthState.UNKNOWN], title: status };
return { ...healthStateMapping[HealthState.UNKNOWN], title: status };
}
if (status === OperatorStatus.Updating) {
return { ...healthPriority[HealthState.UPDATING], title: status };
return { ...healthStateMapping[HealthState.UPDATING], title: status };
}
return { ...healthPriority[HealthState.OK], title: status };
return { ...healthStateMapping[HealthState.OK], title: status };
};

export const getClusterOperatorHealthStatus: GetOperatorsWithStatuses<ClusterOperator> = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ export const StatusPopupSection: React.FC<StatusPopupSectionProps> = ({
const Status: React.FC<StatusProps> = ({ value, icon, children }) => (
<div className="co-status-popup__row">
{children}
{!!value && (
{value ? (
<div className="co-status-popup__status">
<div className="text-secondary">{value}</div>
<div className="co-status-popup__icon">{icon}</div>
</div>
) : (
icon && (
<div className="co-status-popup__status">
<div className="co-status-popup__icon">{icon}</div>
</div>
)
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
OperatorHealth,
GetOperatorStatusPriority,
} from '@console/plugin-sdk';
import { healthPriority, HealthState } from './states';
import { HealthState, healthStateMapping } from './states';

export const getMostImportantStatuses = (
operatorStatuses: OperatorStatusWithResources[],
Expand All @@ -21,7 +21,7 @@ export const getOperatorsStatus = <R extends K8sResourceCommon>(
if (!operators.length) {
return {
status: {
...healthPriority[HealthState.OK],
...healthStateMapping[HealthState.OK],
title: 'Available',
},
operators: [],
Expand Down Expand Up @@ -62,7 +62,7 @@ export const getOperatorsHealthState = (
return { health: HealthState.LOADING, detailMessage: undefined };
}
const sortedStatuses = healthStatuses.sort(
(a, b) => healthPriority[b.health].priority - healthPriority[a.health].priority,
(a, b) => healthStateMapping[b.health].priority - healthStateMapping[a.health].priority,
);
const groupedStatuses = _.groupBy(sortedStatuses, (s) => s.health);
const statusKeys = Object.keys(groupedStatuses);
Expand All @@ -87,8 +87,8 @@ export const getOperatorsHealthState = (

return {
health: HealthState[statusKeys[0]],
detailMessage: healthPriority[statusKeys[0]].message
? `${finalCount} ${healthPriority[statusKeys[0]].message.toLowerCase()}`
detailMessage: healthStateMapping[statusKeys[0]].message
? `${finalCount} ${healthStateMapping[statusKeys[0]].message.toLowerCase()}`
: undefined,
};
};