Skip to content

Commit

Permalink
feat(System/Masters): allow to 'Switch leader' for 'Secondary masters…
Browse files Browse the repository at this point in the history
…' and 'Timestamp provider' [YTFRONT-4214]
  • Loading branch information
ma-efremoff committed Dec 18, 2024
1 parent e696da8 commit 7911ce2
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 84 deletions.
15 changes: 13 additions & 2 deletions packages/ui/src/ui/hooks/use-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,27 @@ export type UseUpdaterOptions = {
* if `true` then `fn()` will be called only once
*/
onlyOnce?: boolean;
/**
* Enforces to ignore 'Use auto refresh' user option
*/
forceAutoRefresh?: boolean;
};

export function useUpdater(
fn?: () => unknown,
{timeout = DEFAULT_UPDATER_TIMEOUT, destructFn, onlyOnce}: UseUpdaterOptions = {},
{
timeout = DEFAULT_UPDATER_TIMEOUT,
destructFn,
onlyOnce,
forceAutoRefresh,
}: UseUpdaterOptions = {},
) {
const useAutoRefresh = useSelector(getUseAutoRefresh) as boolean;
const optionsRef = React.useRef({skipNextCall: !useAutoRefresh});

optionsRef.current.skipNextCall = !useAutoRefresh;
const allowAutoRefresh = forceAutoRefresh ?? useAutoRefresh;

optionsRef.current.skipNextCall = !allowAutoRefresh;

React.useEffect(() => {
let updater: Updater | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useEffect, useRef, useState} from 'react';
import React, {useRef, useState} from 'react';
import cn from 'bem-cn-lite';

import MetaTable from '../../../components/MetaTable/MetaTable';
Expand All @@ -8,11 +8,12 @@ import {getStateForHost, loadMasters} from '../../../store/actions/system/master
import {useDispatch} from 'react-redux';
import moment from 'moment';
import './SwitchLeaderShortInfo.scss';
import {useUpdater} from '../../../hooks/use-updater';

const block = cn('switch-leader-short-info');

interface Props {
newLeaderAddress: string;
newLeaderPath: string;
}

export function SwitchLeaderShortInfo(props: Props) {
Expand All @@ -21,44 +22,26 @@ export function SwitchLeaderShortInfo(props: Props) {
const [finishTime, setFinishTime] = useState<any>();
const dispatch = useDispatch();

useEffect(() => {
const intervalId = setInterval(() => {
const updateCurrentTime = React.useCallback(() => {
if (!finishTime) {
setCurrentTime(moment());
}
}, [finishTime]);
useUpdater(updateCurrentTime, {timeout: 1000, forceAutoRefresh: true});

if (finishTime) {
clearInterval(intervalId);
}
}, 1 * 1000);
const updateFn = React.useCallback(async () => {
if (finishTime) {
return;
}

return () => {
clearInterval(intervalId);
};
}, []);
const hostState = await getStateForHost(props.newLeaderPath);

useEffect(() => {
let stillMounted = true;

const waitForState = async () => {
try {
const hostState = await getStateForHost(props.newLeaderAddress);

if (hostState === 'leading') {
setFinishTime(moment());
dispatch(loadMasters());
}
} catch {
if (stillMounted) {
waitForState();
}
}
};

waitForState();

return () => {
stillMounted = false;
};
}, [props.newLeaderAddress]);
if (hostState === 'leading') {
setFinishTime(moment());
dispatch(loadMasters());
}
}, [props.newLeaderPath, finishTime, dispatch]);
useUpdater(updateFn, {timeout: 3000, forceAutoRefresh: true});

return (
<div className={block()}>
Expand Down
16 changes: 1 addition & 15 deletions packages/ui/src/ui/pages/system/Masters/MasterGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ class MasterGroup extends Component {
'no-quorum': 'missing',
unknown: 'unknown',
};
let leadingHost = '';
const hosts = instances.map(({$address, state}) => {
if (state === 'leading') {
leadingHost = $address;
}

return $address;
});

return (
<Fragment>
Expand Down Expand Up @@ -96,13 +88,7 @@ class MasterGroup extends Component {
<div className={b('quorum-cell')} title={cellTitle}>
{cellTag && <Icon className={b('icon-glyph')} face="solid" awesome="tag" />}
{hammer.format['Hex'](cellTag)}
{cellId && (
<SwitchLeaderButton
cellId={cellId}
hosts={hosts}
leadingHost={leadingHost}
/>
)}
{cellId && <SwitchLeaderButton cellId={cellId} hosts={instances} />}
</div>
</div>
</Fragment>
Expand Down
47 changes: 23 additions & 24 deletions packages/ui/src/ui/pages/system/Masters/SwitchLeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ type SwitchLeaderDialogProps = {
confirm: (newLeader: string) => Promise<void>;
visible: boolean;
cellId: string;
hosts: string[];
leadingHost: string;
hosts: Array<{getPath: () => string; state: 'leading'}>;
};

type FormValues = {
Expand All @@ -24,20 +23,24 @@ const SwitchLeaderDialog = (props: SwitchLeaderDialogProps) => {
const [error, setError] = useState(undefined);

const selectLeadingHostOptions = props.hosts.map((host) => {
const path = host.getPath();
return {
value: host,
content: host,
value: path,
content: path.split('/').pop(),
};
});

const leader = props.hosts.find(({state}) => state === 'leading');
const leaderPath = leader?.getPath();

return (
<YTDFDialog<FormValues>
visible={props.visible}
headerProps={{
title: `Switch leader for ${props.cellId}`,
}}
initialValues={{
leading_primary_master: [props.leadingHost],
leading_primary_master: leaderPath ? [leaderPath] : [],
}}
fields={[
{
Expand Down Expand Up @@ -79,27 +82,22 @@ const SwitchLeaderDialog = (props: SwitchLeaderDialogProps) => {
type SwitchLeaderButtonProps = {
className: string;
cellId: string;
hosts: string[];
leadingHost: string;
hosts: Array<{getPath: () => string; state: 'leading'}>;
};

export const SwitchLeaderButton = ({
cellId,
hosts,
leadingHost,
className,
}: SwitchLeaderButtonProps) => {
export const SwitchLeaderButton = ({cellId, hosts, className}: SwitchLeaderButtonProps) => {
const [visible, setVisible] = useState(false);

const handleClick = () => {
setVisible(true);
};

const handleConfirm = async (newLeader: string) => {
const handleConfirm = async (newLeaderPath: string) => {
const leaderAddress = newLeaderPath.split('/').pop();
const switchLeader = () => {
return ytApiV4Id.switchLeader(YTApiId.switchLeader, {
cell_id: cellId,
new_leader_address: newLeader,
new_leader_address: leaderAddress,
});
};

Expand All @@ -108,7 +106,7 @@ export const SwitchLeaderButton = ({
successContent() {
return (
<AppStoreProvider>
<SwitchLeaderShortInfo newLeaderAddress={newLeader} />
<SwitchLeaderShortInfo newLeaderPath={newLeaderPath} />
</AppStoreProvider>
);
},
Expand All @@ -134,14 +132,15 @@ export const SwitchLeaderButton = ({
>
<Icon awesome="crowndiamond" />
</Button>
<SwitchLeaderDialog
cellId={cellId}
hosts={hosts}
leadingHost={leadingHost}
confirm={handleConfirm}
cancel={handleCancel}
visible={visible}
/>
{visible && (
<SwitchLeaderDialog
visible
cellId={cellId}
hosts={hosts}
confirm={handleConfirm}
cancel={handleCancel}
/>
)}
</React.Fragment>
);
};
30 changes: 23 additions & 7 deletions packages/ui/src/ui/store/actions/system/masters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ async function loadMastersConfig(): Promise<[MastersConfigResponse, MasterAlert[
const primaryMaster = primaryMasterResult.output;
const secondaryMasters = secondaryMastersResult.output;

const timestampProvierCellId = ypath.getValue(timestampProviderCellTag.output)?.cell_id;
const timestampProviders = !timestampProvidersResult.output
? {}
: {
Expand All @@ -190,8 +191,10 @@ async function loadMastersConfig(): Promise<[MastersConfigResponse, MasterAlert[
attributes: ypath.getValue(value, '/@'),
};
}),
cellId: ypath.getValue(timestampProviderCellTag.output)?.cell_id,
cellTag: getCellIdTag(ypath.getValue(timestampProviderCellTag.output)?.cell_id),
cellId: isSameClusterByCellId(masterCellId, timestampProvierCellId)
? timestampProvierCellId
: undefined,
cellTag: getCellIdTag(timestampProvierCellId),
};

const mainResult: MastersConfigResponse = {
Expand All @@ -218,6 +221,7 @@ async function loadMastersConfig(): Promise<[MastersConfigResponse, MasterAlert[
attributes: ypath.getValue(value, '/@'),
};
}),
cellId: replaceCellIdTag(masterCellId, Number(cellTag).toString(16)),
cellTag: Number(cellTag),
};
}),
Expand Down Expand Up @@ -352,16 +356,13 @@ function loadHydra(
}

export const getStateForHost = async (
host: string,
path: string,
): Promise<'leading' | 'following' | undefined> => {
const cypressPath = '//sys/primary_masters';
const hydraPath = '/orchid/monitoring/hydra';

const masterDataRequests: BatchSubRequest[] = [
{
command: 'get' as const,
parameters: {
path: cypressPath + '/' + host + hydraPath,
path: `${path}/orchid/monitoring/hydra`,
...USE_SUPRESS_SYNC,
},
},
Expand Down Expand Up @@ -512,3 +513,18 @@ function getCellIdTag(uuid?: string): number | undefined {
const [, , third = ''] = uuid.split('-');
return Number(`0x${third.substring(0, third.length - 4)}`);
}

function replaceCellIdTag(uuid: string, newCellIdTag: string) {
const [first, second, third, ...rest] = uuid.split('-');
const newThird = newCellIdTag + third.substring(third.length - 4);
return [first, second, newThird, ...rest].join('-');
}

function isSameClusterByCellId(lCell_id: string, rClell_id: string) {
return removeCellTag(lCell_id) === removeCellTag(rClell_id);
}

function removeCellTag(cellId: string) {
const [first, second, third, ...rest] = cellId.split('-');
return [first, second, third.substring(third.length - 4), ...rest].join('-');
}
3 changes: 3 additions & 0 deletions packages/ui/src/ui/store/reducers/system/masters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,15 @@ function processMastersConfig(
instances: map_(sortBy_(master.addresses, 'host'), (address) => {
return new MasterInstance(address, 'secondary', master.cellTag);
}),
cellId: master.cellId,
cellTag: master.cellTag,
};
}),
providers: {
instances: map_(sortBy_(timestampProviders.addresses, 'host'), (address) => {
return new MasterInstance(address, 'providers', timestampProviders.cellTag);
}),
cellId: timestampProviders.cellId,
cellTag: timestampProviders.cellTag,
},
discovery: {
Expand Down Expand Up @@ -352,6 +354,7 @@ function processMastersData(
const providers = {
instances: orderBy_(providersInstances, (instance) => instance.$address),
cellTag: state.providers.cellTag,
cellId: state.providers.cellId,
quorum: getQuorum(providersInstances),
leader: getLeader(providersInstances),
};
Expand Down

0 comments on commit 7911ce2

Please sign in to comment.