realm-object-server-enterprise
Version:
Realm Object Server Enterprise
463 lines • 20.4 kB
JavaScript
"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