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

PostgresSQL DB v15 and health check endpoint #5312

Merged
merged 26 commits into from
Dec 10, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cvat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "7.2.2",
"version": "7.3.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "src/api.ts",
"scripts": {
Expand Down
5 changes: 5 additions & 0 deletions cvat-core/src/api-implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ export default function implementAPI(cvat) {
return result;
};

cvat.server.healthCheck.implementation = async (requestTimeout = 5000) => {
const result = await serverProxy.server.healthCheck(requestTimeout);
return result;
};

cvat.server.request.implementation = async (url, data) => {
const result = await serverProxy.server.request(url, data);
return result;
Expand Down
17 changes: 17 additions & 0 deletions cvat-core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,23 @@ function build() {
const result = await PluginRegistry.apiWrapper(cvat.server.authorized);
return result;
},
/**
* Method allows to health check the server
* @method healthCheck
* @async
* @memberof module:API.cvat.server
* @param {number} requestTimeout
* @returns {Object | undefined} response data if exist
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async healthCheck(requestTimeout = 5000) {
const result = await PluginRegistry.apiWrapper(
cvat.server.healthCheck,
requestTimeout,
);
return result;
},
/**
* Method allows to do requests via cvat-core with authorization headers
* @method request
Expand Down
15 changes: 15 additions & 0 deletions cvat-core/src/server-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,20 @@ async function authorized() {
return true;
}

async function healthCheck(requestTimeout) {
const { backendAPI } = config;
const url = `${backendAPI}/server/health-check`;

try {
return await Axios.get(url, {
proxy: config.proxy,
timeout: requestTimeout,
});
} catch (errorData) {
throw generateError(errorData);
}
}

async function serverRequest(url, data) {
try {
return (
Expand Down Expand Up @@ -2227,6 +2241,7 @@ export default Object.freeze({
requestPasswordReset,
resetPassword,
authorized,
healthCheck,
register,
request: serverRequest,
userAgreements,
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.44.4",
"version": "1.45.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
54 changes: 54 additions & 0 deletions cvat-ui/src/actions/health-check-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { getCore } from 'cvat-core-wrapper';
import { ServerError } from 'cvat-core/src/exceptions';

import consts from 'consts';

const core = getCore();

export enum HealthCheckActionTypes {
GET_HEALTH = 'GET_HEALTH',
GET_HEALTH_SUCCESS = 'GET_HEALTH_SUCCESS',
GET_HEALTH_FAILED = 'GET_HEALTH_FAILED',
GET_HEALTH_PROGRESS = 'GET_HEALTH_PROGRESS',
}

const healthCheckActions = {
getHealthCheck: () => createAction(HealthCheckActionTypes.GET_HEALTH),
getHealthCheckSuccess: () => createAction(HealthCheckActionTypes.GET_HEALTH_SUCCESS),
getHealthCheckFailed: (error: any) => createAction(HealthCheckActionTypes.GET_HEALTH_FAILED, { error }),
getHealthCheckProgress: (progress: string) => createAction(
HealthCheckActionTypes.GET_HEALTH_PROGRESS, { progress },
),
};

export type HealthActions = ActionUnion<typeof healthCheckActions>;

export const getHealthAsync = (): ThunkAction => async (dispatch): Promise<void> => {
const healthCheck = async (maxRetries: number, checkPeriod: number, requestTimeout: number, attempt = 0) => {
dispatch(healthCheckActions.getHealthCheckProgress(`${attempt}/${attempt + maxRetries}`));
return core.server.healthCheck(requestTimeout)
.catch((serverError: ServerError) => {
if (maxRetries > 0) {
return new Promise((resolve) => setTimeout(resolve, checkPeriod))
.then(() => healthCheck(maxRetries - 1, checkPeriod, requestTimeout, attempt + 1));
}
throw serverError;
});
};

const { HEALH_CHECK_RETRIES, HEALTH_CHECK_PERIOD, HEALTH_CHECK_REQUEST_TIMEOUT } = consts;

dispatch(healthCheckActions.getHealthCheck());

try {
await healthCheck(HEALH_CHECK_RETRIES, HEALTH_CHECK_PERIOD, HEALTH_CHECK_REQUEST_TIMEOUT);
dispatch(healthCheckActions.getHealthCheckSuccess());
} catch (error) {
dispatch(healthCheckActions.getHealthCheckFailed(error));
}
};
80 changes: 76 additions & 4 deletions cvat-ui/src/components/cvat-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Col, Row } from 'antd/lib/grid';
import Layout from 'antd/lib/layout';
import Modal from 'antd/lib/modal';
import notification from 'antd/lib/notification';
import Spin from 'antd/lib/spin';
import Progress, { ProgressProps } from 'antd/lib/progress';
import Text from 'antd/lib/typography/Text';
import 'antd/dist/antd.css';

Expand Down Expand Up @@ -64,6 +64,7 @@ import showPlatformNotification, {
showUnsupportedNotification,
} from 'utils/platform-checker';
import '../styles.scss';
import consts from 'consts';
import EmailConfirmationPage from './email-confirmation-pages/email-confirmed';
import EmailVerificationSentPage from './email-confirmation-pages/email-verification-sent';
import IncorrectEmailConfirmationPage from './email-confirmation-pages/incorrect-email-confirmation';
Expand All @@ -81,6 +82,7 @@ interface CVATAppProps {
switchSettingsDialog: () => void;
loadAuthActions: () => void;
loadOrganizations: () => void;
loadBackendHealth: () => void;
keyMap: KeyMap;
userInitialized: boolean;
userFetching: boolean;
Expand All @@ -101,12 +103,16 @@ interface CVATAppProps {
notifications: NotificationsState;
user: any;
isModelPluginActive: boolean;
healthFetching: boolean;
healthIinitialized: boolean;
backendIsHealthy: boolean;
backendHealthCheckProgress: string;
}

class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentProps> {
public componentDidMount(): void {
const core = getCore();
const { verifyAuthorized, history, location } = this.props;
const { loadBackendHealth, history, location } = this.props;
// configure({ ignoreRepeatedEventsWhenKeyHeldDown: false });

// Logger configuration
Expand All @@ -121,7 +127,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
customWaViewHit(_location.pathname, _location.search, _location.hash);
});

verifyAuthorized();
loadBackendHealth();
azhavoro marked this conversation as resolved.
Show resolved Hide resolved

const {
name, version, engine, os,
Expand Down Expand Up @@ -178,6 +184,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
initModels,
loadOrganizations,
loadAuthActions,
loadBackendHealth,
userInitialized,
userFetching,
organizationsFetching,
Expand All @@ -196,8 +203,49 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
authActionsFetching,
authActionsInitialized,
isModelPluginActive,
healthFetching,
healthIinitialized,
backendIsHealthy,
} = this.props;

const { UPGRADE_GUIDE_URL } = consts;

if (!healthIinitialized && !healthFetching) {
loadBackendHealth();
return;
}
azhavoro marked this conversation as resolved.
Show resolved Hide resolved

if (healthFetching) {
return;
}

if (healthIinitialized && !healthFetching && !backendIsHealthy) {
Modal.error({
title: 'Cannot connect to the server',
className: 'cvat-modal-cannot-connect-server',
closable: false,
okButtonProps: { disabled: true },
content: (
<Text>
Cannot connect to the server.
Make sure the CVAT backend and all necessary services (Database, Redis and Open Policy Agent)
are running and avaliable.
If you upgraded from version 2.2.0 or earlier, manual actions may be needed, see the

<a
target='_blank'
rel='noopener noreferrer'
href={UPGRADE_GUIDE_URL}
>
Upgrade Guide
</a>
.
</Text>
),
});
return;
}

this.showErrors();
this.showMessages();

Expand Down Expand Up @@ -330,6 +378,9 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
keyMap,
location,
isModelPluginActive,
backendHealthCheckProgress,
healthIinitialized,
backendIsHealthy,
} = this.props;

const notRegisteredUserInitialized = (userInitialized && (user == null || !user.isVerified));
Expand Down Expand Up @@ -456,7 +507,28 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
);
}

return <Spin size='large' className='cvat-spinner' />;
const progress = backendHealthCheckProgress.split('/').map(Number);
let status: ProgressProps['status'] = 'active';
if (healthIinitialized) {
if (backendIsHealthy) {
status = 'success';
} else {
status = 'exception';
}
}

return (
<>
<Progress
className='cvat-spinner'
type='circle'
percent={(progress[0] / progress[1]) * 100}
format={() => `${backendHealthCheckProgress} Connection attempts`}
status={status}
width={200}
/>
</>
);
}
}

Expand Down
9 changes: 9 additions & 0 deletions cvat-ui/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const DISCORD_URL = 'https://discord.gg/fNR3eXfk6C';
const GITHUB_URL = 'https://github.com/opencv/cvat';
const GITHUB_IMAGE_URL = 'https://github.com/opencv/cvat/raw/develop/site/content/en/images/cvat.jpg';
const GUIDE_URL = 'https://opencv.github.io/cvat/docs';
const UPGRADE_GUIDE_URL = 'https://opencv.github.io/cvat/docs/administration/advanced/upgrade_guide';
const SHARE_MOUNT_GUIDE_URL =
'https://opencv.github.io/cvat/docs/administration/basics/installation/#share-path';
const NUCLIO_GUIDE =
Expand Down Expand Up @@ -83,6 +84,10 @@ const DEFAULT_GOOGLE_CLOUD_STORAGE_LOCATIONS: string[][] = [
['NAM4', 'US-CENTRAL1 and US-EAST1'],
];

const HEALH_CHECK_RETRIES = 10;
const HEALTH_CHECK_PERIOD = 3000; // ms
const HEALTH_CHECK_REQUEST_TIMEOUT = 5000; // ms

export default {
UNDEFINED_ATTRIBUTE_VALUE,
NO_BREAK_SPACE,
Expand All @@ -93,6 +98,7 @@ export default {
GITHUB_URL,
GITHUB_IMAGE_URL,
GUIDE_URL,
UPGRADE_GUIDE_URL,
SHARE_MOUNT_GUIDE_URL,
CANVAS_BACKGROUND_COLORS,
NEW_LABEL_COLOR,
Expand All @@ -105,4 +111,7 @@ export default {
DEFAULT_GOOGLE_CLOUD_STORAGE_LOCATIONS,
OUTSIDE_PIC_URL,
DATASET_MANIFEST_GUIDE_URL,
HEALH_CHECK_RETRIES,
HEALTH_CHECK_PERIOD,
HEALTH_CHECK_REQUEST_TIMEOUT,
};
12 changes: 12 additions & 0 deletions cvat-ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { authorizedAsync, loadAuthActionsAsync } from 'actions/auth-actions';
import { getFormatsAsync } from 'actions/formats-actions';
import { getModelsAsync } from 'actions/models-actions';
import { getPluginsAsync } from 'actions/plugins-actions';
import { getHealthAsync } from 'actions/health-check-actions';
import { switchSettingsDialog } from 'actions/settings-actions';
import { shortcutsActions } from 'actions/shortcuts-actions';
import { getUserAgreementsAsync } from 'actions/useragreements-actions';
Expand Down Expand Up @@ -51,6 +52,10 @@ interface StateToProps {
user: any;
keyMap: KeyMap;
isModelPluginActive: boolean;
healthFetching: boolean;
healthIinitialized: boolean;
backendIsHealthy: boolean;
backendHealthCheckProgress: string;
}

interface DispatchToProps {
Expand All @@ -66,6 +71,7 @@ interface DispatchToProps {
switchSettingsDialog: () => void;
loadAuthActions: () => void;
loadOrganizations: () => void;
loadBackendHealth: () => void;
}

function mapStateToProps(state: CombinedState): StateToProps {
Expand All @@ -77,6 +83,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { userAgreements } = state;
const { models } = state;
const { organizations } = state;
const { health } = state;

return {
userInitialized: auth.initialized,
Expand All @@ -101,6 +108,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
user: auth.user,
keyMap: shortcuts.keyMap,
isModelPluginActive: plugins.list.MODELS,
healthFetching: health.fetching,
healthIinitialized: health.initialized,
backendIsHealthy: health.isHealthy,
backendHealthCheckProgress: health.progress,
};
}

Expand All @@ -118,6 +129,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
switchSettingsDialog: (): void => dispatch(switchSettingsDialog()),
loadAuthActions: (): void => dispatch(loadAuthActionsAsync()),
loadOrganizations: (): void => dispatch(getOrganizationsAsync()),
loadBackendHealth: (): void => dispatch(getHealthAsync()),
};
}

Expand Down
Loading