/** ** ============================== ** O O O OOOO ** O O O O O O ** O O O O O O ** OOOO OOOO O OOO OOOO ** O O O O O O O ** O O O O O O O ** OOOO OOOO O O OOOO ** ============================== ** Dr. Stefan Bosse http://www.bsslab.de ** ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED ** BY THE AUTHOR(S). ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, ** MODIFIED, OR OTHERWISE USED IN A CONTEXT ** OUTSIDE OF THE SOFTWARE SYSTEM. ** ** $AUTHORS: Stefan Bosse ** $INITIAL: (C) 2006-2016 bLAB ** $CREATED: sbosse on 23-5-16. ** $VERSION: 1.3.5 ** ** $INFO: * * Boot Service that is a monolithic application embeddeding all serviced programs. * ** $ENDOFINFO */ var Io = Require('com/io'); var Comp = Require('com/compat'); var Sch = Require('dos/scheduler'); var Net = Require('dos/network'); var Conn = Require('dos/connection'); var My = {domain:'default',host:'none'}; var Operation = { CLEAR:'clear', DISABLE:'disable', ENABLE:'enable', EXECUTE:'exec', INFO:'info', INIT:'init', MAKEAPP:'makeapp', REFRESH:'refresh', START:'start', STOP:'stop' } var State = { OFFLINE:'offline', ONLINE:'online', UNINITIALIZED:'uninitialized', MAINTENANCE:'maintenance', DISABLED:'disabled', TEST:'test', WAIT:'wait' } var options = { app : '*', args : [], bip:'localhost', boot_ops : [], bootst : 'boot.state', bootsf : 'boot.conf', bport:3001, broker: true, dip : 'localhost', http:false, keepalive:true, monitor:0, prog : process.argv[1]||'?', tcpnet:1, verbose:0, vm : process.argv[0]||'node' } Comp.args.parse(process.argv,[ [[ Operation.CLEAR, Operation.DISABLE, Operation.ENABLE, Operation.INIT, Operation.INFO, Operation.MAKEAPP, Operation.REFRESH, Operation.START, Operation.STOP ], 0,function (val) { options.boot_ops.push(val) }], [Operation.EXECUTE,'*',function (vals) { options.boot_ops.push(Operation.EXECUTE); options.app=Comp.array.head(vals); options.args=Comp.array.tail(vals); }], ['-conf',1,function (val) {options.bootsf=val}], ['-state',1,function (val) {options.bootst=val}], ['-broker',1,function(val){ var tokens = Comp.string.split(':',val); if (tokens.length==1) options.bip=val; else { options.bip=tokens[0]; options.bport=Perv.int_of_string(tokens[1]) } }], ['-dip',1,function(val){options.dip=val}], ['-D',1,function(val){options.dports.push(Perv.int_of_string(val))}], ['-L',2,function(val1,val2){options.links.push([Perv.int_of_string(val1),getip(val2),getipport(val2)])}], ['-nokeepalive',0,function(val){options.keepalive=false;}], ['-monitor',0,function() {options.monitor++;}], [['-v','-verbose'],0,function() {options.verbose++;}], ['-T',0,function(val){options.tcpnet=1;options.http=false;}], ['-T2',0,function(val){options.tcpnet=2;options.http=false;}], ['-H',0,function(val){options.http=true;options.tcpnet=0;}], function (val) { options.app=val; } ],2); var verbose = !Comp.array.contains(options.boot_ops,Operation.EXECUTE); var makeapp = Comp.array.contains(options.boot_ops,Operation.MAKEAPP); var refresh = Comp.array.contains(options.boot_ops,Operation.REFRESH); var start = Comp.array.contains(options.boot_ops,Operation.START); options.privhostport = Net.uniqport(); options.pubhostport = Net.prv2pub(options.privhostport); // console.log(process.argv) if (Comp.array.empty(options.boot_ops)) { Io.out('usage: boot [-verbose -v -monitor -H -T -T2 -nokeepalive] '); Io.out(' [clear disable enable init makeapp refresh(reconf) start stop]'); Io.out(' [-conf ] (default '+options.bootsf+')'); Io.out(' [-state ] (default '+options.bootst+')'); Io.out(' []'); Io.out(' [exec args...]'); process.exit(-1); } else if (Comp.array.contains(options.boot_ops,Operation.EXECUTE)) { Io.out('[BOOT] Executing '+options.app+' '+Comp.printf.list(options.args,_,' ')+' ..'); process.argv=Comp.array.concat(['node',options.app],options.args); Require(options.app) return; } /** Main watchdog service loop. * 1. Poll servers and records status * 2. Stops or restarts services * */ var watchdog = function (serv) { var self=this; this.serv = serv; this.cap = null; this.down = false; this.dying = false; this.delay = serv.polltime||1000; // Poll delay this.maxtimeout = 15000; // Maximal time between a successfull repsonse and failures this.maxfailed = 5; // Maximal number of failures before state is changed to maintenance this.maxrestart = 3; // Maximal number of failed restarts this.init = function () { Io.out('[BOOT] Polling service '+serv.app+' with PID='+serv.process.pid+' with '+serv.pollcap); serv.status = Net.Status.STD_UNKNOWN; serv.timestamp = Io.time(); serv.restart = 0; serv.failed = 0; if (Io.exists(serv.pollcap)) { this.cap = Net.cap_of_file(serv.pollcap); if (this.cap) { Io.out('[BOOT] Using poll capability '+Net.Print.capability(this.cap)+' for '+serv.app); serv.status = Net.Status.STD_OK; } else Io.out('[BOOT] Invalid capability in '+serv.pollcap); } else Io.out('[BOOT] Not found: '+serv.pollcap); }; this.poll = function () { var thr=this; if (!this.cap) return; switch (serv.state) { case State.ONLINE: case State.MAINTENANCE: //print('pollcap: '+Net.Print.capability(this.cap)); Services.network.std.std_info(this.cap, function (stat) { //print('... pollcap: '+Net.Print.capability(self.cap)); serv.status=stat; if (serv.status==Net.Status.STD_OK) serv.timestamp=Io.time(); else { serv.failed++; Io.out('[BOOT] Polling failed for '+serv.app+': '+Net.Print.status(serv.status)); } }); break; } }; this.service = function () { var state=serv.state,t; switch (serv.state) { case State.ONLINE: if ((Io.time()-serv.timestamp) > this.maxtimeout || serv.failed > this.maxfailed) { serv.state=State.MAINTENANCE; Io.out('[BOOT] '+serv.app+': '+state+' -> '+serv.state); } break; case State.MAINTENANCE: Io.out('[BOOT] Stopping '+serv.app); serv.stop(); serv.state=State.OFFLINE; this.cap=undefined; break; case State.OFFLINE: if (serv.restart '+serv.state); serv.timestamp = Io.time(); serv.failed = 0; } else if (serv.restart>=this.maxrestart) { serv.state=State.DISABLED; Io.out('[BOOT] '+serv.app+' too often failed: '+state+' -> '+serv.state); } break; } serv.status = Net.Status.STD_UNKNOWN; }; this.sleep = function () { Delay(this.delay); if (!this.cap && serv.state==State.ONLINE && Io.exists(serv.pollcap)) { this.cap = Net.cap_of_file(serv.pollcap); if (this.cap) { Io.out('[BOOT] Using poll capability '+Net.Print.capability(this.cap)+' for '+serv.app); serv.status = Net.Status.STD_OK; } else Io.out('[BOOT] Invalid capability in '+serv.pollcap); }; }; this.terminate = function () {}; this.transitions = [ [undefined,this.init,function () {return serv.state==State.ONLINE}], [this.init,this.sleep], [this.poll,this.service, function (thr) { return serv.status == Net.Status.RPC_FAILURE }], [this.poll,this.sleep, function (thr) { return serv.status != Net.Status.RPC_FAILURE }], [this.service,this.sleep], [this.sleep,this.poll, function () { return !this.dying && serv.state!=State.OFFLINE}], [this.sleep,this.service, function () { return !this.dying && serv.state==State.OFFLINE}], [this.sleep,this.terminate, function () {return this.dying}] ]; this.on = { }; } // console.log(boot_ops) /*************** ** SERVICES ***************/ var services = function (options) { this.options=options; this.services=[]; }; services.prototype.add = function (options) { if (!options.trans || !options.app) Io.fail('[BOOT] Invalid service object '+Io.inspect(options)); var serv=Service(options); this.services.push(serv); serv.services=this; Io.out('[BOOT] Adding service '+serv.info()); } services.prototype.exec = function (op) { Comp.array.iter(this.services,function (s) { var state=s.state,t,ready; if ((Comp.string.contains(s.app,options.app) || options.app=='*')) { switch (op) { case Operation.INFO: Io.out('[BOOT] '+s.app+' State='+s.state); break; case Operation.MAKEAPP: s.state=State.TEST; Io.out('[BOOT] '+s.app+' doing '+op); t=s.start(); if (t && t.wait) Io.sleep(t.wait); s.state=State.UNINITIALIZED; break; case Operation.INIT: case Operation.START: if (s.state==State.ONLINE) return; if (op==Operation.START && s.state==State.MAINTENANCE) return; if (op==Operation.INIT && s.state!=State.UNINITIALIZED) return; // if (s.state==State.DISABLED) state=s.state=State.OFFLINE; ready=s.check(); if (ready) { Io.out('[BOOT] '+s.app+' doing '+op); s.start(); } else { Io.out('[BOOT] Warning: '+s.app+' has missing dependencie(s), cannot start!'); s.state==State.OFFLINE; } break; case Operation.STOP: case Operation.DISABLE: if (s.state==State.ONLINE) { Io.out('[BOOT] Stopping '+s.app); s.stop(); } if (op==Operation.DISABLE) { s.state=State.DISABLED; Io.out('[BOOT] '+s.app+' State '+state+' -> '+s.state); } break; case Operation.ENABLE: s.state=State.OFFLINE; Io.out('[BOOT] '+s.app+' State '+state+' -> '+s.state); break; case Operation.CLEAR: if (s.state==State.ONLINE) s.state=State.OFFLINE; if (s.state==State.OFFLINE) s.state=State.UNINITIALIZED; Io.out('[BOOT] '+s.app+' State '+state+' -> '+s.state); break; } } }); } services.prototype.find = function (app) { return Comp.array.find(this.services, function (s) { return s.app==app; }); } services.prototype.read = function (file) { try { Io.out('[BOOT] Reading service file '+file+' ..'); var text=Io.read_file(file); if (text==undefined) throw 'No such file'; services=eval(text); if (Comp.obj.isArray(services)) Comp.array.iter(services,this.add.bind(this)); } catch (e) { Io.out('[BOOT] Reading or compiling of configuration file '+file+' failed: '+e); return; } }; /*************** ** SERVICE ***************/ var service = function (options) { this.state=options.state||State.UNINITIALIZED; this.trans=options.trans; this.app=options.app; this.depends=options.depends; this.poll=options.poll||true; this.pollcap=options.pollcap; this.polltime=options.polltime; } var Service = function (options) { return new service(options); } /** Check dependencies. Return true if service is ready to be scheduled. * */ service.prototype.check = function () { var self=this,state=this.state,ready=true,S=this.services; if (!this.depends) return true; Comp.array.iter(this.depends,function (dep) { var serv=S.find(dep); if (serv) { ready=ready && (serv.state==State.ONLINE); } else { Io.out('[BOOT] Warning: Service '+self.app+' has unknwon dependency: '+dep); ready=false; } }); return ready; } service.prototype.info = function () { return this.app+': '+this.state; } /** Start a service. Searches a matching transition based on current state. */ service.prototype.start = function () { var self=this,_args=process.argv,trans; Comp.array.iter_break(this.trans, function (t) { var exit=false; if (Comp.array.contains(t.states,self.state)) { exit=true; trans=t; } return exit; }); if (!trans) return; if (Comp.array.contains(options.boot_ops,Operation.MAKEAPP)) { Io.out('[BOOT] Compiling '+this.app+' '+Comp.printf.list(trans.args,_,' ')+' ..'); process.argv=Comp.array.concat(['node',this.app],trans.args); this.process=Require(this.app); process.argv=_args; } else { this.process=Io.fork(options.prog,Comp.array.concat(['exec',this.app],trans.args)); Io.out('[BOOT] '+this.process.pid+': Started '+this.app+' '+Comp.printf.list(trans.args,_,' ')+' ..'); switch (this.state) { case State.OFFLINE: case State.UNINITIALIZED: if (trans.wait) Io.sleep(trans.wait); this.state=State.ONLINE; break; } } } service.prototype.stop = function () { if (this.process) { Io.out('[BOOT] '+this.process.pid+': Stopping '+this.app+' ..'); this.process.kill(); } this.process=null; this.state=State.OFFLINE; } services.prototype.watchdog = function () { this.network = Conn.setup(options); this.network.init(this.network.start.bind(this)); Comp.array.iter(this.services,function (s) { if (s.state != State.TEST && s.pollcap) { Io.out('[BOOT] Adding watchdog service for '+s.app); Sch.NewTask('WATCHDDOG '+s.app,watchdog,s); } }); } services.prototype.write = function (file) { var services=[], text; Io.out('[BOOT] Writing service file '+file+' ..'); Comp.array.iter(this.services,function (s) { var sc={app:s.app,trans:s.trans,state:s.state}; if (s.poll) sc.poll=s.poll; if (s.pollcap) sc.pollcap=s.pollcap; if (s.polltime) sc.pollcap=s.polltime; if (s.depends) sc.depends=s.depends; services.push(sc); }); text=JSON.stringify(services); Io.write_file(file,text); } My.host=Io.hostname(); var Services = new services({}); var scheduler = Sch.TaskScheduler(); scheduler.Init(); if (makeapp || refresh || !Io.exists(options.bootst)) Services.read(options.bootsf); else Services.read(options.bootst); process.on('SIGINT', function () { Io.out('Got SIGINT ..'); Io.out('[BOOT] Stopping all services ..'); Services.exec(Operation.STOP); Services.write(options.bootst); process.exit(2); }); for (var cmd in options.boot_ops) { Services.exec(options.boot_ops[cmd]); } Services.write(options.bootst); if (start) { Services.watchdog(); scheduler.Run(); } else if (!makeapp) { Io.out('Exiting ..'); process.exit(0); } else Io.out('Waiting ..');