Skip to content

Commit

Permalink
Block Save on operator's reconciliation
Browse files Browse the repository at this point in the history
  • Loading branch information
mareklibra committed Oct 24, 2022
1 parent f2a9713 commit b010d5e
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 25 deletions.
7 changes: 6 additions & 1 deletion ui/frontend/src/components/BasicLayout/BasicLayout.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
margin-bottom: var(--pf-global--spacer--2xl);
}

.basic-layout__alert {
/* like .content-section */
width: 50vw;
}

.basic-layout-left-center {
height: 70vh;
}
Expand All @@ -30,7 +35,7 @@
background-color: var(--pf-global--Color--300) !important;
}

.basic-layout-content {
.basic-layout__navigation {
min-height: 80vh;
}

Expand Down
76 changes: 64 additions & 12 deletions ui/frontend/src/components/BasicLayout/BasicLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import {
Divider,
Flex,
FlexItem,
List,
ListItem,
Panel,
PanelMain,
Spinner,
Stack,
StackItem,
Text,
Expand All @@ -28,6 +31,7 @@ import RedHatLogo from './RedHatLogo.svg';
import cloudyCircles from './cloudyCircles.svg';

import './BasicLayout.css';
import { useOperatorsReconciling } from '../operators';

export const BasicLayout: React.FC<{
error?: UIError;
Expand All @@ -37,7 +41,10 @@ export const BasicLayout: React.FC<{
isSaving?: boolean;
actions?: React.ReactNode[];
}> = ({ error, warning, isValueChanged, isSaving, onSave, actions = [], children }) => {
const operatorsReconciling = useOperatorsReconciling();

const isSaveButton = onSave !== undefined;
const isOperatorReconciling = operatorsReconciling && operatorsReconciling.length > 0;

return (
<Sidebar tabIndex={0}>
Expand Down Expand Up @@ -77,7 +84,7 @@ export const BasicLayout: React.FC<{

<SidebarContent className="basic-layout-right">
<Flex>
<FlexItem>
<FlexItem className="basic-layout__navigation">
<TextContent className="basic-layout__settings">
<Text component={TextVariants.h1}>Settings</Text>
</TextContent>
Expand Down Expand Up @@ -107,16 +114,50 @@ export const BasicLayout: React.FC<{
{!isSaving && (
<Flex justifyContent={{ default: 'justifyContentCenter' }} flex={{ default: 'flex_1' }}>
<Stack hasGutter>
{error?.title && (
<Alert variant={AlertVariant.danger} isInline title={error.title}>
{error.message}
</Alert>
)}
{warning?.title && (
<Alert variant={AlertVariant.warning} isInline title={warning.title}>
{warning.message}
</Alert>
)}
<StackItem>
{error?.title && (
<Alert
variant={AlertVariant.danger}
isInline
title={error.title}
className="basic-layout__alert"
>
{error.message}
</Alert>
)}
{isOperatorReconciling && isSaveButton && (
<Alert
variant={AlertVariant.warning}
isInline
title="Operator reconciliation is in progress"
className="basic-layout__alert"
>
Saving changes is not possible until operators become ready. They are probably
reconciling after previous changes.
{operatorsReconciling !== undefined && (
<>
<br />
<List isPlain>
{operatorsReconciling.map((op) => (
<ListItem key="op.metadata.name">{op.metadata.name}</ListItem>
))}
</List>
</>
)}
</Alert>
)}
{warning?.title && (
<Alert
variant={AlertVariant.warning}
isInline
title={warning.title}
className="basic-layout__alert"
>
{warning.message}
</Alert>
)}
</StackItem>

<StackItem isFilled className="basic-layout-content">
{children}
</StackItem>
Expand All @@ -125,6 +166,12 @@ export const BasicLayout: React.FC<{
<PanelMain>
{isSaveButton && (
<Button onClick={onSave} isDisabled={!isValueChanged || isSaving}>
{(operatorsReconciling === undefined || isOperatorReconciling) && (
<>
<Spinner size="sm" />
&nbsp;
</>
)}
Save
</Button>
)}
Expand All @@ -133,7 +180,12 @@ export const BasicLayout: React.FC<{
<Button
variant={ButtonVariant.link}
onClick={reloadPage}
isDisabled={!isValueChanged || isSaving}
isDisabled={
!isValueChanged ||
isSaving ||
operatorsReconciling === undefined ||
isOperatorReconciling
}
>
Cancel
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.content-section {
/* width: 768px; */
width: 50vw;
background-color: var(--pf-global--BackgroundColor--200);
border-radius: 16px;
Expand Down
3 changes: 0 additions & 3 deletions ui/frontend/src/components/DomainPage/DomainCertificates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
PanelMainBody,
FlexItem,
Flex,
FlexProps,
DescriptionList,
DescriptionListGroup,
DescriptionListTerm,
Expand Down Expand Up @@ -48,8 +47,6 @@ const getTitle = (
keyValidated: FormGroupProps['validated'],
): React.ReactElement | string => {
let title: React.ReactElement | string;
const forDomain = isExpanded ? undefined : <> certificate for {domain}</>;

if (!domainCert?.['tls.crt'] && !domainCert?.['tls.key']) {
title = (
<>
Expand Down
10 changes: 3 additions & 7 deletions ui/frontend/src/components/DomainPage/dataLoad.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getClusterDomainFromComponentRoutes, Ingress, Service } from '../../copy-backend-common';
import { getClusterDomainFromComponentRoutes, Ingress } from '../../copy-backend-common';
import { getIngressConfig } from '../../resources/ingress';
import { workaroundUnmarshallObject } from '../../test-utils';
import { setUIErrorType } from '../types';
Expand All @@ -21,17 +21,13 @@ export const loadDomainData = async ({
message: 'The cluster is not properly deployed.',
});
return;
}

if (e.code === 401) {
} else if (e.code === 401) {
setError({
title: 'Unauthorized',
message: 'Redirecting to login page.',
});
return;
}

if (e.code !== 404) {
} else {
console.error(e, e.code);
setError({ title: 'Failed to contact OpenShift Platform API.', message: e.message });
return;
Expand Down
2 changes: 1 addition & 1 deletion ui/frontend/src/components/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
URI_CREDENTIALS,
URI_DOMAIN,
URI_INGRESS,
URI_LAYER3,
// URI_LAYER3,
URI_SSHKEY,
} from './routes';

Expand Down
7 changes: 7 additions & 0 deletions ui/frontend/src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ export const ADDRESS_POOL_NAMESPACE = 'metallb';

export const DELAY_BEFORE_RECONCILIATION = 10 * 1000;
export const DELAY_BEFORE_QUERY_RETRY = 5 * 1000; /* ms */
export const CLUSTER_OPERATOR_POLLING_INTERVAL = DELAY_BEFORE_QUERY_RETRY;
export const MAX_LIVENESS_CHECK_COUNT = 20 * ((60 * 1000) / DELAY_BEFORE_QUERY_RETRY); // max 20 minutes

export const KubeadminSecret = { name: 'kubeadmin', namespace: 'kube-system' };
export const SSH_PRIVATE_KEY_SECRET = {
name: 'cluster-ssh-keypair',
namespace: 'default' /* !?! */,
};

export const MONITORED_CLUSTER_OPERATORS = [
'kube-apiserver',
'openshift-apiserver',
'authentication',
];
50 changes: 50 additions & 0 deletions ui/frontend/src/components/operators.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';

import { ClusterOperator, getCondition } from '../copy-backend-common';
import { getClusterOperators } from '../resources/clusteroperator';

import { CLUSTER_OPERATOR_POLLING_INTERVAL, MONITORED_CLUSTER_OPERATORS } from './constants';
import { delay } from './utils';

export const useOperatorsReconciling = (): ClusterOperator[] | undefined => {
const [operatorsReconciling, setOperatorsReconciling] = React.useState<ClusterOperator[]>();
const [pollingTimmer, setPollingTimmer] = React.useState<number>(0);

React.useEffect(() => {
let stopIt = false;

const doItAsync = async () => {
const operators = await getClusterOperators().promise;

const filteredOperators = operators.filter(
(op) =>
MONITORED_CLUSTER_OPERATORS.includes(op.metadata.name as string) &&
(getCondition(op, 'Progressing')?.status === 'True' ||
getCondition(op, 'Degraded')?.status === 'True' ||
getCondition(op, 'Available')?.status === 'False'),
);

if (!stopIt) {
setOperatorsReconciling(
filteredOperators.sort(
(op1, op2) => op1.metadata.name?.localeCompare(op2.metadata.name as string) || -1,
),
);

await delay(CLUSTER_OPERATOR_POLLING_INTERVAL);

if (!stopIt) {
setPollingTimmer(pollingTimmer + 1);
}
}
};

doItAsync();

return () => {
stopIt = true;
};
}, [pollingTimmer]);

return operatorsReconciling;
};

0 comments on commit b010d5e

Please sign in to comment.