UNPKG

realm-object-server-enterprise

Version:

Realm Object Server Enterprise

463 lines 20.4 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const realm_sync_server_1 = require("realm-sync-server"); const realm_object_server_1 = require("realm-object-server"); const Promoter_1 = require("./Promoter"); const mkdirp = require("mkdirp-promise"); class MasterRecord { } exports.MasterRecord = MasterRecord; class CallbackQueue { constructor() { this.busy = false; this.q = []; } push(func) { this.q.push(func); if (!this.busy) { this.pop(); } } pop() { return __awaiter(this, void 0, void 0, function* () { this.busy = true; while (true) { const func = this.q.shift(); if (func) { yield func(); } else { break; } } this.busy = false; }); } } var ReplicatedSyncBackupMode; (function (ReplicatedSyncBackupMode) { ReplicatedSyncBackupMode[ReplicatedSyncBackupMode["Asynchronous"] = 1] = "Asynchronous"; ReplicatedSyncBackupMode[ReplicatedSyncBackupMode["Synchronous"] = 2] = "Synchronous"; })(ReplicatedSyncBackupMode = exports.ReplicatedSyncBackupMode || (exports.ReplicatedSyncBackupMode = {})); let BaseSyncService = class BaseSyncService { constructor(syncConfig) { this.tags = []; this.syncConfig = syncConfig; } start(wrapperService) { return __awaiter(this, void 0, void 0, function* () { this.wrapperService = wrapperService; this.logger = this.wrapperService.logger; }); } stop() { return __awaiter(this, void 0, void 0, function* () { if (this.server) { yield this.server.stop().catch(); delete this.server; } yield this.deregister(); }); } getAddress() { return { address: this.syncConfig.listenAddress, port: Number(this.syncConfig.listenPort), }; } register(roleTag) { return __awaiter(this, void 0, void 0, function* () { const { address, port } = this.getAddress(); this.tags = [`label=${this.wrapperService.label}`]; if (roleTag) { this.tags.push(`role=${roleTag}`); } yield this.wrapperService.discovery.registerService(this, address, port); }); } deregister() { return __awaiter(this, void 0, void 0, function* () { yield this.wrapperService.discovery.deregisterService(this); }); } }; BaseSyncService = __decorate([ realm_object_server_1.ServiceName("sync"), __metadata("design:paramtypes", [Object]) ], BaseSyncService); exports.BaseSyncService = BaseSyncService; let MasterSyncService = class MasterSyncService extends BaseSyncService { constructor() { super(...arguments); this.callbackQueue = new CallbackQueue(); this.settingSlaveStatus = false; } start(wrapperService) { const _super = name => super[name]; return __awaiter(this, void 0, void 0, function* () { yield _super("start").call(this, wrapperService); const config = Object.assign({}, this.syncConfig, { operatingMode: wrapperService.backupMode, slaveStatusCallback: this.setSlaveStatus.bind(this) }); this.server = new realm_sync_server_1.RealmSyncServer(config); yield this.server.start(); }); } setSlaveStatus(slave, complete) { return __awaiter(this, void 0, void 0, function* () { this.callbackQueue.push(() => __awaiter(this, void 0, void 0, function* () { if (this.settingSlaveStatus) { this.logger.fatal("already setting slave status"); process.exit(1); } this.settingSlaveStatus = true; try { this.logger.info(`slave ${slave} is now ${complete ? "complete" : "incomplete"}`); const { cas, record } = yield this.wrapperService.getMasterRecord(); if (record.slave.id === slave && record.slave.complete) { this.logger.debug("do not change the slave status, because it is 'complete'"); } record.slave.id = slave; record.slave.complete = complete; const saved = yield this.wrapperService.setMasterRecord(cas, record); this.settingSlaveStatus = false; if (!saved) { this.logger.warn("could not save the master record, someone was faster"); } yield this.register("master"); } catch (e) { this.logger.error(`could not update slave status in the master record: ${e}`); } })); }); } }; MasterSyncService = __decorate([ realm_object_server_1.ServiceName("sync") ], MasterSyncService); exports.MasterSyncService = MasterSyncService; let SlaveSyncService = class SlaveSyncService extends BaseSyncService { start(wrapperService) { const _super = name => super[name]; return __awaiter(this, void 0, void 0, function* () { yield _super("start").call(this, wrapperService); const config = Object.assign({}, this.syncConfig, { operatingMode: realm_sync_server_1.RealmSyncServerOperatingMode.Slave }); this.server = new realm_sync_server_1.RealmSyncServer(config); yield this.server.start(); yield this.register("slave"); }); } }; SlaveSyncService = __decorate([ realm_object_server_1.ServiceName("sync") ], SlaveSyncService); exports.SlaveSyncService = SlaveSyncService; let SpareSyncService = class SpareSyncService extends BaseSyncService { start(wrapperService) { const _super = name => super[name]; return __awaiter(this, void 0, void 0, function* () { yield _super("start").call(this, wrapperService); yield this.register("spare"); }); } }; SpareSyncService = __decorate([ realm_object_server_1.ServiceName("sync") ], SpareSyncService); exports.SpareSyncService = SpareSyncService; class ReplicatedSyncService { constructor(serviceConfig) { this.logger = new realm_object_server_1.MuteLogger(); this.consul = serviceConfig.consul; this.label = serviceConfig.label; if (!this.label) { this.label = "default"; } this.tags = [`label=${this.label}`]; this.namespace = "sync-worker-group"; if (serviceConfig.namespace) { this.namespace = `${serviceConfig.namespace}/sync-worker-group`; } this.masterKey = `${this.namespace}/${this.label}/master`; this.slaveKey = `${this.namespace}/${this.label}/slave`; let verifyRealmsAtStart; if (serviceConfig.verifyRealmsAtStart !== undefined) { verifyRealmsAtStart = serviceConfig.verifyRealmsAtStart; } else if (serviceConfig.skipVerifyRealmsAtStart !== undefined) { verifyRealmsAtStart = !serviceConfig.skipVerifyRealmsAtStart; } let runPrecheckInChildProcess; if (serviceConfig.runPrecheckInChildProcess !== undefined) { runPrecheckInChildProcess = serviceConfig.runPrecheckInChildProcess; } else if (serviceConfig.disablePrecheckInChildProc !== undefined) { runPrecheckInChildProcess = !serviceConfig.disablePrecheckInChildProc; } this.syncConfig = { id: serviceConfig.id, dataPath: serviceConfig.dataPath, listenAddress: serviceConfig.listenAddress, listenPort: serviceConfig.listenPort, logLevel: serviceConfig.logLevel, logCallback: (levelNo, message, timestampMicroseconds) => { const ms = Math.floor(timestampMicroseconds / 1000); const epoch = Math.floor(ms / 1000); const nano = (timestampMicroseconds - (epoch * 1000 * 1000)) * 1000; this.logger.log(realm_object_server_1.SyncLogLevel[levelNo], message, {}, [epoch, nano]); }, publicKeyPath: serviceConfig.publicKeyPath, featureToken: serviceConfig.featureToken, enableDownloadLogCompaction: serviceConfig.enableDownloadLogCompaction, maxDownloadSize: serviceConfig.maxDownloadSize, enableLogCompaction: serviceConfig.enableLogCompaction, enableRealmStateSizeReporting: serviceConfig.enableRealmStateSizeReporting, historyCompactionInterval: serviceConfig.historyCompactionInterval, historyTtl: serviceConfig.historyTtl, shouldCompactRealmsAtStart: serviceConfig.shouldCompactRealmsAtStart, shouldPerformPartialSyncAtStart: serviceConfig.shouldPerformPartialSyncAtStart, skipVerifyRealmsAtStart: !verifyRealmsAtStart, disablePrecheckInChildProc: !runPrecheckInChildProcess, maxOpenFiles: serviceConfig.maxFilesInCache, maxUploadBacklog: serviceConfig.maxUploadBacklog, enableDownloadBootstrapCache: serviceConfig.enableDownloadBootstrapCache, disablePartialSyncCompleter: serviceConfig.disablePartialSyncCompleter, }; this.promoter = new Promoter_1.Promoter(serviceConfig.id, this.masterKey + "-lock", this.slaveKey + "-lock", this.consul); const mode = serviceConfig.backupMode || ReplicatedSyncBackupMode.Synchronous; this.backupMode = mode; } setLogger(logger) { this.logger = logger; this.promoter.setLogger(logger); } start(discovery) { return __awaiter(this, void 0, void 0, function* () { this.discovery = discovery; yield mkdirp(this.syncConfig.dataPath); this.promoter.on("master", this.candidate.bind(this)); this.promoter.on("spare", this.spare.bind(this)); this.promoter.on("slave", this.slave.bind(this)); this.promoter.start(); }); } startWithServer(server) { return __awaiter(this, void 0, void 0, function* () { const serverConfig = server["serverConfig"]; if (serverConfig.https) { this.syncConfig.ssl = true; this.syncConfig.masterSlaveSsl = true; this.syncConfig.sslCertificatePath = serverConfig.httpsCertChainPath; this.syncConfig.masterSlaveSslTrustCertificatePath = serverConfig.httpsCertChainPath; this.syncConfig.sslCertificateKeyPath = serverConfig.httpsKeyPath; if (!serverConfig.serviceAgent.options.rejectUnauthorized || process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0") { this.syncConfig.masterVerifySslCertificate = false; } } if (this.stats) { this.statsdReceiver = new realm_object_server_1.stats.StatsdReceiver({ logger: this.logger }); this.statsdToStatsSink = new realm_object_server_1.stats.StatsdToStatsSink({ logger: this.logger, statsSink: this.stats, }, this.statsdReceiver); this.statsdSocket = yield this.statsdReceiver.start(); this.syncConfig.statsEndpoint = `127.0.0.1:${this.statsdSocket.address().port}`; } yield this.start(server.discovery); }); } stop() { return __awaiter(this, void 0, void 0, function* () { clearTimeout(this.timeout); this.promoter.stop(); if (this.service) { yield this.service.stop(); delete this.service; } if (this.statsdSocket) { yield new Promise((resolve) => this.statsdSocket.close(resolve)).catch(); delete this.statsdSocket; } }); } getAddress() { return { address: this.syncConfig.listenAddress, port: Number(this.syncConfig.listenPort), }; } getMasterRecord() { return __awaiter(this, void 0, void 0, function* () { const v = yield this.consul.kv.get(this.masterKey); if (!v) { return { cas: "0", record: undefined }; } return { cas: v.ModifyIndex, record: JSON.parse(v.Value) }; }); } setMasterRecord(cas, record) { return __awaiter(this, void 0, void 0, function* () { return yield this.consul.kv.set({ key: this.masterKey, cas, value: JSON.stringify(record), }); }); } spare() { return __awaiter(this, void 0, void 0, function* () { clearTimeout(this.timeout); if (this.service) { this.logger.info("stopping the server, becoming spare"); yield this.service.stop(); delete this.service; } this.logger.info("starting as spare"); this.service = new SpareSyncService(this.syncConfig); yield this.service.start(this); }); } slave(master, retry = 0) { return __awaiter(this, void 0, void 0, function* () { if (this.service) { this.logger.info("stopping existing service"); yield this.service.stop(); delete this.service; } clearTimeout(this.timeout); const { cas, record } = yield this.getMasterRecord(); if (!cas || record.master === undefined || record.master.id !== master) { if (retry > 3) { this.logger.error(`master is not ${master}, human needed`); this.promoter.demote(); return; } const timeout = 5000; this.logger.warn(`master is not ${master}, retrying in ${timeout} ms`); this.timeout = setTimeout(() => this.slave(master, retry + 1), timeout); return; } const { address, port } = record.master; this.logger.info(`starting as slave to master ${master} at ${address}:${port}`); const config = Object.assign({}, this.syncConfig, { masterAddress: address, masterPort: port }); this.service = new SlaveSyncService(config); yield this.service.start(this); }); } candidate() { return __awaiter(this, void 0, void 0, function* () { clearTimeout(this.timeout); this.logger.info("we are a candidate"); if (this.service) { this.logger.info("stopping existing service"); yield this.service.stop(); delete this.service; } const endpoint = { address: this.discovery.advertiseAddress || this.syncConfig.listenAddress, port: this.discovery.advertisePortMap[this.syncConfig.listenPort] || this.syncConfig.listenPort, }; const { cas, record } = yield this.getMasterRecord(); if (!record) { const newrecord = new MasterRecord(); this.logger.info("no previous master, bootstrap: "); newrecord.master = Object.assign({ id: this.syncConfig.id }, endpoint); newrecord.slave = { id: undefined, complete: false, }; const saved = yield this.setMasterRecord(cas, newrecord); if (!saved) { this.logger.warn("could not save the master record, someone was faster"); return; } this.master(); } else if (record.master.id === this.syncConfig.id) { this.logger.info("I was the master, recover"); record.master = Object.assign({}, record.master, endpoint); const saved = yield this.setMasterRecord(cas, record); if (!saved) { this.logger.warn("could not save the master record, someone was faster"); return; } this.master(); } else if (record.slave.id === this.syncConfig.id) { if (record.slave.complete) { this.logger.info("I was a complete slave, promote"); record.master = Object.assign({ id: this.syncConfig.id }, endpoint); record.slave = { id: undefined, complete: false, }; const saved = yield this.setMasterRecord(cas, record); if (!saved) { this.logger.warn("could not save the master record, someone was faster"); return; } this.master(); } else { this.logger.error("I was an incomplete slave, human needed"); this.promoter.demote(); return; } } else { this.logger.error("I was not around, human needed"); this.promoter.demote(); return; } }); } master() { return __awaiter(this, void 0, void 0, function* () { this.logger.info("starting as master"); this.service = new MasterSyncService(this.syncConfig); yield this.service.start(this); }); } } __decorate([ realm_object_server_1.Unmute(), __metadata("design:type", Function), __metadata("design:paramtypes", [realm_object_server_1.Logger]), __metadata("design:returntype", void 0) ], ReplicatedSyncService.prototype, "setLogger", null); __decorate([ realm_object_server_1.Start(), __metadata("design:type", Function), __metadata("design:paramtypes", [realm_object_server_1.Server]), __metadata("design:returntype", Promise) ], ReplicatedSyncService.prototype, "startWithServer", null); __decorate([ realm_object_server_1.Stop(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], ReplicatedSyncService.prototype, "stop", null); __decorate([ realm_object_server_1.Address(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], ReplicatedSyncService.prototype, "getAddress", null); exports.ReplicatedSyncService = ReplicatedSyncService; //# sourceMappingURL=ReplicatedSyncService.js.map