1
0
Fork 0

Started implementing of highlevel js agency communication. Right now it is possible to change the target by adding/removing Database servers

This commit is contained in:
Michael Hackstein 2014-01-03 17:10:57 +01:00
parent ea2a93103a
commit ad99e44223
2 changed files with 719 additions and 0 deletions

View File

@ -0,0 +1,334 @@
/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true */
/*global module, require, exports */
////////////////////////////////////////////////////////////////////////////////
/// @brief Agency Communication
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- Agency-Communication
// -----------------------------------------------------------------------------
exports.Communication = function() {
var agency,
_AgencyWrapper,
cache,
splitServerName,
storeServersInCache,
storeServerAddressesInCache,
updateAddresses,
updatePlan,
updateTarget,
DBServers,
agencyRoutes,
Target,
_ = require("underscore");
_AgencyWrapper = function() {
var _agency = exports._createAgency();
var routes = {
vision: "Vision/",
target: "Target/",
plan: "Plan/",
current: "Current/",
fail: "Fail/"
};
var stubs = {
get: function(route, recursive) {
return _agency.get(route, recursive);
},
set: function(route, name, value) {
return _agency.set(route + "/" + name, value);
},
remove: function(route, name) {
return _agency.remove(route + "/" + name);
},
checkVersion: function(route) {
return false;
}
};
var addLevel = function(base, name, route, functions) {
var newRoute = base.route;
if (newRoute) {
newRoute += "/";
} else {
newRoute = "";
}
newRoute += route;
var newLevel = {
route: newRoute
};
_.each(functions, function(f) {
newLevel[f] = stubs[f].bind(null, newLevel.route);
});
base[name] = newLevel;
return newLevel;
};
var target = addLevel(this, "target", "Target");
addLevel(target, "dbServers", "DBServers", ["get", "set", "remove", "checkVersion"]);
var plan = addLevel(this, "plan", "Plan");
addLevel(plan, "dbServers", "DBServers", ["get"]);
//this.get = _agency.get;
//this.set = _agency.set;
}
agency = new _AgencyWrapper();
agencyRoutes = {
vision: "Vision/",
target: "Target/",
plan: "Plan/",
current: "Current/",
fail: "Fail/"
};
// -----------------------------------------------------------------------------
// --SECTION-- Update Wanted Configuration
// -----------------------------------------------------------------------------
splitServerName = function(route) {
var splits = route.split("/");
return splits[splits.length - 1];
};
storeServersInCache = function(place, servers) {
_.each(servers, function(v, k) {
var pName = splitServerName(k);
place[pName] = place[pName] || {};
place[pName].role = "primary";
if (v !== "none") {
place[v] = {
role: "secondary"
};
place[pName] = place[pName] || {};
place[pName].secondary = v;
}
});
};
storeServerAddressesInCache = function(servers) {
_.each(servers, function(v, k) {
var pName = splitServerName(k);
// Data Servers
if (cache.wanted[pName]) {
cache.wanted[pName].address = v;
}
if (cache.current[pName]) {
cache.current[pName].address = v;
}
// Coordinators
/*
if (cache.wanted[pName]) {
cache.wanted[pName].address = v;
}
*/
});
};
updateAddresses = function() {
if (cache.wanted && cache.current) {
var addresses = agency.get(agencyRoutes.current + "ServersRegistered", true);
storeServerAddressesInCache(addresses);
}
};
updateTarget = function() {
cache.target = {};
var servers = agency.target.dbServers.get(true);
storeServersInCache(cache.target, servers);
updateAddresses();
};
updatePlan = function(force) {
cache.plan = {};
var servers = agency.plan.dbServers.get(true);
storeServersInCache(cache.plan, servers);
updateAddresses();
};
// -----------------------------------------------------------------------------
// --SECTION-- Configuration Cache
// -----------------------------------------------------------------------------
cache = {
getTarget: function() {
if (!agency.target.dbServers.checkVersion()) {
updateTarget();
}
// TODO Add Update on version mismatch
return this.target;
},
getPlan: function() {
return this.plan;
}
};
//Fill Cache
updateTarget();
updatePlan();
// -----------------------------------------------------------------------------
// --SECTION-- Vision
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// --SECTION-- Target
// -----------------------------------------------------------------------------
Target = function() {
var DBServers;
this.DBServers = function() {
if (!DBServers) {
//Add DBServer specific functions
DBServers = {
getList: function() {
return cache.getTarget();
},
addPrimary: function(name) {
return agency.target.dbServers.set(name, "none");
},
addSecondary: function(name, primaryName) {
return agency.target.dbServers.set(primaryName, name);
},
addPair: function(primaryName, secondaryName) {
return agency.target.dbServers.set(primaryName, secondaryName);
},
removeServer: function(name) {
var res = -1;
_.each(cache.getTarget(), function(opts, n) {
if (n === name) {
// The removed server is a primary
if (opts.role === "primary") {
res = agency.target.dbServers.remove(name);
if (!res) {
res = -1;
return;
}
if (opts.secondary !== "none") {
res = agency.target.dbServers.set(opts.secondary, "none");
}
return;
}
}
if (opts.role === "primary" && opts.secondary === name) {
res = agency.target.dbServers.set(n, "none");
return;
}
});
if (res === -1) {
//TODO Debug info
require("internal").print("Trying to remove a server that is not known");
}
return res;
}
};
}
return DBServers;
};
};
// -----------------------------------------------------------------------------
// --SECTION-- Servers
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_agency-communication_servers_general
/// @brief A generic communication interface for lists of servers
///
/// @FUN{new Servers(@FA{route})}
///
/// This creates a new Interface to the agency to handle a set of servers.
/// The first argument is the subroute where the configuration of this type of
/// servers is stored.
///
/// @EXAMPLES
///
/// @code
/// list = new Servers("DBServers");
/// @endcode
////////////////////////////////////////////////////////////////////////////////
this.DBServers = function() {
if (!DBServers) {
//Add DBServer specific functions
DBServers = {
list: function() {
return cache.getPlan();
},
add: function() {},
set: function() {},
get: function() {},
remove: function() {}
};
}
return DBServers;
};
// -----------------------------------------------------------------------------
// --SECTION-- Global Object binding
// -----------------------------------------------------------------------------
this.target = new Target();
}
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_agency-communication_agency
/// @brief A wrapper around the Agency initialisation
///
/// @FUN{_createAgency()}
///
/// This returns a singleton instance for the agency or creates it.
///
/// @EXAMPLES
///
/// @code
/// agency = communication._createAgency();
/// @endcode
////////////////////////////////////////////////////////////////////////////////
exports._createAgency = function() {
var agency;
if (agency) {
return agency;
}
agency = new ArangoAgency();
return agency;
};
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
/// Local Variables:
/// mode: outline-minor
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
/// End:

View File

@ -0,0 +1,385 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief test the agency communication layer
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var internal = require("internal");
var _ = require("underscore");
// -----------------------------------------------------------------------------
// --SECTION-- agency-result-helper
// -----------------------------------------------------------------------------
var createResult = function(prefix, list) {
var res = {};
_.each(list, function(v, k) {
res[prefix + k] = v;
});
return res;
};
// -----------------------------------------------------------------------------
// --SECTION-- good-case
// -----------------------------------------------------------------------------
function runGoodCaseTests(test) {
var Communication = require("org/arangodb/sharding/agency-communication");
var configResult;
var tmpConfigResult;
var registeredResult;
var agencyMock;
var resetToDefault;
var comm;
var agencyRoutes;
var setup;
var teardown;
var agencyTargetServers;
var plannedServers;
var registered;
agencyRoutes = {
vision: "Vision/",
target: "Target/",
plan: "Plan/",
current: "Current/",
fail: "Fail/"
};
resetToDefault = function() {
agencyTargetServers = createResult(agencyRoutes.target + "DBServers/", {
"pavel": "sandro",
"paul": "sally",
"patricia": "sandra"
});
plannedServers = createResult(agencyRoutes.plan + "DBServers/", {
"pavel": "sandro",
"paul": "sally",
"patricia": "sandra"
});
registered = createResult(agencyRoutes.current + "ServersRegistered/", {
"pavel": "tcp://192.168.0.1:8529",
"paul": "tcp://192.168.0.2:8529",
"patricia": "tcp://192.168.0.3:8529",
"sandro": "tcp://192.168.0.4:8529",
"sally": "tcp://192.168.0.5:8529",
"sandra": "tcp://192.168.0.6:8529"
});
};
setup = function() {
resetToDefault();
};
teardown = function() {
};
agencyMock = {
get: function(route, recursive) {
if (route === agencyRoutes.target + "DBServers" && recursive) {
return agencyTargetServers;
}
if (route === agencyRoutes.plan + "DBServers" && recursive) {
return plannedServers;
}
if (route === agencyRoutes.current + "ServersRegistered" && recursive) {
return registered;
}
fail();
}
};
Communication._createAgency = function() {
return agencyMock;
};
comm = new Communication.Communication();
function VisionSuite() {
return {
setUp: setup,
tearDown: teardown,
testGetVision: function() {
var expectedResult = {
};
assertTrue(false);
}
};
};
function TargetDBServersSuite() {
var targetServers;
return {
setUp: function() {
setup();
targetServers = comm.target.DBServers();
},
tearDown: teardown,
testGetServerList: function() {
var expected = {
pavel: {
role: "primary",
secondary: "sandro"
},
paul: {
role: "primary",
secondary: "sally"
},
patricia: {
role: "primary",
secondary: "sandra"
},
sandro: {
role: "secondary"
},
sally: {
role: "secondary"
},
sandra: {
role: "secondary"
}
};
var res = targetServers.getList();
assertEqual(res, expected);
},
testAddNewPrimaryServer: function() {
var name = "pancho";
var wasCalled = false;
agencyMock.set = function(route, value) {
assertEqual(route, agencyRoutes.target + "DBServers/" + name);
assertEqual(value, "none");
agencyTargetServers[route] = value;
wasCalled = true;
return true;
};
assertTrue(targetServers.addPrimary(name), "Failed to insert a new primary");
assertTrue(wasCalled, "Agency has not been informed to insert primary.");
var newList = targetServers.getList();
assertNotUndefined(newList[name]);
assertEqual(newList[name].role, "primary");
assertUndefined(newList[name].secondary);
},
testAddNewSecondaryServer: function() {
var name = "pancho";
var secName = "samuel";
var wasCalled = false;
agencyMock.set = function(route, value) {
assertEqual(route, agencyRoutes.target + "DBServers/" + name);
assertEqual(value, "none");
assertFalse(wasCalled, "Set has been called multiple times");
agencyTargetServers[route] = value;
wasCalled = true;
return true;
};
assertTrue(targetServers.addPrimary(name), "Failed to insert a new primary");
assertTrue(wasCalled, "Agency has not been informed to insert primary.");
wasCalled = false;
agencyMock.set = function(route, value) {
assertEqual(route, agencyRoutes.target + "DBServers/" + name);
assertEqual(value, secName);
assertFalse(wasCalled, "Set has been called multiple times");
agencyTargetServers[route] = value;
wasCalled = true;
return true;
};
assertTrue(targetServers.addSecondary(secName, name), "Failed to insert a new secondary");
assertTrue(wasCalled, "Agency has not been informed to insert secondary.");
var newList = targetServers.getList();
assertNotUndefined(newList[name]);
assertEqual(newList[name].role, "primary");
assertEqual(newList[name].secondary, secName);
assertNotUndefined(newList[secName]);
assertEqual(newList[secName].role, "secondary");
},
testAddNewServerPair: function() {
var name = "pancho";
var secName = "samuel";
var wasCalled = false;
agencyMock.set = function(route, value) {
assertEqual(route, agencyRoutes.target + "DBServers/" + name);
assertEqual(value, secName);
assertFalse(wasCalled, "Set has been called multiple times");
agencyTargetServers[route] = value;
wasCalled = true;
return true;
};
assertTrue(targetServers.addPair(name, secName), "Failed to insert a new primary/secondary pair");
assertTrue(wasCalled, "Agency has not been informed to insert the new pair.");
var newList = targetServers.getList();
assertNotUndefined(newList[name]);
assertEqual(newList[name].role, "primary");
assertEqual(newList[name].secondary, secName);
assertNotUndefined(newList[secName]);
assertEqual(newList[secName].role, "secondary");
},
testRemovePrimaryServer: function() {
var name = "pavel";
var secondaryName = "sandro";
var setWasCalled = false;
agencyMock.set = function(route, value) {
assertEqual(route, agencyRoutes.target + "DBServers/" + secondaryName);
assertEqual(value, "none");
assertFalse(setWasCalled, "Set has been called multiple times");
agencyTargetServers[route] = value;
setWasCalled = true;
return true;
};
var delWasCalled = false;
agencyMock.remove = function(route) {
assertEqual(route, agencyRoutes.target + "DBServers/" + name);
assertFalse(delWasCalled, "Delete has been called multiple times");
delete agencyTargetServers[route];
delWasCalled = true;
return true;
};
assertTrue(targetServers.removeServer(name), "Failed to remove a primary server");
assertTrue(setWasCalled, "Agency has not been informed to replace the primary with the secondary.");
assertTrue(delWasCalled, "Agency has not been informed to remove the primary/secondary pair.");
var newList = targetServers.getList();
assertUndefined(newList[name]);
assertNotUndefined(newList[secondaryName]);
assertEqual(newList[secondaryName].role, "primary");
assertUndefined(newList[secondaryName].secondary);
},
testRemoveSecondaryServer: function() {
var name = "sandro";
var pName = "pavel";
var wasCalled = false;
agencyMock.set = function(route, value) {
assertEqual(route, agencyRoutes.target + "DBServers/" + pName);
assertEqual(value, "none");
assertFalse(wasCalled, "Set has been called multiple times");
agencyTargetServers[route] = value;
wasCalled = true;
return true;
};
assertTrue(targetServers.removeServer(name), "Failed to remove a secondary server.");
assertTrue(wasCalled, "Agency has not been informed to update the primary server.");
var newList = targetServers.getList();
assertUndefined(newList[name]);
assertNotUndefined(newList[pName]);
assertEqual(newList[pName].role, "primary");
assertUndefined(newList[pName].secondary);
}
};
};
function TargetShardSuite() {
return {
setUp: setup,
tearDown: teardown,
testGetDatabaseList: function() {
assertTrue(true);
}
}
};
function DBServersSuite() {
var DBServers;
return {
setUp: function() {
DBServers = comm.DBServers();
},
tearDown: function() {},
testGetList: function() {
var expectedResult = {
pavel: {
address: "tcp://192.168.0.1:8529",
role: "primary",
secondary: "sandro"
},
paul: {
address: "tcp://192.168.0.2:8529",
role: "primary",
secondary: "sally"
},
patricia: {
address: "tcp://192.168.0.3:8529",
role: "primary",
secondary: "sandra"
},
sandro: {
address: "tcp://192.168.0.4:8529",
role: "secondary"
},
sally: {
address: "tcp://192.168.0.5:8529",
role: "secondary"
},
sandra: {
address: "tcp://192.168.0.6:8529",
role: "secondary"
}
};
var res = DBServers.list();
assertEqual(expectedResult, res);
}
};
};
// test.run(VisionSuite);
test.run(TargetDBServersSuite);
test.run(TargetShardSuite);
// test.run(DBServersSuite);
};
// -----------------------------------------------------------------------------
// --SECTION-- main
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suites
////////////////////////////////////////////////////////////////////////////////
runGoodCaseTests(jsunity);
return jsunity.done();
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End: