'use strict'; // ////////////////////////////////////////////////////////////////////////////// // / @brief Replication management // / // / @file // / // / DISCLAIMER // / // / Copyright 2012 triagens GmbH, Cologne, Germany // / // / Licensed under the Apache License, Version 2.0 (the "License") // / you may not use this file except in compliance with the License. // / You may obtain a copy of the License at // / // / http://www.apache.org/licenses/LICENSE-2.0 // / // / Unless required by applicable law or agreed to in writing, software // / distributed under the License is distributed on an "AS IS" BASIS, // / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // / See the License for the specific language governing permissions and // / limitations under the License. // / // / Copyright holder is triAGENS GmbH, Cologne, Germany // / // / @author Jan Steemann // / @author Copyright 2013, triAGENS GmbH, Cologne, Germany // ////////////////////////////////////////////////////////////////////////////// const internal = require('internal'); const arangosh = require('@arangodb/arangosh'); const rpc = require('@arangodb/replication-common'); let logger = {}; let applier = {}; let globalApplier = {}; function appendChar(append) { return (append === '' ? '?' : '&'); } // ////////////////////////////////////////////////////////////////////////////// // / @brief return the replication logger state // ////////////////////////////////////////////////////////////////////////////// logger.state = function () { var db = internal.db; var requestResult = db._connection.GET('/_api/replication/logger-state'); arangosh.checkRequestResult(requestResult); return requestResult; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief return the tick ranges that can be provided by the replication logger // ////////////////////////////////////////////////////////////////////////////// logger.tickRanges = function () { var db = internal.db; var requestResult = db._connection.GET('/_api/replication/logger-tick-ranges'); arangosh.checkRequestResult(requestResult); return requestResult; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief return the first tick that can be provided by the replication logger // ////////////////////////////////////////////////////////////////////////////// logger.firstTick = function () { var requestResult = internal.db._connection.GET('/_api/replication/logger-first-tick'); arangosh.checkRequestResult(requestResult); return requestResult.firstTick; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief starts the replication applier // ////////////////////////////////////////////////////////////////////////////// function applierStart(global, initialTick, barrierId) { var append = ''; if (initialTick !== undefined) { append = appendChar(append) + 'from=' + encodeURIComponent(initialTick); } if (barrierId !== undefined) { append += appendChar(append) + 'barrierId=' + encodeURIComponent(barrierId); } var url; if (global) { url = '/_db/_system/_api/replication/applier-start' + append + appendChar(append) + 'global=true'; } else { url = '/_api/replication/applier-start' + append; } var requestResult = internal.db._connection.PUT(url, ''); arangosh.checkRequestResult(requestResult); return requestResult; } applier.start = function (initialTick, barrierId) { return applierStart(false, initialTick, barrierId); }; globalApplier.start = function (initialTick, barrierId) { return applierStart(true, initialTick, barrierId); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief stops the replication applier // ////////////////////////////////////////////////////////////////////////////// function applierStop(global) { var url; if (global) { url = '/_db/_system/_api/replication/applier-stop?global=true'; } else { url = '/_api/replication/applier-stop'; } var requestResult = internal.db._connection.PUT(url, ''); arangosh.checkRequestResult(requestResult); return requestResult; }; applier.stop = function () { return applierStop(false); }; globalApplier.stop = function () { return applierStop(true); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief return the replication applier state // ////////////////////////////////////////////////////////////////////////////// function applierState(global) { var url; if (global) { url = '/_db/_system/_api/replication/applier-state?global=true'; } else { url = '/_api/replication/applier-state'; } var requestResult = internal.db._connection.GET(url); arangosh.checkRequestResult(requestResult); return requestResult; }; applier.state = function () { return applierState(false); }; globalApplier.state = function () { return applierState(true); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief return all replication applier states // ////////////////////////////////////////////////////////////////////////////// function applierStateAll(global) { var url; if (global) { url = '/_db/_system/_api/replication/applier-state-all?global=true'; } else { url = '/_api/replication/applier-state-all'; } var requestResult = internal.db._connection.GET(url); arangosh.checkRequestResult(requestResult); return requestResult; }; applier.stateAll= function () { return applierStateAll(false); }; globalApplier.stateAll = function () { return applierStateAll(true); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief stop the replication applier state and "forget" all state // ////////////////////////////////////////////////////////////////////////////// function applierForget(global) { var url; if (global) { url = '/_db/_system/_api/replication/applier-state?global=true'; } else { url = '/_api/replication/applier-state'; } var requestResult = internal.db._connection.DELETE(url); arangosh.checkRequestResult(requestResult); return requestResult; }; applier.forget = function () { return applierForget(false); }; globalApplier.forget = function () { return applierForget(true); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief configures the replication applier // ////////////////////////////////////////////////////////////////////////////// function applierProperties(global, config) { var url; if (global) { url = '/_db/_system/_api/replication/applier-config?global=true'; } else { url = '/_api/replication/applier-config'; } var requestResult; if (config === undefined) { requestResult = internal.db._connection.GET(url); } else { requestResult = internal.db._connection.PUT(url, config); } arangosh.checkRequestResult(requestResult); return requestResult; }; applier.properties = function (config) { return applierProperties(false, config); }; globalApplier.properties = function (config) { return applierProperties(true, config); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief helper function for fetching the result of an async job // ////////////////////////////////////////////////////////////////////////////// var waitForResult = function (config, id) { const db = internal.db; if (!config.hasOwnProperty('progress')) { config.progress = true; } internal.sleep(1); var iterations = 0; while (true) { const jobResult = db._connection.PUT('/_api/job/' + encodeURIComponent(id), ''); try { arangosh.checkRequestResult(jobResult); } catch (err) { throw err; } if (jobResult.code !== 204) { return jobResult; } ++iterations; if (iterations < 6) { internal.sleep(2); } else { internal.sleep(3); } if (config.progress && iterations % 3 === 0) { try { var progress = applier.state().state.progress; var msg = progress.time + ': ' + progress.message; internal.print('still synchronizing... last received status: ' + msg); } catch (err) {} } } }; // ////////////////////////////////////////////////////////////////////////////// // / @brief performs a one-time synchronization with a remote endpoint // ////////////////////////////////////////////////////////////////////////////// var sync = function (global, config) { var url; if (global) { url = '/_db/_system/_api/replication/sync?global=true'; } else { url = '/_api/replication/sync'; } const headers = { 'X-Arango-Async': 'store' }; const requestResult = internal.db._connection.PUT_RAW(url, config || {}, headers); arangosh.checkRequestResult(requestResult); if (config.async) { return requestResult.headers['x-arango-async-id']; } return waitForResult(config, requestResult.headers['x-arango-async-id']); }; var syncDatabase = function (config) { return sync(false, config); }; var syncGlobal = function (config) { return sync(true, config); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief performs a one-time synchronization with a remote endpoint, for // / a single collection // ////////////////////////////////////////////////////////////////////////////// var syncCollection = function (collection, config) { config = config || {}; config.restrictType = 'include'; config.restrictCollections = [collection]; config.includeSystem = true; return sync(false, config); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief sets up the replication (all-in-one function for initial // / synchronization and continuous replication) // ////////////////////////////////////////////////////////////////////////////// var setup = function (global, config) { var url; if (global) { url = '/_db/_system/_api/replication/make-slave?global=true'; } else { url = '/_api/replication/make-slave'; } config = config || { }; if (!config.hasOwnProperty('autoStart')) { config.autoStart = true; } if (!config.hasOwnProperty('includeSystem')) { config.includeSystem = true; } if (!config.hasOwnProperty('verbose')) { config.verbose = false; } const headers = { 'X-Arango-Async': 'store' }; const requestResult = internal.db._connection.PUT_RAW(url, config, headers); arangosh.checkRequestResult(requestResult); if (config.async) { return requestResult.headers['x-arango-async-id']; } return waitForResult(config, requestResult.headers['x-arango-async-id']); }; var setupReplication = function (config) { return setup(false, config); }; var setupReplicationGlobal = function (config) { return setup(true, config); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief queries the sync result status // ////////////////////////////////////////////////////////////////////////////// var getSyncResult = function (id) { var db = internal.db; var requestResult = db._connection.PUT_RAW('/_api/job/' + encodeURIComponent(id), ''); arangosh.checkRequestResult(requestResult); if (requestResult.headers.hasOwnProperty('x-arango-async-id')) { return JSON.parse(requestResult.body); } return false; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief fetches a server's id // ////////////////////////////////////////////////////////////////////////////// var serverId = function () { var db = internal.db; var requestResult = db._connection.GET('/_api/replication/server-id'); arangosh.checkRequestResult(requestResult); return requestResult.serverId; }; exports.logger = logger; exports.applier = applier; exports.globalApplier = globalApplier; exports.sync = syncDatabase; exports.syncGlobal = syncGlobal; exports.syncCollection = syncCollection; exports.setupReplication = setupReplication; exports.setupReplicationGlobal = setupReplicationGlobal; exports.getSyncResult = getSyncResult; exports.serverId = serverId; exports.compareTicks = rpc.compareTicks;