UNPKG

dl

Version:

DreamLab Libs

565 lines (461 loc) 18.2 kB
var AbstractDataProvider = require('./AbstractDataProvider.js').AbstractDataProvider; var core = require('core'); var Crypto = core.crypto; var Types = core.common.Types; var CredentialsProvider = core.credentials.CredentialsProvider; var OpalCredentialsProvider = require('../opal/OpalCredentialsProvider.js').OpalCredentialsProvider; var ZooKeeper = require('zookeeper'); var ZooKeeperDataProvider = function (params) { AbstractDataProvider.apply(this, arguments); this._params = params; this._cred = OpalCredentialsProvider.getInstance(params.credential, params.identity, params.gtw); this._readyCallback = null; this._reloadedCallback = null; this._credentialTimeout = 15000; this._zookeeperTimeout = 10000; this._zookeeper = null; this._clientId = null; this._clientPassword = ZooKeeperDataProvider.PASSWORD; this._ready = false; this._credentialObserver = true; /* lista zapiętych watcherów: {key: klucz, callback: callback} */ this._watchers = []; this._requestQueue = []; this._auth = []; }; ZooKeeperDataProvider.prototype = Object.create(AbstractDataProvider.prototype); ZooKeeperDataProvider.prototype.getClientId = function () { return this._clientId; }; ZooKeeperDataProvider.prototype.setAuth = function (value) { this._auth = value; return this; }; ZooKeeperDataProvider.prototype.addAuth = function (scheme, data, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, scheme, data, callback), callback); return; } /* remember auth for reconnect */ this._auth.push({ scheme: scheme, data: data, callback: callback }); // for backward compatibility if (!callback) { callback = function () {}; } console.info('DataProvider[ZooKeeper]: add_auth %s %s', scheme, data); var that = this; this._zookeeper.add_auth(scheme, data, function(rc) { switch (rc) { case ZooKeeper.ZOK: callback(null); break; case ZooKeeper.ZCONNECTIONLOSS: /* stracilismy polaczenie do ZK */ case ZooKeeper.ZOPERATIONTIMEOUT: console.warn('DataProvider[ZooKeeper]:', rc, ' - defering request add_auth', scheme, data); that._deferRequest(that.set.bind(that, scheme, data, callback), callback); break; default: callback(rc, null); } }); }; ZooKeeperDataProvider.prototype._credLoaded = function (ev) { console.info('DataProvider[ZooKeeper]: Credential loaded'); var that = this; var timeout = this._zookeeperTimeout; var hosts = ev.data.hosts; var hostsList = []; for (var i = 0, l = hosts.length; i < l; i++) { hostsList.push(hosts[i].host + ':' + hosts[i].port); } if (parseInt(ev.data.timeout) > 0) { timeout = ev.data.timeout; } if (this._zookeeper) { this._zookeeper.forceClose = true; this._zookeeper.close(); } this._zookeeper = new ZooKeeper(); this._zookeeper.forceClose = false; this._zookeeper.on(ZooKeeper.on_closed, function (zk, code) { console.info('DataProvider[ZooKeeper]: Entered closed state, sid: %s, (forced: %s)', zk.client_id, zk.forceClose); if (parseInt(code) == ZooKeeper.ZOO_EXPIRED_SESSION_STATE) { console.warn('DataProvider[ZooKeeper]: Session expired, sid: %s', zk.client_id); that._clientId = null; //w sumie tutaj mozna zrobic reconnecta, nie trzeba tutaj pobierac credentiali od nowa w takim wypadku } if (!zk.forceClose) { that._watchCredentials(); that._scheduleRefreshCredentials(0); } }); this._zookeeper.on(ZooKeeper.on_connecting, function (zk) { console.info('DataProvider[ZooKeeper]: Entered connecting state, sid: %s', zk.client_id); that._watchCredentials(); that._scheduleRefreshCredentials(that._credentialTimeout); }); this._zookeeper.on(ZooKeeper.on_connected, function (zk) { console.info('DataProvider[ZooKeeper]: Entered connected state, sid: %s', zk.client_id); that._unwatchCredentials(); if (that._clientId && that._clientId == zk.client_id) { console.info('DataProvider[ZooKeeper]: Connection restored, sid: %s', zk.client_id); } else { console.info('DataProvider[ZooKeeper]: New connection established, sid: %s', zk.client_id); that._clientId = zk.client_id; that._reauthorize(); that._assignWatchers(); that._reloadedCallback(); } that._flushQueue(); /* pierwsze uruchomienie */ if (!that._ready) { that._readyCallback(); } that._ready = true; }); this._zookeeper.setLogger(false); var zkInit = { connect: hostsList.join(','), timeout: timeout, debug_level: ZooKeeper.ZOO_LOG_LEVEL_WARNING, hosts_order_deterministic: false, data_as_buffer: false }; if (this._clientId) { console.info('DataProvider[ZooKeeper]: Trying to renew old session, sid: %s with password: %s', this._clientId, this._clientPassword); zkInit.client_id = this._clientId; zkInit.client_password = this._clientPassword; } this._zookeeper.init(zkInit); this._scheduleRefreshCredentials(15000); }; ZooKeeperDataProvider.prototype._unwatchCredentials = function () { if (this._credentialTimer) { clearTimeout(this._credentialTimer); } if (this._credentialObserver) { this._cred.removeEventListener(CredentialsProvider.Event.LOAD, this._credLoaded, this); this._cred.removeEventListener(CredentialsProvider.Event.TIMEOUT, this._credError, this); this._cred.removeEventListener(CredentialsProvider.Event.ERROR, this._credError, this); this._credentialObserver = false; } }; ZooKeeperDataProvider.prototype._watchCredentials = function () { if (!this._credentialObserver) { this._cred.addEventListener(CredentialsProvider.Event.LOAD, this._credLoaded, this); this._cred.addEventListener(CredentialsProvider.Event.TIMEOUT, this._credError, this); this._cred.addEventListener(CredentialsProvider.Event.ERROR, this._credError, this); this._credentialObserver = true; } }; ZooKeeperDataProvider.prototype._refreshCredentials = function () { console.info('DataProvider[ZooKeeper]: Refreshing credentials'); this._cred.refresh(true); }; ZooKeeperDataProvider.prototype._scheduleRefreshCredentials = function (timeout) { if (this._credentialTimer) { clearTimeout(this._credentialTimer); } if (timeout == 0) { this._refreshCredentials(); this._scheduleRefreshCredentials(this._credentialTimeout); } else { this._credentialTimer = setTimeout(this._refreshCredentials.bind(this), timeout); } }; ZooKeeperDataProvider.prototype._credError = function (ev) { this._cred.refresh(); }; ZooKeeperDataProvider.prototype.init = function (readyCallback, reloadedCallback) { var that = this; this._readyCallback = function () { readyCallback(null, true); }; this._reloadedCallback = function () { reloadedCallback(null, true); }; this._cred.addEventListener(CredentialsProvider.Event.LOAD, this._credLoaded, this); this._cred.addEventListener(CredentialsProvider.Event.TIMEOUT, this._credError, this); this._cred.addEventListener(CredentialsProvider.Event.ERROR, this._credError, this); this._cred.get(function () {}); }; ZooKeeperDataProvider.prototype.get = function (key, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, key, callback), callback); return; } if (!this._isKeyValid(key)) { console.warn('DataProvider[ZooKeeper]: invalid key', key); return callback(ZooKeeper.ZNOTHING); } var that = this; var onGetCallback = function (rc, error, data, stat) { switch (rc) { case ZooKeeper.ZOK: callback(null, data, stat); break; case ZooKeeper.ZCONNECTIONLOSS: case ZooKeeper.ZOPERATIONTIMEOUT: console.info('DataProvider[ZooKeeper]:', rc, ' - requeuing request', 'GET', key); that._deferRequest(that.get.bind(that, key, callback), callback); break; default: console.warn('DataProvider[ZooKeeper]:', 'Unhandled error', rc, error, key); callback(rc, null); } }; /* sprawdzenie czy chce pobrać liste */ if (key[key.length - 1] == '/') { /* ucinam ostatni znak*/ var newKey = key.slice(0, -1); this._zookeeper.a_get_children2(newKey, false, function (rc, error, children, stat) { onGetCallback(rc, error, children, stat); }); } else { this._zookeeper.a_get(key, false, function (rc, error, stat, data) { onGetCallback(rc, error, data, stat); }); } }; ZooKeeperDataProvider.prototype.set = function (key, value, options, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, key, value, options, callback), callback); return; } if (!this._isKeyValid(key)) { console.warn('DataProvider[ZooKeeper]: invalid key', key); return callback(ZooKeeper.ZNOTHING); } var that = this; var version = isNaN(options.version) ? -1 : options.version; var flags = options.flags || 0; this._zookeeper.a_set(key, value, version, function (rc, error, stat) { switch (rc) { case ZooKeeper.ZOK: callback(null, stat); break; case ZooKeeper.ZNONODE: /* brak klucza, probuje utworzyc */ that._createKey(key, value, flags, callback); break; case ZooKeeper.ZCONNECTIONLOSS: /* stracilismy polaczenie do ZK */ case ZooKeeper.ZOPERATIONTIMEOUT: console.warn('DataProvider[ZooKeeper]:', rc, ' - defering request', 'SET', key); that._deferRequest(that.set.bind(that, key, value, options, callback), callback); break; default: callback(rc, null); } }); }; ZooKeeperDataProvider.prototype.sync = function (key, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, key, callback), callback); return; } if (!this._isKeyValid(key)) { console.warn('DataProvider[ZooKeeper]: invalid key', key); return callback(ZooKeeper.ZNOTHING); } var that = this; this._zookeeper.a_sync(key, function (rc, error, path) { switch (rc) { case ZooKeeper.ZOK: callback(null, null); break; case ZooKeeper.ZCONNECTIONLOSS: case ZooKeeper.ZOPERATIONTIMEOUT: console.warn('DataProvider[ZooKeeper]:', rc, ' - defering request', 'SYNC', key); that._deferRequest(that.sync.bind(that, key, callback), callback); break; default: callback(rc, error); } }); }; ZooKeeperDataProvider.prototype.remove = function (key, options, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, key, options, callback), callback); return; } if (!this._isKeyValid(key)) { console.warn('DataProvider[ZooKeeper]: invalid key', key); return callback(ZooKeeper.ZNOTHING); } var that = this; var version = options.version || -1; this._zookeeper.a_delete_(key, version, function (rc, error) { switch (rc) { case ZooKeeper.ZOK: callback(null, null); break; case ZooKeeper.ZCONNECTIONLOSS: case ZooKeeper.ZOPERATIONTIMEOUT: console.warn('DataProvider[ZooKeeper]:', rc, ' - defering request', 'DELETE', key); that._deferRequest(that.remove.bind(that, key, options, callback), callback); break; default: callback(rc, error); } }); }; ZooKeeperDataProvider.prototype._createKey = function (key, value, flags, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, key, value, flags, callback), callback); return; } var that = this; this._zookeeper.a_create(key, value, flags, function (rc, error, path) { switch (rc) { case ZooKeeper.ZOK: //tutaj musimy jednak zwrocic obiekt stat a nie sciezke do zalonego obiektu that._zookeeper.a_exists(path, false, function (rc, error, stat) { callback(null, stat); }); break; case ZooKeeper.ZCONNECTIONLOSS: case ZooKeeper.ZOPERATIONTIMEOUT: console.warn('DataProvider[ZooKeeper]:', rc, ' - defering request', 'CREATE', key); that._deferRequest(that._createKey.bind(that, key, value, flags, callback), callback); break; default: callback(rc, error); } }); }; ZooKeeperDataProvider.prototype.exists = function (key, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, key, callback), callback); return; } if (!this._isKeyValid(key)) { console.warn('DataProvider[ZooKeeper]: invalid key', key); return callback(ZooKeeper.ZNOTHING); } var that = this; this._zookeeper.a_exists(key, false, function (rc, error, stat) { switch (rc) { case ZooKeeper.ZOK: callback(null, stat); break; case ZooKeeper.ZCONNECTIONLOSS: case ZooKeeper.ZOPERATIONTIMEOUT: console.warn('DataProvider[ZooKeeper]:', rc, ' - defering request', 'CREATE', key); that._deferRequest(that.exists.bind(that, key, callback), callback); break; default: callback(rc, error); } }); }; ZooKeeperDataProvider.prototype.watch = function (key, callback) { if (!this.isConnected()) { this._deferRequest(arguments.callee.bind(this, key, callback)); return; } if (!this._isKeyValid(key)) { console.warn('DataProvider[ZooKeeper]: invalid key', key); return callback(ZooKeeper.ZNOTHING); } /* dopisanie watchera - potrzebne przy reconnect */ this._watchers.push({ key: key, callback: callback }); if (key[key.length - 1] == '/') { this._watchChildren(key, callback); } else { this._watchNode(key, callback); }; }; ZooKeeperDataProvider.prototype._watchChildren = function (key, callback) { var that = this; key = key.slice(0, -1); var watcher = function (type, state, path) { if (!that.isConnected()) { that._deferRequest(arguments.callee.bind(that, type, state, path)); return; } that._zookeeper.aw_get_children2(path, function (type, state, path) { watcher(type, state, path); }, function (rc, error, children, stat) { if (rc) { return callback(rc, null); } return callback(null, children, stat); }); }; watcher(null, null, key); }; ZooKeeperDataProvider.prototype._watchNode = function (key, callback) { var that = this; var watcher = function (type, state, path) { if (!that.isConnected()) { that._deferRequest(arguments.callee.bind(that, type, state, path)); return; } that._zookeeper.aw_get(path, function (type, state, path) { watcher(type, state, path); }, function (rc, error, stat, data) { if (rc) { return callback(rc, null); } return callback(null, data, stat); }); }; watcher(null, null, key); }; ZooKeeperDataProvider.prototype._reauthorize = function () { var tmpAuth = this._auth; this._auth = []; for (var i = 0, len = tmpAuth.length; i < len; i++) { this.addAuth(tmpAuth[i].scheme, tmpAuth[i].data, tmpAuth[i].callback); } }; ZooKeeperDataProvider.prototype._assignWatchers = function () { var tmpWatchers = this._watchers; this._watchers = []; for (var i = 0, len = tmpWatchers.length; i < len; i++) { this.watch(tmpWatchers[i].key, tmpWatchers[i].callback); } }; ZooKeeperDataProvider.prototype.isConnected = function () { return this._zookeeper && this._zookeeper.state == ZooKeeper.ZOO_CONNECTED_STATE; }; ZooKeeperDataProvider.prototype._deferRequest = function (fnc, timeoutCb) { this._requestQueue.push(fnc); if (timeoutCb) { var that = this; setTimeout(function () { var idx = that._requestQueue.indexOf(fnc); if (idx === -1) { return; } that._requestQueue.splice(idx, 1); return timeoutCb(new Error('Timed out')); }, ZooKeeperDataProvider.DEFER_TIMEOUT); } }; ZooKeeperDataProvider.prototype._flushQueue = function () { console.info('DataProvider[ZooKeeper]: Flushing queue', this._requestQueue.length); var requestQueue = this._requestQueue.slice(); this._requestQueue = []; while (requestQueue.length) { requestQueue.shift()(); } }; ZooKeeperDataProvider.prototype._isKeyValid = function (key) { if (!Types.isString(key)) { return false; } if (key.indexOf('//') !== -1 || key.indexOf(' ') !== -1) { return false; } return true; }; ZooKeeperDataProvider.DEFER_TIMEOUT = 60000; ZooKeeperDataProvider.PASSWORD = Crypto.md5('tajnehaslo'); exports.ZooKeeperDataProvider = ZooKeeperDataProvider;