595 lines
16 KiB
JavaScript
595 lines
16 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-2017 bLAB
|
|
** $CREATED: 29-3-16 by sbosse.
|
|
** $VERSION: 1.5,1
|
|
**
|
|
** $INFO:
|
|
**
|
|
** JAM Standalone Node VM with DOS: WEB edition
|
|
**
|
|
** $ENDOFINFO
|
|
*/
|
|
|
|
global.config={
|
|
simulation:false,
|
|
dos:true
|
|
};
|
|
|
|
var Io = Require('com/io');
|
|
var Comp = Require('com/compat');
|
|
var Name = Require('com/pwgen');
|
|
|
|
var Net = Require('dos/network');
|
|
var Buf = Require('dos/buf');
|
|
var Sch = Require('dos/scheduler');
|
|
var Conn = Require('dos/connection');
|
|
var Rpc = Require('dos/rpc');
|
|
var Std = Require('dos/std');
|
|
var Router = Require('dos/router');
|
|
var assert = Comp.assert;
|
|
var String = Comp.string;
|
|
var Array = Comp.array;
|
|
var Perv = Comp.pervasives;
|
|
var Printf = Comp.printf;
|
|
var Filename = Comp.filename;
|
|
var Obj = Comp.obj;
|
|
var Args = Comp.args;
|
|
var Status = Net.Status;
|
|
var Command = Net.Command;
|
|
var Dns = Require('dos/dns');
|
|
var Cs = Require('dos/capset');
|
|
var Getenv = Require('com/getenv');
|
|
var HostSrv = Require('dos/hostsrv');
|
|
var Run = Require('dos/run');
|
|
var RunSrv = Require('dos/runsrv');
|
|
var Esprima = Require('parser/esprima');
|
|
var Json = Require('jam/jsonfn');
|
|
var satelize = Require('dos/ext/satelize');
|
|
//var FileReader = Require('os/FileReader');
|
|
//var FileSaver = Require('os/FileSaver');
|
|
|
|
var Db = Require('db/db');
|
|
var Aios = Require('jam/aios');
|
|
var Dios = Require('dos/dios');
|
|
|
|
var nameopts = {length:8, memorable:true, lowercase:true};
|
|
|
|
var out = function (msg) { Io.out('[JAM] '+msg)};
|
|
|
|
/* ----------------------- */
|
|
|
|
var jamnet = function (_options) {
|
|
var self=this;
|
|
this.options = {
|
|
bip:'localhost',
|
|
bport:3001,
|
|
broker:false,
|
|
default:true,
|
|
env:{},
|
|
geo:undefined,
|
|
http:false,
|
|
links:[],
|
|
myip:'localhost',
|
|
monitor:0,
|
|
out:function (msg) {Io.out(msg)},
|
|
err:function (msg) {Io.out(msg)},
|
|
warn:function (msg) {Io.out(msg)},
|
|
verbose:0
|
|
};
|
|
for (p in _options) this.options[p]=_options[p];
|
|
|
|
this.world = this.options.world; // name only!
|
|
this.out=function (msg) { (self.options.out||Io.out)('[NET] '+msg)};
|
|
this.err=function (msg) { throw ('[NET] Error: '+msg)};
|
|
this.warn=function (msg) { (self.options.out||Io.out)('[NET] Warning: '+msg)};
|
|
|
|
this.broker=this.options.http;
|
|
this.bport=this.options.bport||3001;
|
|
this.bip=this.options.bip;
|
|
|
|
this.scheduler=Sch.TaskScheduler();
|
|
|
|
this.todo=[];
|
|
this.exit = [];
|
|
|
|
this.env=this.options.env;
|
|
|
|
satelize.satelize({}, function(err, geoData) {
|
|
// process err
|
|
if (err != undefined) {
|
|
self.out('GEO Location failed: '+err)
|
|
} else if (geoData) {
|
|
try {
|
|
var obj = JSON.parse(geoData);
|
|
self.out('GEO Location (lati=' + obj.lat + ', long=' + obj.lon + ')');
|
|
self.options.geo=obj;
|
|
} catch (e) {
|
|
if (self.options.verbose>1) myJam.out('GEO Location failed: '+e+',\n'+geoData);
|
|
else self.out('GEO Location not available: '+e);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.out('Network with host port '+Net.Print.port(this.options.pubhostport)+
|
|
' created for world '+this.world+'.');
|
|
this.run=false;
|
|
|
|
|
|
}
|
|
|
|
jamnet.prototype.init=function () {
|
|
var self=this;
|
|
this.out('Initializing ..');
|
|
if (this.broker) {
|
|
this.network = Conn.setup(this.options);
|
|
this.router = this.network.router;
|
|
this.rpc = this.network.rpc;
|
|
this.std = Std.StdInt(this.rpc,this.env);
|
|
this.dns = Dns.DnsInt(this.rpc,this.env);
|
|
this.cs = Cs.CsInt(this.rpc,this.env);
|
|
this.hostsrv = none; // requires router init., created on initialization
|
|
Aios.current.network = this.network;
|
|
}
|
|
this.scheduler.Init();
|
|
}
|
|
|
|
jamnet.prototype.start=function () {
|
|
this.run=true;
|
|
this.out('Starting ..');
|
|
// Start up the network ..
|
|
Sch.ScheduleBlock([this.network.init]);
|
|
// this.out('Starting ..');
|
|
this.scheduler.Run();
|
|
}
|
|
|
|
jamnet.prototype.status=function () {
|
|
return this.router?this.router.status():'Not initialized.';
|
|
}
|
|
|
|
jamnet.prototype.stop=function () {
|
|
this.run=true;
|
|
this.out('Stopping ..');
|
|
this.network.stop();
|
|
}
|
|
|
|
|
|
var JamNet = function(options) {
|
|
var jamNet;
|
|
jamNet = new jamnet(options);
|
|
return jamNet;
|
|
};
|
|
|
|
|
|
/* ----------------------- */
|
|
|
|
var jam = function (_options) {
|
|
var main=this,
|
|
options = {
|
|
default:true,
|
|
domain:'default',
|
|
env:{},
|
|
geo:undefined,
|
|
hostname:'localhost',
|
|
hostport:undefined,
|
|
nodename:Name.generate(nameopts), // pre-guess
|
|
onexit:false,
|
|
out:function (msg) {Io.out(msg)},
|
|
start:false,
|
|
verbose:0
|
|
};
|
|
for (p in _options) options[p]=_options[p];
|
|
|
|
this.options = options;
|
|
this.env=this.options.env;
|
|
this.verbose=options.verbose||0;
|
|
|
|
this.out=function (msg) { (options.out||Io.out)('[JAM '+main.nodename+'] '+msg)};
|
|
this.err=function (msg) { throw ('[JAM '+main.nodename+'] Error: '+msg)};
|
|
this.warn=function (msg) { (options.out||Io.out)('[JAM '+main.nodename+'] Warning: '+msg)};
|
|
|
|
if (!this.options.id) this.id=Aios.aidgen();
|
|
else this.id=options.id;
|
|
|
|
this.world = options.world||Aios.World.World([],{
|
|
id:this.options.id,
|
|
classes:options.classes||[]
|
|
});
|
|
this.node = Aios.Node.Node({
|
|
id:this.options.id,
|
|
out:this.out,
|
|
position:{x:0,y:0}
|
|
});
|
|
this.world.add(this.node);
|
|
|
|
this.run=false;
|
|
this.looprun=none;
|
|
|
|
this.network=options.network;
|
|
if (this.network) this.rpc=this.network.rpc;
|
|
if (this.network) this.dios = Dios.Dios(this.network.rpc,this.network.env);
|
|
if (options.scheduler) this.scheduler=options.scheduler;
|
|
|
|
// Register a DOS link-connection for agent migration
|
|
if (this.network) this.node.connections.dos = {
|
|
send: function (text,dest,context) {
|
|
main.node.connections.dos.count += text.length;
|
|
if (Obj.isObject(dest)) // cap
|
|
{
|
|
var stat;
|
|
// This schedule block must by passed to the global (DOS) scheduler!!
|
|
Sch.B([
|
|
function () {
|
|
main.network.run.ps_migrate(dest,text,function (_stat) {
|
|
stat=_stat;
|
|
});
|
|
},
|
|
function () {
|
|
if (stat!=Net.Status.STD_OK) {
|
|
// context???
|
|
context.error='Migration to server '+Net.Print.capability(dest)+' failed: '+Net.Print.status(stat);
|
|
// We're still in the agent process context! Throw an error for this agent ..
|
|
throw 'MOVE';
|
|
};
|
|
|
|
// kill ghost agent
|
|
context.process.finalize();
|
|
}
|
|
]);
|
|
} else if (Obj.isString(dest)) { // path
|
|
|
|
}
|
|
},
|
|
status: function () {
|
|
// TODO
|
|
return main.network.status()==Net.Status.STD_OK;
|
|
},
|
|
count:0
|
|
}
|
|
|
|
|
|
this.options.privhostport= Net.uniqport();
|
|
this.options.pubhostport = Net.prv2pub(this.options.privhostport);
|
|
|
|
|
|
this.todo=[];
|
|
|
|
this.exit = [];
|
|
|
|
this.hostname=options.hostname;
|
|
this.nodename=options.nodename;
|
|
this.domain=options.domain;
|
|
|
|
/* Install HOST/DNS tuple provider
|
|
** Expected patterns:
|
|
** DNS,path,?
|
|
** DOMAIN,?
|
|
** HOSTNAME,?
|
|
*/
|
|
|
|
this.node.ts.register(function (pat) {
|
|
var stat,tuple;
|
|
// Caching?
|
|
if (pat.length<2) return none;
|
|
switch (pat[0]) {
|
|
case 'DNS':
|
|
if (pat.length<3) return none; else Sch.B([
|
|
function () {
|
|
main.dios.dir(pat[1],function (rows,_stat) {
|
|
stat=_stat;
|
|
if (stat==Status.STD_OK) tuple=[pat[0],pat[1],Comp.array.filtermap(rows,function (row) {
|
|
if (row.stat==Status.STD_OK) return {name:row.name,cap:Net.Print.capability(row.cap)};
|
|
else return none;
|
|
})];
|
|
})
|
|
},
|
|
function () {
|
|
// console.log('>> '+Status.print(stat));
|
|
if (stat==Status.STD_OK)
|
|
main.node.ts.checkwaiter(tuple);
|
|
}
|
|
]);
|
|
break;
|
|
case 'HOSTNAME': return ['HOSTNAME',main.hostname];
|
|
case 'NODENAME': return ['NODENAME',main.nodename];
|
|
case 'DOMAIN': return ['DOMAIN','default'];
|
|
}
|
|
// console.log(Sch.GetCurrent())
|
|
return none;
|
|
});
|
|
|
|
this.out('JAM created in world '+this.world.id+'.');
|
|
}
|
|
|
|
// Import analyzer class...
|
|
var JamAnal = Require('jam/analyzer');
|
|
JamAnal.current(Aios);
|
|
jam.prototype.analyze=JamAnal.jamc.prototype.analyze;
|
|
jam.prototype.syntax=JamAnal.jamc.prototype.syntax;
|
|
|
|
// Run server extension
|
|
RunSrv.current(Aios);
|
|
jam.prototype.request=RunSrv.run.prototype.request;
|
|
|
|
/** Add an agent class template {<ac name>:<ac constructor fun>} to the JAM world
|
|
*
|
|
*/
|
|
jam.prototype.addclass = function (ac) {
|
|
for (var p in ac) {
|
|
if (this.options.verbose>=0) this.out((this.world.classes[p]?'Updating':'Adding')+' agent class template '+p+'.');
|
|
this.world.classes[p]=ac[p];
|
|
}
|
|
};
|
|
|
|
|
|
/** Compile agent class templates from text.
|
|
* Expected text format: var classes = {ac1: function () {}, ac2: function ..}
|
|
*
|
|
*/
|
|
jam.prototype.compile = function (text,modu) {
|
|
var ac,
|
|
ast,
|
|
p,
|
|
regex1,
|
|
ast=null,
|
|
off=null,
|
|
code,
|
|
content,
|
|
syntax,
|
|
more;
|
|
|
|
if (this.options.verbose>0) this.out('Compiling agent class template(s)');
|
|
|
|
if (text!=undefined) {
|
|
code=text+' classes';
|
|
try {
|
|
ast=Esprima.parse(code, { tolerant: true, loc:true });
|
|
if (ast.errors && ast.errors.length>0) throw ast.errors[0];
|
|
modu=eval(code);
|
|
} catch (e) {
|
|
if (e.lineNumber) more = ", in line "+e.lineNumber;
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
for (p in modu) {
|
|
ac={};
|
|
ac[p]=modu[p];
|
|
if (ast) off=this.syntax.find(ast,'VariableDeclarator',p);
|
|
if (off && off.loc) this.syntax.offset=off.loc.start.line-1;
|
|
content = 'var ac = '+modu[p];
|
|
try {
|
|
syntax = Esprima.parse(content, { tolerant: true, loc:true })
|
|
if (syntax.errors && syntax.errors.length>0) throw syntax.errors[0];
|
|
} catch (e) {
|
|
if (e.lineNumber) more = ", in line "+e.lineNumber;
|
|
throw e;
|
|
};
|
|
|
|
this.analyze(syntax,{classname:p,level:2,verbose:1});
|
|
text=Json.stringify(ac);
|
|
regex1= /this\.next=([a-zA-Z0-9_]+)/;
|
|
text=text.replace(regex1,"this.next='$1'");
|
|
// console.log(text);
|
|
ac=Json.parse(text,{});
|
|
this.addclass(ac);
|
|
this.syntax.offset=0;
|
|
}
|
|
};
|
|
|
|
/** Create and start an agent from class ac with arguments.
|
|
*
|
|
*/
|
|
jam.prototype.create = function (ac,args) {
|
|
var node=this.node;
|
|
var agent=none;
|
|
if (this.world.classes[ac])
|
|
agent = Aios.Code.createOn(node,this.world.classes[ac],args);
|
|
else this.out('create: Cannot find agent class '+ac);
|
|
if (agent) {
|
|
if (this.options.verbose>0) this.out('Created agent '+agent.agent.id+' from class template(s) '+ac);
|
|
agent.agent.ac=ac;
|
|
return agent.agent.id;
|
|
} else return none;
|
|
}
|
|
|
|
/** Initialize JAMDOS and create/start host server
|
|
*
|
|
*/
|
|
jam.prototype.init=function () {
|
|
var self=this,
|
|
stat,
|
|
cs,
|
|
csrow,
|
|
csdir,
|
|
i,
|
|
names=[];
|
|
|
|
this.out('Initializing ..');
|
|
for(i=0;i<10;i++) names.push(Name.generate(nameopts));
|
|
|
|
this.todo.push([
|
|
// TODO: GEO location from broker using satellize ...
|
|
function () {
|
|
},
|
|
function () {
|
|
// Append default DNS and root directory
|
|
var dnscs;
|
|
if (self.env.rootdir) {
|
|
dnscs = self.env.rootdir;
|
|
self.hostsrv.append('/dns/default', dnscs, function (_stat) {stat=_stat});
|
|
if (stat != Status.STD_OK) self.out('Cannot append ' + 'dns/default' + ': ' + Status.print(stat));
|
|
self.hostsrv.append('/root', dnscs, function (_stat) {stat=_stat});
|
|
if (stat != Status.STD_OK) self.out('Cannot append ' + 'root' + ': ' + Status.print(stat));
|
|
}
|
|
},
|
|
function () {
|
|
// Check for default domain ...
|
|
self.hostsrv.lookup('/root/domains/'+self.domain, function (_stat,_cs) {
|
|
if (self.options.verbose>0) self.out('lookup /root/domains/'+self.domain+': '+Status.print(_stat))
|
|
stat=_stat;
|
|
csdir=_cs;
|
|
});
|
|
},
|
|
function () {
|
|
if (stat==Status.STD_OK)
|
|
L(
|
|
function () {
|
|
return names.length>0;
|
|
},
|
|
[
|
|
function () {
|
|
// Check for node in default domain ...
|
|
self.hostsrv.lookup('/root/domains/'+self.domain+'/'+self.nodename, function (_stat,_cs) {
|
|
if (self.options.verbose>0) self.out('lookup /root/domains/'+self.domain+'/'+self.nodename+': '+Status.print(_stat))
|
|
stat=_stat;
|
|
cs=_cs;
|
|
});
|
|
},
|
|
function () {
|
|
// Try to remove remains of node in default domain (2)
|
|
if (stat==Status.RPC_FAILURE) {
|
|
self.hostsrv.delete('/root/domains/'+self.domain+'/'+self.nodename,
|
|
function (_stat) {
|
|
if (self.options.verbose>0) self.out('delete /root/domains/'+self.domain+'/'+self.nodename+': '+Status.print(_stat))
|
|
stat=_stat;
|
|
}
|
|
);
|
|
}
|
|
},
|
|
function () {
|
|
// Try to register node in default domain (3)
|
|
|
|
if (stat==Status.STD_NOTFOUND || stat==Status.STD_OK) {
|
|
self.hostsrv.append('/root/domains/'+self.domain+'/'+self.nodename, self.hostsrv.hostcap,
|
|
function (_stat) {
|
|
self.out('Publish /domains/'+self.domain+'/'+self.nodename+': '+Status.print(_stat))
|
|
stat=_stat;
|
|
}
|
|
);
|
|
}
|
|
},
|
|
function () {
|
|
if (stat==Status.STD_OK) names=[]; // Done
|
|
else {
|
|
self.nodename=Array.head(names);
|
|
names=Array.tail(names);
|
|
}
|
|
}
|
|
],
|
|
[
|
|
function () {
|
|
self.out('My final host name is '+self.hostname+'.'+self.nodename);
|
|
}
|
|
]
|
|
);
|
|
}
|
|
]);
|
|
B(Comp.array.flatten(this.todo));
|
|
this.exit.push([
|
|
function () {
|
|
self.hostsrv.delete('/root/domains/'+self.domain+'/'+self.nodename,
|
|
function (_stat) {
|
|
self.out('Unpublish /domains/'+self.domain+'/'+self.nodename+': '+Status.print(_stat))
|
|
stat=_stat;
|
|
}
|
|
);
|
|
}
|
|
]);
|
|
this.env.rootdir=this.network.env.rootdir;
|
|
this.hostsrv=HostSrv.HostServer(this.scheduler,
|
|
this.rpc,
|
|
this.options,
|
|
this.hostname+'.'+this.nodename,
|
|
this.env);
|
|
// Register our request handler and make a link back to our class handler
|
|
this.hostsrv.register(this.request);
|
|
this.hostsrv.addclass=function (ac) {
|
|
self.addclass(ac);
|
|
};
|
|
this.hostsrv.getclass=function (classname) {
|
|
return self.world.classes[classname];
|
|
};
|
|
this.hostsrv.receive=function (code,start) {
|
|
self.node.receive(code,start);
|
|
};
|
|
if (this.options.geo) myJam.hostsrv.set_geo(this.options.geo);
|
|
}
|
|
|
|
|
|
/** Read and compile agent class templates from file
|
|
* Format: module.exports = {ac1: function, ac2: function , ...]
|
|
*
|
|
*/
|
|
jam.prototype.readclass = function (file) {
|
|
var text,
|
|
modu;
|
|
|
|
if (this.options.verbose>0) this.out('Looking up agent class template(s) from '+file);
|
|
modu=Require(file);
|
|
this.compile(text,modu);
|
|
};
|
|
|
|
|
|
/** Start the JAM scheduler
|
|
*
|
|
*/
|
|
jam.prototype.start=function () {
|
|
this.run=true;
|
|
// this.out('Starting ..');
|
|
}
|
|
|
|
|
|
|
|
/** Stop the JAM scheduler
|
|
*
|
|
*/
|
|
jam.prototype.stop=function () {
|
|
this.run=false;
|
|
this.out('Stopping ..');
|
|
if (this.looprun)
|
|
clearTimeout(this.looprun);
|
|
B(Comp.array.flatten(this.exit));
|
|
}
|
|
|
|
|
|
var Jam = function(options) {
|
|
var myJam,
|
|
myJam = new jam(options);
|
|
return myJam;
|
|
};
|
|
|
|
out('Main module compiled.');
|
|
|
|
module.exports={
|
|
Aios:Aios,
|
|
Cs:Cs,
|
|
Dns:Dns,
|
|
Dios:Dios,
|
|
// FileReader:FileReader,
|
|
// FileSaver:FileSaver,
|
|
Jam:Jam,
|
|
Io:Io,
|
|
Name:Name,
|
|
Net:Net,
|
|
JamNet: JamNet,
|
|
Sch: Sch
|
|
};
|