diff --git a/js/server/modules/org/arangodb/cluster/kickstarter.js b/js/server/modules/org/arangodb/cluster/kickstarter.js index 7d583c67c1..9515302707 100644 --- a/js/server/modules/org/arangodb/cluster/kickstarter.js +++ b/js/server/modules/org/arangodb/cluster/kickstarter.js @@ -2,9 +2,9 @@ /*global module, require, exports, ArangoAgency, SYS_TEST_PORT */ //////////////////////////////////////////////////////////////////////////////// -/// @brief Cluster startup functionality by dispatchers +/// @brief Cluster kickstarting functionality using dispatchers /// -/// @file js/server/modules/org/arangodb/cluster/dispatcher.js +/// @file js/server/modules/org/arangodb/cluster/kickstarter.js /// /// DISCLAIMER /// @@ -29,7 +29,7 @@ //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- -// --SECTION-- Dispatcher functionality +// --SECTION-- Kickstarter functionality // ----------------------------------------------------------------------------- var download = require("internal").download; @@ -37,6 +37,8 @@ var executeExternal = require("internal").executeExternal; var fs = require("fs"); var wait = require("internal").wait; +var exchangePort = require("org/arangodb/cluster/planner").exchangePort; + var print = require("internal").print; var actions = {}; @@ -58,17 +60,32 @@ function getAddr (endpoint) { return addrPort; } +function getPort (endpoint) { + var pos = endpoint.lastIndexOf(":"); + if (pos !== -1) { + return parseInt(endpoint.substr(pos+1),10); + } + return 8529; +} + actions.startAgent = function (dispatchers, cmd) { var agentDataDir = fs.join(cmd.dataPath, "agent"+cmd.agencyPrefix+cmd.extPort); if (fs.exists(agentDataDir)) { fs.removeDirectoryRecursive(agentDataDir,true); } - // FIXME: could distinguish cases and sometimes only bind to 127.0.0.1??? var args = ["-data-dir", agentDataDir, "-name", "agent"+cmd.agencyPrefix+cmd.extPort, - "-addr", "0.0.0.0:"+cmd.extPort, - "-peer-addr", "0.0.0.0:"+cmd.intPort]; + "-bind-addr", (cmd.onlyLocalhost ? "127.0.0.1:" + : "0.0.0.0:")+cmd.extPort, + "-addr", getAddrPort( + exchangePort(dispatchers[cmd.dispatcher].endpoint, + cmd.extPort)), + "-peer-bind-addr", (cmd.onlyLocalhost ? "127.0.0.1:" + : "0.0.0.0:")+cmd.intPort, + "-peer-addr", getAddrPort( + exchangePort(dispatchers[cmd.dispatcher].endpoint, + cmd.intPort)) ]; var i; if (cmd.peers.length > 0) { args.push("-peers"); @@ -78,33 +95,157 @@ actions.startAgent = function (dispatchers, cmd) { } args.push(getAddrPort(cmd.peers[0])); } - print("Starting agent: command: ",cmd.agentPath); - for (i = 0;i < args.length;i++) { - print(args[i]); - } var pid = executeExternal(cmd.agentPath, args); - wait(3); // Wait a bit, such that the next one will be able to connect - return {"error":false, "pid": pid}; + var res; + while (true) { + wait(0.5); // Wait a bit to give it time to startup + res = download("http://localhost:"+cmd.extPort+"/v2/keys/"); + if (res.code === 200) { + return {"error":false, "isAgent": true, "pid": pid}; + } + } }; +function encode (st) { + var st2 = ""; + var i; + for (i = 0; i < st.length; i++) { + if (st[i] === "_") { + st2 += "@U"; + } + else if (st[i] === "@") { + st2 += "@@"; + } + else { + st2 += st[i]; + } + } + return encodeURIComponent(st2); +} + +function sendToAgency (agencyURL, path, obj) { + var res; + var body; + print("Sending ",path," to agency..."); + if (typeof obj === "string") { + var count = 0; + while (count++ <= 2) { + body = "value="+encodeURIComponent(obj); + //print("sending:",agencyURL+path,"\nwith body",body); + res = download(agencyURL+path,body, + {"method":"PUT", "followRedirects": true, + "headers": { "Content-Type": "application/x-www-form-urlencoded"}}); + //print("Code ", res.code); + if (res.code === 201) { + return true; + } + } + return res; + } + if (typeof obj !== "object") { + return "Strange object found: not a string or object"; + } + var keys = Object.keys(obj); + var i; + if (keys.length !== 0) { + for (i = 0; i < keys.length; i++) { + res = sendToAgency (agencyURL, path+"/"+encode(keys[i]), obj[keys[i]]); + if (res !== true) { + return res; + } + } + return true; + } + // Create a directory + var count2 = 0; + while (count2++ <= 2) { + body = "dir=true"; + res = download(agencyURL+path,body, + {"method": "PUT", "followRedirects": true, + "headers": { "Content-Type": "application/x-www-form-urlencoded"}}); + if (res.code === 201) { + return true; + } + } + return res; +} + actions.sendConfiguration = function (dispatchers, cmd) { - print("Sending configuration..."); - return {"error":false}; + var url = "http://"+getAddrPort(cmd.agency.endpoints[0])+"/v2/keys"; + var res = sendToAgency(url, "", cmd.data); + if (res === true) { + return {"error":false, "isAgencyConfiguration": true}; + } + return {"error":true, "isAgencyConfiguration": true, "suberror": res}; }; actions.startLauncher = function (dispatchers, cmd) { - print("Starting launcher..."); - return {"error":false}; + var url = "http://"+getAddrPort(cmd.agency.endpoints[0])+"/v2/keys/"+ + cmd.agency.agencyPrefix+"/"; + print("Downloading ",url+"Launchers/"+cmd.name); + var res = download(url+"Launchers/"+cmd.name,"",{method:"GET", + followRedirects:true}); + if (res.code !== 200) { + return {"error": true, "isStartLauncher": true, "suberror": res}; + } + var body = JSON.parse( res.body ); + var info = JSON.parse(body.node.value); + var id,ep,args,pids,port; + + print("Starting servers..."); + var i; + print(info); + var servers = info.DBservers.concat(info.Coordinators); + pids = []; + for (i = 0; i < servers.length; i++) { + id = servers[i]; + print("Downloading ",url+"Target/MapIDToEndpoint/"+id); + res = download(url+"Target/MapIDToEndpoint/"+id); + if (res.code !== 200) { + return {"error": true, "pids": pids, + "isStartLauncher": true, "suberror": res}; + } + print("Starting server ",id); + body = JSON.parse(res.body); + ep = JSON.parse(body.node.value); + port = getPort(ep); + args = ["--cluster.my-id", id, + "--cluster.agency-prefix", cmd.agency.agencyPrefix, + "--cluster.agency-endpoint", cmd.agency.endpoints[0], + "--server.endpoint"]; + if (cmd.onlyLocalhost) { + args.push("tcp://127.0.0.1:"+port); + } + else { + args.push("tcp://0.0.0.0:"+port); + } + args.push("--log.file"); + var logfile = fs.join(cmd.dataPath,"log-"+cmd.agency.agencyPrefix+"-"+id); + if (fs.exists(logfile)) { + fs.remove(logfile); + } + args.push(logfile); + var datadir = fs.join(cmd.dataPath,"data-"+cmd.agency.agencyPrefix+"-"+id); + if (fs.exists(datadir)) { + fs.removeDirectoryRecursive(datadir,true); + } + fs.makeDirectory(datadir); + args.push(datadir); + pids.push(executeExternal(cmd.arangodPath, args)); + } + return {"error": false, "pids": pids}; }; -function dispatch (startupPlan) { - var myname; +function Kickstarter (startupPlan) { + this.startupPlan = startupPlan; if (!startupPlan.hasOwnProperty("myname")) { - myname = "me"; - } - else { - myname = startupPlan.myname; + startupPlan.myname = "me"; } +} + +Kickstarter.prototype.launch = function () { + var startupPlan = this.startupPlan; + var myname = startupPlan.myname; var dispatchers = startupPlan.dispatchers; var cmds = startupPlan.commands; var results = []; @@ -143,14 +284,14 @@ function dispatch (startupPlan) { } } if (error) { - return {"error": true, "errorMessage": "some error during dispatch", + return {"error": true, "errorMessage": "some error during launch", "results": results}; } return {"error": false, "errorMessage": "none", "results": results}; -} +}; -exports.dispatch = dispatch; +exports.Kickstarter = Kickstarter; // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE diff --git a/js/server/modules/org/arangodb/cluster/planner.js b/js/server/modules/org/arangodb/cluster/planner.js index e5b6a9fca1..145e2d961d 100644 --- a/js/server/modules/org/arangodb/cluster/planner.js +++ b/js/server/modules/org/arangodb/cluster/planner.js @@ -2,9 +2,9 @@ /*global module, require, exports, ArangoAgency, SYS_TEST_PORT */ //////////////////////////////////////////////////////////////////////////////// -/// @brief Cluster kickstart functionality +/// @brief Cluster planning functionality /// -/// @file js/server/modules/org/arangodb/cluster/kickstarter.js +/// @file js/server/modules/org/arangodb/cluster/planner.js /// /// DISCLAIMER /// @@ -29,7 +29,7 @@ //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- -// --SECTION-- Kickstarter functionality +// --SECTION-- Planner functionality // ----------------------------------------------------------------------------- // possible config attributes (for defaults see below): @@ -46,8 +46,8 @@ // all the data directories of agents or servers // live, this can be relative or even empty, which // is equivalent to "./", it will be made into -// an absolute path by the kickstarter, using -// the current directory when the kickstarter +// an absolute path by the planner, using +// the current directory when the planner // runs, use with caution! // .logPath path where the log files are written, same // comments as for .dataPath apply @@ -59,15 +59,15 @@ // .arangodPath path to the arangod executable on // all machines in the cluster, will be made // absolute (if it is not already absolute) -// in the process running the kickstarter +// in the process running the planner // .agentPath path to the agent executable on // all machines in the cluster, will be made // absolute (if it is not already absolute) -// in the process running the kickstarter +// in the process running the planner // some port lists: // for these the following rules apply: // every list overwrites the default list -// when running out of numbers the kickstarter increments the last one +// when running out of numbers the planner increments the last one // used by one for every port needed // // .agentExtPorts a list port numbers to use for the @@ -79,11 +79,10 @@ // var fs = require("fs"); -var dispatch = require("org/arangodb/cluster/dispatcher").dispatch; // Our default configurations: -var KickstarterLocalDefaults = { +var PlannerLocalDefaults = { "agencyPrefix" : "meier", "numberOfAgents" : 3, "numberOfDBservers" : 2, @@ -105,7 +104,7 @@ var KickstarterLocalDefaults = { "avoidPorts": {}}} }; -var KickstarterDistributedDefaults = { +var PlannerDistributedDefaults = { "agencyPrefix" : "mueller", "numberOfAgents" : 3, "numberOfDBservers" : 3, @@ -238,22 +237,22 @@ function fillConfigWithDefaults (config, defaultConfig) { } } -// Our Kickstarter class: +// Our Planner class: -function Kickstarter (userConfig) { +function Planner (userConfig) { "use strict"; if (typeof userConfig !== "object") { throw "userConfig must be an object"; } var defaultConfig = userConfig.defaultConfig; if (defaultConfig === undefined) { - defaultConfig = KickstarterLocalDefaults; + defaultConfig = PlannerLocalDefaults; } if (defaultConfig === "local") { - defaultConfig = KickstarterLocalDefaults; + defaultConfig = PlannerLocalDefaults; } else if (defaultConfig === "distributed") { - defaultConfig = KickstarterDistributedDefaults; + defaultConfig = PlannerDistributedDefaults; } this.config = copy(userConfig); fillConfigWithDefaults(this.config, defaultConfig); @@ -267,7 +266,7 @@ function Kickstarter (userConfig) { this.makePlan(); } -Kickstarter.prototype.makePlan = function() { +Planner.prototype.makePlan = function() { // This sets up the plan for the cluster according to the options var config = this.config; @@ -277,6 +276,15 @@ Kickstarter.prototype.makePlan = function() { if (Object.keys(dispatchers).length === 0) { dispatchers.me = { "id": "me", "endpoint": "tcp://localhost:", "avoidPorts": {} }; + config.onlyLocalhost = true; + } + else { + config.onlyLocalhost = false; + var k = Object.keys(dispatchers); + if (k.length === 1 && + dispatchers[k[0]].endpoint.substr(0,16) === "tcp://localhost:") { + config.onlyLocalhost = true; + } } var dispList = Object.keys(dispatchers); @@ -364,30 +372,18 @@ Kickstarter.prototype.makePlan = function() { var s; for (i = 0; i < DBservers.length; i++) { s = DBservers[i]; - if (dispatchers[s.dispatcher].endpoint === "tcp://localhost:") { - dbs[s.id] = map[s.id] - = '"'+exchangePort("tcp://127.0.0.1:0",s.port)+'"'; - } - else { - dbs[s.id] = map[s.id] - = '"'+exchangePort(dispatchers[s.dispatcher].endpoint,s.port)+'"'; - } + dbs[s.id] = map[s.id] + = '"'+exchangePort(dispatchers[s.dispatcher].endpoint,s.port)+'"'; launchers[s.dispatcher].DBservers.push(s.id); } var coo = tmp.Coordinators = {}; for (i = 0; i < coordinators.length; i++) { s = coordinators[i]; - if (dispatchers[s.dispatcher].endpoint === "tcp://localhost:") { - coo[s.id] = map[s.id] - = '"'+exchangePort("tcp://127.0.0.1:0",s.port)+'"'; - } - else { - coo[s.id] = map[s.id] - = '"'+exchangePort(dispatchers[s.dispatcher].endpoint,s.port)+'"'; - } + coo[s.id] = map[s.id] + = '"'+exchangePort(dispatchers[s.dispatcher].endpoint,s.port)+'"'; launchers[s.dispatcher].Coordinators.push(s.id); } - tmp.Databases = { "_system" : {} }; + tmp.Databases = { "_system" : '{"name":"_system"}' }; tmp.Collections = { "_system" : {} }; // Now Plan: @@ -429,14 +425,15 @@ Kickstarter.prototype.makePlan = function() { "agencyPrefix": config.agencyPrefix, "dataPath": config.dataPath, "logPath": config.logPath, - "agentPath": config.agentPath }; + "agentPath": config.agentPath, + "onlyLocalhost": config.onlyLocalhost }; for (j = 0; j < i; j++) { var ep = dispatchers[agents[j].dispatcher].endpoint; tmp2.peers.push( exchangePort( ep, agents[j].intPort ) ); } tmp.push(tmp2); } - var agencyPos = { "prefix": config.agencyPrefix, + var agencyPos = { "agencyPrefix": config.agencyPrefix, "endpoints": agents.map(function(a) { return exchangePort(dispatchers[a.dispatcher].endpoint, a.extPort);}) }; @@ -449,48 +446,40 @@ Kickstarter.prototype.makePlan = function() { "dataPath": config.dataPath, "logPath": config.logPath, "arangodPath": config.arangodPath, + "onlyLocalhost": config.onlyLocalhost, "agency": copy(agencyPos) } ); } this.myname = "me"; }; -Kickstarter.prototype.checkDispatchers = function() { +Planner.prototype.checkDispatchers = function() { // Checks that all dispatchers are active, if none is configured, // a local one is started. throw "not yet implemented"; }; -Kickstarter.prototype.getStartupProgram = function() { +Planner.prototype.getStartupProgram = function() { // Computes the dispatcher commands and returns them as JSON return { "dispatchers": this.dispatchers, "commands": this.commands }; }; -Kickstarter.prototype.launch = function() { +Planner.prototype.launch = function() { // Starts the cluster according to startup plan - dispatch(this); + //dispatch(this); }; -Kickstarter.prototype.shutdown = function() { +Planner.prototype.shutdown = function() { throw "not yet implemented"; }; -Kickstarter.prototype.isHealthy = function() { +Planner.prototype.isHealthy = function() { throw "not yet implemented"; }; exports.PortFinder = PortFinder; -exports.Kickstarter = Kickstarter; - -// TODO for kickstarting: -// -// * finden des Pfads zum eigenen Executable in JS -// * etcd in distribution -// * JS function zum Ausführen von Startup-Programmen (lokal u. delegation) -// * REST interface zum Ausführen von Startup-Programmen -// * arangod-Rolle "Launcher" per Kommandozeile -// * REST interface to SYS_TEST_PORT, ev. mit auth -// * Dokumentation +exports.Planner = Planner; +exports.exchangePort = exchangePort; // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE