dl
Version:
DreamLab Libs
565 lines (461 loc) • 18.2 kB
JavaScript
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;