500 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			500 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  **      ==============================
 | |
|  **       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  <pathtoconffile>]  (default '+options.bootsf+')');
 | |
|     Io.out('            [-state <pathtostatefile>] (default '+options.bootst+')');
 | |
|     Io.out('            [<service>]');
 | |
|     Io.out('            [exec <app> 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<this.maxrestart && serv.check()) {
 | |
|           Io.out('[BOOT] Starting '+serv.app);
 | |
|           serv.restart++;
 | |
|           serv.start();
 | |
|           Io.out('[BOOT] '+serv.app+': '+state+' -> '+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 ..');
 |