Console-less OpenSim
From OpenSimulator
(Added "screen" description) |
(added new xmlrpc daemon method) |
||
Line 1: | Line 1: | ||
+ | ==Background== | ||
+ | OpenSimulator uses consoles for each service it's running. It was already possible to run the services(UGAIMR) in the background, while still being able to access the consoles. On Linux we used the convenient screen tool for this. The idea situation would be though, to run most commands from an | ||
+ | external source like a webportal. The region service has an XMLRPC interface, that's called remote admin. While remote admin offers controls to some extend, it's still impossible to let's say shutdown the region service, and restart it from a webpage. This would require a seperate xmlrpc daemon, which has control over the OpenSim services. In python, it's quite easy to make such a xmlrpc daemon as you will see below. | ||
− | + | ==XMLRPC== | |
+ | XMLRPC is a simple protocol that's supported by most scripting languages. PHP5 has some quick functions to encode a command, and parameters from an array. I got the best results using Curl for this. | ||
− | + | ==Source== | |
− | + | <source lang="python"> | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
#!/usr/bin/python | #!/usr/bin/python | ||
− | + | from twisted.web import xmlrpc, server | |
− | + | from twisted.internet import reactor | |
− | import | + | import socket, os, sys, time, xmlrpclib, pycurl |
− | import | + | import xmlrpclib, pycurl |
− | + | import subprocess | |
− | import os | + | sys.path.append("./lib") |
− | + | from daemon import Daemon | |
− | + | from configobj import ConfigObj | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | try: | |
− | + | import signal | |
− | + | from signal import SIGPIPE, SIG_IGN | |
− | + | signal.signal(signal.SIGPIPE, signal.SIG_IGN) | |
− | + | except ImportError: | |
+ | pass | ||
+ | try: | ||
+ | from cStringIO import StringIO | ||
+ | except ImportError: | ||
+ | from StringIO import StringIO | ||
− | + | class CURLTransport(xmlrpclib.Transport): | |
− | + | xmlrpc_h = [ "Content-Type: text/xml" ] | |
− | + | def __init__(self, username=None, password=None): | |
− | + | self.c = pycurl.Curl() | |
− | + | self.c.setopt(pycurl.POST, 1) | |
− | + | self.c.setopt(pycurl.NOSIGNAL, 1) | |
+ | self.c.setopt(pycurl.CONNECTTIMEOUT, 30) | ||
+ | self.c.setopt(pycurl.HTTPHEADER, self.xmlrpc_h) | ||
+ | if username != None and password != None: | ||
+ | self.c.setopt(pycurl.USERPWD, '%s:%s' % (username, password)) | ||
+ | self._use_datetime = False | ||
− | + | def request(self, host, handler, request_body, verbose=0): | |
+ | b = StringIO() | ||
+ | self.c.setopt(pycurl.URL, 'http://%s%s' % (host, handler)) | ||
+ | self.c.setopt(pycurl.POSTFIELDS, request_body) | ||
+ | self.c.setopt(pycurl.WRITEFUNCTION, b.write) | ||
+ | self.c.setopt(pycurl.VERBOSE, verbose) | ||
+ | self.verbose = verbose | ||
+ | try: | ||
+ | self.c.perform() | ||
+ | except pycurl.error, v: | ||
+ | raise xmlrpclib.ProtocolError( | ||
+ | host + handler, | ||
+ | v[0], v[1], None | ||
+ | ) | ||
+ | b.seek(0) | ||
+ | return self.parse_response(b) | ||
− | + | class OSAdmin(xmlrpc.XMLRPC): | |
+ | config = ConfigObj('settings.ini') | ||
+ | radminhost = config['RPC']['radminhost'] | ||
+ | radminpw = config['RPC']['radminpw'] | ||
+ | radminGW = xmlrpclib.ServerProxy(radminhost,transport=CURLTransport()) | ||
+ | def xmlrpc_admin_broadcast(self,message): | ||
+ | return self.radminGW.admin_broadcast({"password": self.radminpw, "message": message}) | ||
+ | def xmlrpc_admin_create_region(self,region_id,region_name,region_x,region_y,listen_port,region_master_first,region_master_last,region_master_password,listen_ip,external_address,allow_alternate_ports,lastmap_uuid,lastmap_refresh): | ||
+ | return self.radminGW.admin_create_region({"password": self.radminpw,"region_id": region_id,"region_name": region_name,"region_x":region_x,"region_y":region_y,"listen_port":listen_port,"region_master_first":region_master_first,"region_master_last":region_master_last,"region_master_password":region_master_password,"listen_ip":listen_ip,"external_address":external_address,"allow_alternate_ports":allow_alternate_ports,"lastmap_uuid":lastmap_uuid,"lastmap_refresh":lastmap_refresh}) | ||
+ | def xmlrpc_admin_delete_region(self,region_name): | ||
+ | return self.radminGW.admin_delete_region({"password": self.radminpw,"region_name": region_name}) | ||
+ | def xmlrpc_admin_shutdown(self): | ||
+ | return self.radminGW.admin_shutdown({"password": self.radminpw}) | ||
+ | def xmlrpc_admin_region_query(self,region_name): | ||
+ | return self.radminGW.admin_region_query({"password": self.radminpw,"region_name": region_name}) | ||
+ | def xmlrpc_start_simulator(self): | ||
+ | os.chdir('/opt/opensim/simulator') | ||
+ | cmd = "screen -dmS simulator mono /opt/opensim/simulator/OpenSim.exe" | ||
+ | os.system(cmd) | ||
+ | def xmlrpc_ping_simulator(self): | ||
+ | try: | ||
+ | print self.radminGW.admin_simhealth({"password": self.radminpw}) | ||
+ | except xmlrpclib.Error, v: | ||
+ | if str(v)[1:14] == "ProtocolError": | ||
+ | return 0 | ||
+ | elif str(v)[1:14] == "Fault -32601:": | ||
+ | return 1 | ||
+ | else: | ||
+ | return 0 | ||
+ | def xmlrpc_stop_ugaim(self): | ||
+ | cmd = "screen -S user -p 0 -X stuff $'shutdown\n'" | ||
+ | os.system(cmd) | ||
+ | cmd = "screen -S grid -X -p 0 stuff $'shutdown\n'" | ||
+ | os.system(cmd) | ||
+ | cmd = "screen -S asset -X -p 0 stuff $'shutdown\n'" | ||
+ | os.system(cmd) | ||
+ | cmd = "screen -S inventory -p 0 -X stuff $'shutdown\n'" | ||
+ | os.system(cmd) | ||
+ | cmd = "screen -S messaging -p 0 -X stuff $'shutdown\n'" | ||
+ | os.system(cmd) | ||
+ | def xmlrpc_start_ugaim(self): | ||
+ | os.chdir('/opt/opensim/simulator') | ||
+ | cmd = "screen -dmS user mono /opt/opensim/simulator/OpenSim.Grid.UserServer.exe" | ||
+ | os.system(cmd) | ||
+ | time.sleep(4) | ||
+ | cmd = "screen -dmS grid mono /opt/opensim/simulator/OpenSim.Grid.GridServer.exe" | ||
+ | os.system(cmd) | ||
+ | time.sleep(3) | ||
+ | cmd = "screen -dmS asset mono /opt/opensim/simulator/OpenSim.Grid.AssetServer.exe" | ||
+ | os.system(cmd) | ||
+ | time.sleep(3) | ||
+ | cmd = "screen -dmS inventory mono /opt/opensim/simulator/OpenSim.Grid.InventoryServer.exe" | ||
+ | os.system(cmd) | ||
+ | cmd = "screen -dmS messaging mono /opt/opensim/simulator/OpenSim.Grid.MessagingServer.exe" | ||
+ | os.system(cmd) | ||
+ | def xmlrpc_ping_ugaim(self): | ||
+ | os.chdir('/opt/opensim/simulator') | ||
+ | cmd = "screen -dmS simulator mono /opt/opensim/simulator/OpenSim.exe" | ||
+ | os.system(cmd) | ||
+ | def xmlrpc_ping(self): | ||
+ | return 1 | ||
+ | def xmlrpc_fault(self): | ||
+ | raise xmlrpc.Fault(123, "The fault procedure is faulty.") | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | class RPCDaemon(Daemon): | |
+ | def run(self): | ||
+ | reactor.listenTCP(9996, server.Site(OSAdmin())) | ||
+ | reactor.run() | ||
− | + | if __name__ == "__main__": | |
+ | daemon = RPCDaemon('/tmp/rpcMoo.pid') | ||
+ | if len(sys.argv) == 2: | ||
+ | if 'start' == sys.argv[1]: | ||
+ | daemon.start() | ||
+ | elif 'stop' == sys.argv[1]: | ||
+ | daemon.stop() | ||
+ | elif 'restart' == sys.argv[1]: | ||
+ | daemon.restart() | ||
+ | else: | ||
+ | print "Unknown command" | ||
+ | sys.exit(2) | ||
+ | sys.exit(0) | ||
+ | else: | ||
+ | print "usage: %s start|stop|restart" % sys.argv[0] | ||
+ | sys.exit(2) | ||
− | + | </source> |
Revision as of 04:16, 18 February 2009
Background
OpenSimulator uses consoles for each service it's running. It was already possible to run the services(UGAIMR) in the background, while still being able to access the consoles. On Linux we used the convenient screen tool for this. The idea situation would be though, to run most commands from an external source like a webportal. The region service has an XMLRPC interface, that's called remote admin. While remote admin offers controls to some extend, it's still impossible to let's say shutdown the region service, and restart it from a webpage. This would require a seperate xmlrpc daemon, which has control over the OpenSim services. In python, it's quite easy to make such a xmlrpc daemon as you will see below.
XMLRPC
XMLRPC is a simple protocol that's supported by most scripting languages. PHP5 has some quick functions to encode a command, and parameters from an array. I got the best results using Curl for this.
Source
#!/usr/bin/python from twisted.web import xmlrpc, server from twisted.internet import reactor import socket, os, sys, time, xmlrpclib, pycurl import xmlrpclib, pycurl import subprocess sys.path.append("./lib") from daemon import Daemon from configobj import ConfigObj try: import signal from signal import SIGPIPE, SIG_IGN signal.signal(signal.SIGPIPE, signal.SIG_IGN) except ImportError: pass try: from cStringIO import StringIO except ImportError: from StringIO import StringIO class CURLTransport(xmlrpclib.Transport): xmlrpc_h = [ "Content-Type: text/xml" ] def __init__(self, username=None, password=None): self.c = pycurl.Curl() self.c.setopt(pycurl.POST, 1) self.c.setopt(pycurl.NOSIGNAL, 1) self.c.setopt(pycurl.CONNECTTIMEOUT, 30) self.c.setopt(pycurl.HTTPHEADER, self.xmlrpc_h) if username != None and password != None: self.c.setopt(pycurl.USERPWD, '%s:%s' % (username, password)) self._use_datetime = False def request(self, host, handler, request_body, verbose=0): b = StringIO() self.c.setopt(pycurl.URL, 'http://%s%s' % (host, handler)) self.c.setopt(pycurl.POSTFIELDS, request_body) self.c.setopt(pycurl.WRITEFUNCTION, b.write) self.c.setopt(pycurl.VERBOSE, verbose) self.verbose = verbose try: self.c.perform() except pycurl.error, v: raise xmlrpclib.ProtocolError( host + handler, v[0], v[1], None ) b.seek(0) return self.parse_response(b) class OSAdmin(xmlrpc.XMLRPC): config = ConfigObj('settings.ini') radminhost = config['RPC']['radminhost'] radminpw = config['RPC']['radminpw'] radminGW = xmlrpclib.ServerProxy(radminhost,transport=CURLTransport()) def xmlrpc_admin_broadcast(self,message): return self.radminGW.admin_broadcast({"password": self.radminpw, "message": message}) def xmlrpc_admin_create_region(self,region_id,region_name,region_x,region_y,listen_port,region_master_first,region_master_last,region_master_password,listen_ip,external_address,allow_alternate_ports,lastmap_uuid,lastmap_refresh): return self.radminGW.admin_create_region({"password": self.radminpw,"region_id": region_id,"region_name": region_name,"region_x":region_x,"region_y":region_y,"listen_port":listen_port,"region_master_first":region_master_first,"region_master_last":region_master_last,"region_master_password":region_master_password,"listen_ip":listen_ip,"external_address":external_address,"allow_alternate_ports":allow_alternate_ports,"lastmap_uuid":lastmap_uuid,"lastmap_refresh":lastmap_refresh}) def xmlrpc_admin_delete_region(self,region_name): return self.radminGW.admin_delete_region({"password": self.radminpw,"region_name": region_name}) def xmlrpc_admin_shutdown(self): return self.radminGW.admin_shutdown({"password": self.radminpw}) def xmlrpc_admin_region_query(self,region_name): return self.radminGW.admin_region_query({"password": self.radminpw,"region_name": region_name}) def xmlrpc_start_simulator(self): os.chdir('/opt/opensim/simulator') cmd = "screen -dmS simulator mono /opt/opensim/simulator/OpenSim.exe" os.system(cmd) def xmlrpc_ping_simulator(self): try: print self.radminGW.admin_simhealth({"password": self.radminpw}) except xmlrpclib.Error, v: if str(v)[1:14] == "ProtocolError": return 0 elif str(v)[1:14] == "Fault -32601:": return 1 else: return 0 def xmlrpc_stop_ugaim(self): cmd = "screen -S user -p 0 -X stuff $'shutdown\n'" os.system(cmd) cmd = "screen -S grid -X -p 0 stuff $'shutdown\n'" os.system(cmd) cmd = "screen -S asset -X -p 0 stuff $'shutdown\n'" os.system(cmd) cmd = "screen -S inventory -p 0 -X stuff $'shutdown\n'" os.system(cmd) cmd = "screen -S messaging -p 0 -X stuff $'shutdown\n'" os.system(cmd) def xmlrpc_start_ugaim(self): os.chdir('/opt/opensim/simulator') cmd = "screen -dmS user mono /opt/opensim/simulator/OpenSim.Grid.UserServer.exe" os.system(cmd) time.sleep(4) cmd = "screen -dmS grid mono /opt/opensim/simulator/OpenSim.Grid.GridServer.exe" os.system(cmd) time.sleep(3) cmd = "screen -dmS asset mono /opt/opensim/simulator/OpenSim.Grid.AssetServer.exe" os.system(cmd) time.sleep(3) cmd = "screen -dmS inventory mono /opt/opensim/simulator/OpenSim.Grid.InventoryServer.exe" os.system(cmd) cmd = "screen -dmS messaging mono /opt/opensim/simulator/OpenSim.Grid.MessagingServer.exe" os.system(cmd) def xmlrpc_ping_ugaim(self): os.chdir('/opt/opensim/simulator') cmd = "screen -dmS simulator mono /opt/opensim/simulator/OpenSim.exe" os.system(cmd) def xmlrpc_ping(self): return 1 def xmlrpc_fault(self): raise xmlrpc.Fault(123, "The fault procedure is faulty.") class RPCDaemon(Daemon): def run(self): reactor.listenTCP(9996, server.Site(OSAdmin())) reactor.run() if __name__ == "__main__": daemon = RPCDaemon('/tmp/rpcMoo.pid') if len(sys.argv) == 2: if 'start' == sys.argv[1]: daemon.start() elif 'stop' == sys.argv[1]: daemon.stop() elif 'restart' == sys.argv[1]: daemon.restart() else: print "Unknown command" sys.exit(2) sys.exit(0) else: print "usage: %s start|stop|restart" % sys.argv[0] sys.exit(2)