ovsdb-client
Version:
Client to an OVS database
388 lines (327 loc) • 13.4 kB
JavaScript
/*
* Open vMonitor is a WEB-based tool for monitoring and troubleshooting Open vSwitch
* Copyright (C) 2014-2016 PLVision
* Ihor Chumak, Roman Gotsiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* PLVision, developers@plvision.eu
*/
var util = require('util'), events = require('events'), uuid = require('node-uuid'), debug = require('debug')('tables');
var Connection = require('./connection');
var asyncForEach = require("async").forEach;
var DataModelBuilder = require("./datamodel");
/**
* Create a new JSON-RPC client.
*
* @api public
*/
exports.createClient = function (port, host, certs) {
// FIXME: we should not fill-in the Connection with an extra-logic
return new Client(port, host, certs);
};
const default_update_statistics_interval = 5e3; // 1e4
function Client(port, host, certs) {
var self = this;
this.host = host;
this.port = port;
if (certs) {
this.secure = true;
this.certs = certs;
}
this._connection = new Connection();
this._updates = [];
this.info = {
databases: {},
stats_refresh_interval: 0,
statistics: {}
};
events.EventEmitter.call(this);
}
/**
* Inherit from `events.EventEmitter`.
*/
util.inherits(Client, events.EventEmitter);
Client.prototype.connect = function (callback) {
var self = this;
// TODO: do we really need an echo empty?
this._connection.expose('echo', function (result) {
return result(null, '');
});
this._connection.expose('update', function (_uuid, result) {
debug('ERR: ', _uuid, result);
self._updates.unshift(result);
// we always send OK
return result(null, '');
});
/*
* do a connection
*/
var connect;
if (this.secure) {
connect = function (port, host, callback) {
self._connection.secure_connect(port, host, self.certs, callback);
};
} else {
connect = this._connection.connect.bind(this._connection);
}
connect(this.port, this.host, function (remote, authorized, auth_error) {
self.authorized = authorized;
self.auth_error = auth_error;
remote.call('list_dbs', null, function (err, result) {
if (err) {
callback(err, null);
return;
}
asyncForEach(result, function (db_name, next) {
var database = {
name: db_name,
statistics: {},
};
remote.call('get_schema', database.name, function (err, result) {
database.schema = result;
var data_model = DataModelBuilder(self, database.name, database.schema);
database.model = data_model;
self.info.databases[database.name] = database;
next();
});
}, function (err) {
// notify that we're connected to an OVS database
// subscribe to notifications
// FIXME: need to fix an issue with multiple monitoring (initiated by page and deviceview)
self.start_monitor("Bridge", ['name']);
self.start_monitor("Port", ['name']);
// self.start_monitor("Controller", [ 'target' ]);
// self.start_monitor("Manager", [ 'target' ]);
self.start_monitor_statistics();
if (callback) {
callback(null, remote);
}
});
});
});
this._connection.on("error", function (err) {
callback(err, null);
});
};
Client.prototype.close = function () {
var self = this;
self._connection.end();
// clearInterval(this.info.statistics.refresh_interval);
clearInterval(this.info.stats_refresh_interval);
};
Client.prototype.connected = function () {
return this._connection._connected;
};
Client.prototype.database = function (dbname) {
if (dbname === undefined) {
// select first database if not specified
return this.info.databases[this.databases()[0]].model;
}
return this.info.databases[dbname].model;
};
Client.prototype.db_schema = function (dbname) {
if (dbname === undefined) {
return this.info.databases[this.databases()[0]].schema;
}
return this.info.databases[dbname].schema;
};
Client.prototype.databases = function () {
return Object.keys(this.info.databases);
};
Client.prototype.statistics = function (dbname, intf, callback) {
// need to add maximum diff
if (intf == "*") {
callback(null, this.info.statistics);
} else {
callback(null, this.info.statistics[intf]);
}
};
Client.prototype.select = function (dbname, table, where, columns, callback) {
if (arguments.length == 3) {
callback = where;
where = null;
columns = null;
}
if (arguments.length == 4) {
callback = columns;
columns = null;
}
if (!where) {
where = [];
}
var operation = {
op: 'select',
table: table,
where: where,
};
if (columns) {
if (!(columns instanceof Array)) {
columns = [columns];
}
operation.columns = columns;
}
this._connection._remote.call('transact', dbname, operation, function (err, result) {
if (err)
return callback(err);
if (callback) {
callback(err, result[0].rows);
}
});
};
Client.prototype.multicall = function (dbname) {
var self = this;
var multicall = {};
multicall.operations = [];
multicall.add = function (operation) {
multicall.operations.push(operation);
};
multicall.call = function (callback) {
self._connection._remote.call('transact', dbname, multicall.operations, callback);
};
return multicall;
};
Client.prototype.raw_request = function (operation, callback) {
var self = this;
var dbname = "Open_vSwitch";
var remote = this._connection._remote;
debug(operation);
remote.call('transact', dbname, operation, function (err, result) {
debug(result);
if (callback) {
callback(result);
}
});
};
Client.prototype.ping = function (callback) {
var self = this;
var remote = this._connection._remote;
remote.call('list_dbs', null, function (err) {
if (callback) {
callback(err);
}
});
};
Client.prototype.start_monitor = function (table, columns, callback) {
var self = this;
var dbname = "Open_vSwitch";
var remote = this._connection._remote;
var monitor_request = {};
monitor_request[table] = {
columns: columns
}
debug(JSON.stringify(monitor_request));
remote.call('monitor', dbname, uuid.v4(), monitor_request, function (err, data) {
debug(err, data);
if (callback) {
callback();
}
});
}
Client.prototype.start_monitor_statistics = function (num) {
var self = this;
const max_number_of_entries = num || 100;
var dbname = "Open_vSwitch";
if (!this.info.stats_refresh_interval) {
// TODO: add support for multiple databases
// asyncForEach(Object.keys(self.info.databases), function (name, next) {
this.info.stats_refresh_interval = setInterval(function () {
self.database(dbname).bridges().each(function (bridge, next_bridge) {
bridge.data(function (err, data) {
if (!err) {
bridge.ports().each(function (port, next_port) {
port.data(function (err, data) {
if (!err) {
port.interfaces().each(function (intf, next_intf) {
intf.data(function (err, data) {
if (!err) {
var name = data.name;
//console.log(name);
// TODO: add rx/tx stats
var stats = data.statistics[1].filter(function (elem) {
return (elem[0] == 'rx_bytes' || elem[0] == 'tx_bytes');
});
// get all stats (tx/rx)
var value = (stats.length > 0) ? stats[0][1] + stats[1][1] : 0
// for debugging only
if (!self.info.statistics[name]) {
self.info.statistics[name] = [];
self.info.statistics[name].last = value;
}
if (self.info.statistics[name].length > max_number_of_entries) {
self.info.statistics[name].shift();
}
var tmp = value;
value -= self.info.statistics[name].last;
// do interpolate
{
var sum = value;
var len = self.info.statistics[name].length;
var idx = 0;
while (len - idx > 0 && idx < 2) {
sum += self.info.statistics[name][len - idx - 1].value;
idx++;
}
var res = (sum / (idx + 1)) >>> 0;
// FIXME: need to optimize this
var time = new Date();
var info = {
date: time,
value: (value / 5) << 3 >>> 0 /* res */
};
self.info.statistics[name].last = tmp
self.info.statistics[name].push(info);
}
next_intf();
} else next_intf(err);
});
}, function (err) {
// when we're done
});
next_port();
} else next_port(err);
});
}, function (err) {
// when we're done
});
next_bridge();
} else next_bridge(err);
});
}, function (err) {
// when we're done
if (err) {
console.error(new Date().getTime(), ': fetch failed with ...', err, '...');
}
});
}, default_update_statistics_interval);
}
}
/**
* Retrieves updates from "MONITOR" messages
*
* @param clear
* do clean of updates
* @param callback
* call with results
*/
Client.prototype.retrieve_updates = function (count, callback) {
var updates = ((count == -1) ? this._updates : this._updates.slice(-count));
debug(JSON.stringify(updates));
if (callback) {
callback(updates);
}
};
Client.prototype.clear_updates = function (callback) {
this._updates.clear();
if (callback) {
callback();
}
};