Handle connectivity diagnostics failures
This commit is contained in:
@@ -111,7 +111,7 @@ jobs:
|
|||||||
DATAPLANE_IMAGE="${IMAGE}-dataplane:${{ gitea.sha }}"
|
DATAPLANE_IMAGE="${IMAGE}-dataplane:${{ gitea.sha }}"
|
||||||
UPDATE_DATAPLANE=false
|
UPDATE_DATAPLANE=false
|
||||||
if git diff-tree --no-commit-id --name-only -r -m HEAD | grep -Eq \
|
if git diff-tree --no-commit-id --name-only -r -m HEAD | grep -Eq \
|
||||||
'^(Dockerfile|entrypoint\.sh|package(-lock)?\.json|scripts/build-runtime-base\.sh|\.gitea/workflows/gateway-build\.yml|src/server/(config|dataplane|gatewayRouting|singboxRuntime|version)\.js|src/server/(adapters/neighbors|services/(deviceTrafficService|devicePolicyService))\.js|src/shared/errors\.js)$'; then
|
'^(Dockerfile|entrypoint\.sh|package(-lock)?\.json|scripts/build-runtime-base\.sh|\.gitea/workflows/gateway-build\.yml|src/server/(config|dataplane|gatewayRouting|singbox|singboxRuntime|version)\.js|src/server/(adapters/neighbors|services/(connectivityDiagnosticsService|deviceTrafficService|devicePolicyService))\.js|src/shared/errors\.js)$'; then
|
||||||
UPDATE_DATAPLANE=true
|
UPDATE_DATAPLANE=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import { HarborError } from '../shared/errors.js';
|
import { HarborError } from '../shared/errors.js';
|
||||||
|
|
||||||
function request(socketPath, pathname, method = 'GET', body = null) {
|
function request(socketPath, pathname, method = 'GET', body = null, timeoutMs = 6000) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const encoded = body == null ? null : JSON.stringify(body);
|
const encoded = body == null ? null : JSON.stringify(body);
|
||||||
const req = http.request({
|
const req = http.request({
|
||||||
@@ -29,7 +29,7 @@ function request(socketPath, pathname, method = 'GET', body = null) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
req.on('error', reject);
|
req.on('error', reject);
|
||||||
req.setTimeout(6000, () => req.destroy(new Error('Dataplane не ответил за 6 секунд')));
|
req.setTimeout(timeoutMs, () => req.destroy(new Error(`Dataplane не ответил за ${Math.ceil(timeoutMs / 1000)} секунд`)));
|
||||||
req.end(encoded);
|
req.end(encoded);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,13 @@ export function createDataplaneClient(socketPath, send = request) {
|
|||||||
observeTraffic: () => send(socketPath, '/device-traffic', 'GET'),
|
observeTraffic: () => send(socketPath, '/device-traffic', 'GET'),
|
||||||
observeDevicePolicy: () => send(socketPath, '/device-policy', 'GET'),
|
observeDevicePolicy: () => send(socketPath, '/device-policy', 'GET'),
|
||||||
applyDevicePolicies: (devices) => send(socketPath, '/device-policy', 'PUT', { devices }),
|
applyDevicePolicies: (devices) => send(socketPath, '/device-policy', 'PUT', { devices }),
|
||||||
runConnectivityDiagnostics: () => send(socketPath, '/diagnostics/connectivity', 'POST'),
|
runConnectivityDiagnostics: async () => {
|
||||||
|
try {
|
||||||
|
return await send(socketPath, '/diagnostics/connectivity', 'POST', null, 15_000);
|
||||||
|
} catch (cause) {
|
||||||
|
throw new HarborError('DIAGNOSTICS_FAILED', { cause });
|
||||||
|
}
|
||||||
|
},
|
||||||
apply: () => update('/apply', 'POST'),
|
apply: () => update('/apply', 'POST'),
|
||||||
restart: () => update('/restart', 'POST'),
|
restart: () => update('/restart', 'POST'),
|
||||||
stop: () => update('/stop', 'POST'),
|
stop: () => update('/stop', 'POST'),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const ERROR_DEFINITIONS = Object.freeze({
|
|||||||
DEVICE_NOT_FOUND: { status: 404, message: 'Устройство больше недоступно.', retryable: false },
|
DEVICE_NOT_FOUND: { status: 404, message: 'Устройство больше недоступно.', retryable: false },
|
||||||
DEVICE_IDENTITY_AMBIGUOUS: { status: 409, message: 'Gateway не может безопасно применить маршрут к этому устройству.', retryable: true },
|
DEVICE_IDENTITY_AMBIGUOUS: { status: 409, message: 'Gateway не может безопасно применить маршрут к этому устройству.', retryable: true },
|
||||||
DEVICE_POLICY_APPLY_FAILED: { status: 503, message: 'Не удалось применить маршрут устройства.', retryable: true },
|
DEVICE_POLICY_APPLY_FAILED: { status: 503, message: 'Не удалось применить маршрут устройства.', retryable: true },
|
||||||
|
DIAGNOSTICS_FAILED: { status: 503, message: 'Не удалось проверить маршруты. Попробуйте ещё раз.', retryable: true },
|
||||||
CONFIG_INVALID: { status: 422, message: 'Конфигурация VPN недействительна.', retryable: false },
|
CONFIG_INVALID: { status: 422, message: 'Конфигурация VPN недействительна.', retryable: false },
|
||||||
PROCESS_START_FAILED: { status: 503, message: 'Не удалось запустить VPN-процесс.', retryable: true },
|
PROCESS_START_FAILED: { status: 503, message: 'Не удалось запустить VPN-процесс.', retryable: true },
|
||||||
OPERATION_IN_PROGRESS: { status: 409, message: 'Другая операция ещё выполняется.', retryable: true },
|
OPERATION_IN_PROGRESS: { status: 409, message: 'Другая операция ещё выполняется.', retryable: true },
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const HARBOR_VERSIONS = Object.freeze({
|
export const HARBOR_VERSIONS = Object.freeze({
|
||||||
macClient: '0.15.0',
|
macClient: '0.15.1',
|
||||||
gatewayClient: '0.16.0',
|
gatewayClient: '0.16.1',
|
||||||
gatewayBackend: '0.16.0',
|
gatewayBackend: '0.16.1',
|
||||||
});
|
});
|
||||||
|
|
||||||
export function parseVersion(value) {
|
export function parseVersion(value) {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { createDataplaneClient } from '../../src/server/dataplaneClient.js';
|
|||||||
|
|
||||||
test('control uses the dataplane socket protocol', async () => {
|
test('control uses the dataplane socket protocol', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const send = async (socketPath, pathname, method, body) => {
|
const send = async (socketPath, pathname, method, body, timeoutMs) => {
|
||||||
requests.push({ method, pathname, socketPath, body });
|
requests.push({ method, pathname, socketPath, body, timeoutMs });
|
||||||
return {
|
return {
|
||||||
running: pathname !== '/stop',
|
running: pathname !== '/stop',
|
||||||
startedAt: 'now',
|
startedAt: 'now',
|
||||||
@@ -42,4 +42,16 @@ test('control uses the dataplane socket protocol', async () => {
|
|||||||
'POST /stop /run/dataplane.sock',
|
'POST /stop /run/dataplane.sock',
|
||||||
]);
|
]);
|
||||||
assert.deepEqual(requests[5].body, { devices: [{ id: 'dev_0011223344556677' }] });
|
assert.deepEqual(requests[5].body, { devices: [{ id: 'dev_0011223344556677' }] });
|
||||||
|
assert.equal(requests[6].timeoutMs, 15_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connectivity diagnostics expose a retryable domain error', async () => {
|
||||||
|
const client = createDataplaneClient('/run/dataplane.sock', async () => {
|
||||||
|
throw new Error('Dataplane не ответил');
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(client.runConnectivityDiagnostics(), {
|
||||||
|
code: 'DIAGNOSTICS_FAILED',
|
||||||
|
retryable: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ test('gateway deploy updates control without recreating dataplane', () => {
|
|||||||
assert.match(compose, /DATAPLANE_SOCKET: \/run\/vpn-proxy\/dataplane\.sock/);
|
assert.match(compose, /DATAPLANE_SOCKET: \/run\/vpn-proxy\/dataplane\.sock/);
|
||||||
assert.match(deploy, /up -d --no-deps --wait[^\n]+vpn-proxy-control/);
|
assert.match(deploy, /up -d --no-deps --wait[^\n]+vpn-proxy-control/);
|
||||||
assert.match(workflow, /UPDATE_DATAPLANE="\$\{UPDATE_DATAPLANE\}"/);
|
assert.match(workflow, /UPDATE_DATAPLANE="\$\{UPDATE_DATAPLANE\}"/);
|
||||||
assert.match(workflow, /src\/server\/\(config\|dataplane\|gatewayRouting\|singboxRuntime\|version\)/);
|
assert.match(workflow, /src\/server\/\(config\|dataplane\|gatewayRouting\|singbox\|singboxRuntime\|version\)/);
|
||||||
assert.match(workflow, /src\/server\/\(adapters\/neighbors\|services\/\(deviceTrafficService\|devicePolicyService\)\)/);
|
assert.match(workflow, /src\/server\/\(adapters\/neighbors\|services\/\(connectivityDiagnosticsService\|deviceTrafficService\|devicePolicyService\)\)/);
|
||||||
assert.match(workflow, /src\/shared\/errors/);
|
assert.match(workflow, /src\/shared\/errors/);
|
||||||
assert.doesNotMatch(workflow, /dataplaneClient/);
|
assert.doesNotMatch(workflow, /dataplaneClient/);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user