Further work on controlling the portal with AMP
This commit is contained in:
parent
c46d181566
commit
ff887a07ab
5 changed files with 113 additions and 58 deletions
|
|
@ -463,7 +463,7 @@ class MsgLauncher2Portal(amp.Command):
|
||||||
response = [('result', amp.String())]
|
response = [('result', amp.String())]
|
||||||
|
|
||||||
|
|
||||||
def send_instruction(operation, arguments, callback=None, errback=None, autostop=False):
|
def send_instruction(operation, arguments, callback=None, errback=None):
|
||||||
"""
|
"""
|
||||||
Send instruction and handle the response.
|
Send instruction and handle the response.
|
||||||
|
|
||||||
|
|
@ -484,20 +484,15 @@ def send_instruction(operation, arguments, callback=None, errback=None, autostop
|
||||||
if callback:
|
if callback:
|
||||||
callback(result)
|
callback(result)
|
||||||
prot.transport.loseConnection()
|
prot.transport.loseConnection()
|
||||||
if autostop:
|
|
||||||
reactor.stop()
|
|
||||||
|
|
||||||
def _errback(fail):
|
def _errback(fail):
|
||||||
if errback:
|
if errback:
|
||||||
errback(fail)
|
errback(fail)
|
||||||
prot.transport.loseConnection()
|
prot.transport.loseConnection()
|
||||||
if autostop:
|
|
||||||
reactor.stop()
|
|
||||||
|
|
||||||
if operation == PSTATUS:
|
if operation == PSTATUS:
|
||||||
prot.callRemote(MsgStatus, status="").addCallbacks(_callback, _errback)
|
prot.callRemote(MsgStatus, status="").addCallbacks(_callback, _errback)
|
||||||
else:
|
else:
|
||||||
print("callRemote MsgLauncher %s %s" % (ord(operation), arguments))
|
|
||||||
prot.callRemote(
|
prot.callRemote(
|
||||||
MsgLauncher2Portal,
|
MsgLauncher2Portal,
|
||||||
operation=operation,
|
operation=operation,
|
||||||
|
|
@ -507,8 +502,6 @@ def send_instruction(operation, arguments, callback=None, errback=None, autostop
|
||||||
def _on_connect_fail(fail):
|
def _on_connect_fail(fail):
|
||||||
"This is called if portal is not reachable."
|
"This is called if portal is not reachable."
|
||||||
errback(fail)
|
errback(fail)
|
||||||
if autostop:
|
|
||||||
reactor.stop()
|
|
||||||
|
|
||||||
point = endpoints.TCP4ClientEndpoint(reactor, AMP_HOST, AMP_PORT)
|
point = endpoints.TCP4ClientEndpoint(reactor, AMP_HOST, AMP_PORT)
|
||||||
deferred = endpoints.connectProtocol(point, amp.AMP())
|
deferred = endpoints.connectProtocol(point, amp.AMP())
|
||||||
|
|
@ -516,20 +509,55 @@ def send_instruction(operation, arguments, callback=None, errback=None, autostop
|
||||||
return deferred
|
return deferred
|
||||||
|
|
||||||
|
|
||||||
def send_status(repeat=False):
|
def _parse_status(response):
|
||||||
|
"Unpack the status information"
|
||||||
|
return pickle.loads(response['status'])
|
||||||
|
|
||||||
|
|
||||||
|
def _get_twistd_cmdline(pprofiler, sprofiler):
|
||||||
"""
|
"""
|
||||||
Send ping to portal
|
Compile the command line for starting a Twisted application using the 'twistd' executable.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
portal_cmd = [TWISTED_BINARY,
|
||||||
|
"--logfile={}".format(PORTAL_LOGFILE),
|
||||||
|
"--python={}".format(PORTAL_PY_FILE)]
|
||||||
|
server_cmd = [TWISTED_BINARY,
|
||||||
|
"--logfile={}".format(PORTAL_LOGFILE),
|
||||||
|
"--python={}".format(PORTAL_PY_FILE)]
|
||||||
|
if pprofiler:
|
||||||
|
portal_cmd.extend(["--savestats",
|
||||||
|
"--profiler=cprofiler",
|
||||||
|
"--profile={}".format(PPROFILER_LOGFILE)])
|
||||||
|
if sprofiler:
|
||||||
|
server_cmd.extend(["--savestats",
|
||||||
|
"--profiler=cprofiler",
|
||||||
|
"--profile={}".format(SPROFILER_LOGFILE)])
|
||||||
|
return portal_cmd, server_cmd
|
||||||
|
|
||||||
|
|
||||||
|
def query_status(repeat=False):
|
||||||
|
"""
|
||||||
|
Send status ping to portal
|
||||||
|
|
||||||
|
"""
|
||||||
|
wmap = {True: "RUNNING",
|
||||||
|
False: "NOT RUNNING"}
|
||||||
|
|
||||||
def _callback(response):
|
def _callback(response):
|
||||||
pstatus, sstatus = response['status'].split("|")
|
pstatus, sstatus, ppid, spid = _parse_status(response)
|
||||||
print("Portal: {}\nServer: {}".format(pstatus, sstatus))
|
print("Portal: {} (pid {})\nServer: {} (pid {})".format(
|
||||||
|
wmap[pstatus], ppid, wmap[sstatus], spid))
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
def _errback(fail):
|
def _errback(fail):
|
||||||
pstatus, sstatus = "NOT RUNNING", "NOT RUNNING"
|
print("status fail: %s", fail)
|
||||||
print("Portal: {}\nServer: {}".format(pstatus, sstatus))
|
pstatus, sstatus = False, False
|
||||||
|
print("Portal: {}\nServer: {}".format(wmap[pstatus], wmap[sstatus]))
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
send_instruction(PSTATUS, None, _callback, _errback, autostop=True)
|
send_instruction(PSTATUS, None, _callback, _errback)
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -550,7 +578,7 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
|
||||||
retries (int): How many times to retry before timing out and calling `errback`.
|
retries (int): How many times to retry before timing out and calling `errback`.
|
||||||
"""
|
"""
|
||||||
def _callback(response):
|
def _callback(response):
|
||||||
prun, srun = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
prun, srun, _, _ = _parse_status(response)
|
||||||
if ((portal_running is None or prun == portal_running) and
|
if ((portal_running is None or prun == portal_running) and
|
||||||
(server_running is None or srun == server_running)):
|
(server_running is None or srun == server_running)):
|
||||||
# the correct state was achieved
|
# the correct state was achieved
|
||||||
|
|
@ -594,52 +622,34 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
|
||||||
|
|
||||||
return send_instruction(PSTATUS, None, _callback, _errback)
|
return send_instruction(PSTATUS, None, _callback, _errback)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Helper functions
|
# Operational functions
|
||||||
#
|
#
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def get_twistd_cmdline(pprofiler, sprofiler):
|
|
||||||
|
|
||||||
portal_cmd = [TWISTED_BINARY,
|
|
||||||
"--logfile={}".format(PORTAL_LOGFILE),
|
|
||||||
"--python={}".format(PORTAL_PY_FILE)]
|
|
||||||
server_cmd = [TWISTED_BINARY,
|
|
||||||
"--logfile={}".format(PORTAL_LOGFILE),
|
|
||||||
"--python={}".format(PORTAL_PY_FILE)]
|
|
||||||
if pprofiler:
|
|
||||||
portal_cmd.extend(["--savestats",
|
|
||||||
"--profiler=cprofiler",
|
|
||||||
"--profile={}".format(PPROFILER_LOGFILE)])
|
|
||||||
if sprofiler:
|
|
||||||
server_cmd.extend(["--savestats",
|
|
||||||
"--profiler=cprofiler",
|
|
||||||
"--profile={}".format(SPROFILER_LOGFILE)])
|
|
||||||
return portal_cmd, server_cmd
|
|
||||||
|
|
||||||
|
|
||||||
def start_evennia(pprofiler=False, sprofiler=False):
|
def start_evennia(pprofiler=False, sprofiler=False):
|
||||||
"""
|
"""
|
||||||
This will start Evennia anew by launching the Evennia Portal (which in turn
|
This will start Evennia anew by launching the Evennia Portal (which in turn
|
||||||
will start the Server)
|
will start the Server)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
portal_cmd, server_cmd = get_twistd_cmdline(pprofiler, sprofiler)
|
portal_cmd, server_cmd = _get_twistd_cmdline(pprofiler, sprofiler)
|
||||||
|
|
||||||
def _server_started(*args):
|
def _server_started(*args):
|
||||||
print("... Server started.\nEvennia running.")
|
print("... Server started.\nEvennia running.", args)
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
|
|
||||||
def _portal_started(*args):
|
def _portal_started(*args):
|
||||||
send_instruction(SSTART, server_cmd, _server_started)
|
send_instruction(SSTART, server_cmd, _server_started)
|
||||||
|
|
||||||
def _portal_running(response):
|
def _portal_running(response):
|
||||||
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
prun, srun, ppid, spid = _parse_status(response)
|
||||||
print("Portal is already running as process {pid}. Not restarted.".format(pid=0)) # TODO PID
|
print("Portal is already running as process {pid}. Not restarted.".format(pid=ppid))
|
||||||
if server_running:
|
if srun:
|
||||||
print("Server is already running as process {pid}. Not restarted.".format(pid=0)) # TODO PID
|
print("Server is already running as process {pid}. Not restarted.".format(pid=spid))
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
else:
|
else:
|
||||||
print("Server starting {}...".format("(under cProfile)" if pprofiler else ""))
|
print("Server starting {}...".format("(under cProfile)" if pprofiler else ""))
|
||||||
|
|
@ -648,7 +658,7 @@ def start_evennia(pprofiler=False, sprofiler=False):
|
||||||
def _portal_not_running(fail):
|
def _portal_not_running(fail):
|
||||||
print("Portal starting {}...".format("(under cProfile)" if sprofiler else ""))
|
print("Portal starting {}...".format("(under cProfile)" if sprofiler else ""))
|
||||||
try:
|
try:
|
||||||
Popen(portal_cmd)
|
Popen(portal_cmd, env=getenv())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(PROCESS_ERROR.format(component="Portal", traceback=e))
|
print(PROCESS_ERROR.format(component="Portal", traceback=e))
|
||||||
wait_for_status(True, None, _portal_started)
|
wait_for_status(True, None, _portal_started)
|
||||||
|
|
@ -662,7 +672,7 @@ def reload_evennia(sprofiler=False):
|
||||||
This will instruct the Portal to reboot the Server component.
|
This will instruct the Portal to reboot the Server component.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_, server_cmd = get_twistd_cmdline(False, sprofiler)
|
_, server_cmd = _get_twistd_cmdline(False, sprofiler)
|
||||||
|
|
||||||
def _server_restarted(*args):
|
def _server_restarted(*args):
|
||||||
print("... Server re-started.", args)
|
print("... Server re-started.", args)
|
||||||
|
|
@ -673,8 +683,8 @@ def reload_evennia(sprofiler=False):
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
|
|
||||||
def _portal_running(response):
|
def _portal_running(response):
|
||||||
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
_, srun, _, _ = _parse_status(response)
|
||||||
if server_running:
|
if srun:
|
||||||
print("Server reloading ...")
|
print("Server reloading ...")
|
||||||
send_instruction(SRELOAD, server_cmd, _server_reloaded)
|
send_instruction(SRELOAD, server_cmd, _server_reloaded)
|
||||||
else:
|
else:
|
||||||
|
|
@ -700,16 +710,17 @@ def stop_evennia():
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
|
|
||||||
def _server_stopped(*args):
|
def _server_stopped(*args):
|
||||||
print("... Server stopped.", args)
|
print("... Server stopped.\nStopping Portal ...", args)
|
||||||
send_instruction(PSHUTD, {})
|
send_instruction(PSHUTD, {})
|
||||||
wait_for_status(False, None, _portal_stopped)
|
wait_for_status(False, None, _portal_stopped)
|
||||||
|
|
||||||
def _portal_running(response):
|
def _portal_running(response):
|
||||||
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
prun, srun, ppid, spid = _parse_status(response)
|
||||||
if server_running:
|
if srun:
|
||||||
print("Server stopping ...")
|
print("Server stopping ...")
|
||||||
send_instruction(SSHUTD, {}, _server_stopped)
|
send_instruction(SSHUTD, {}, _server_stopped)
|
||||||
else:
|
else:
|
||||||
|
print("Server already stopped.\nStopping Portal ...")
|
||||||
send_instruction(PSHUTD, {})
|
send_instruction(PSHUTD, {})
|
||||||
wait_for_status(False, False, _portal_stopped)
|
wait_for_status(False, False, _portal_stopped)
|
||||||
|
|
||||||
|
|
@ -1647,7 +1658,7 @@ def main():
|
||||||
|
|
||||||
if args.get_status:
|
if args.get_status:
|
||||||
init_game_directory(CURRENT_DIR, check_db=True)
|
init_game_directory(CURRENT_DIR, check_db=True)
|
||||||
send_status()
|
query_status()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.dummyrunner:
|
if args.dummyrunner:
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,12 @@ def catch_traceback(func):
|
||||||
def decorator(*args, **kwargs):
|
def decorator(*args, **kwargs):
|
||||||
try:
|
try:
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
except Exception:
|
except Exception as err:
|
||||||
global _LOGGER
|
global _LOGGER
|
||||||
if not _LOGGER:
|
if not _LOGGER:
|
||||||
from evennia.utils import logger as _LOGGER
|
from evennia.utils import logger as _LOGGER
|
||||||
_LOGGER.log_trace()
|
_LOGGER.log_trace()
|
||||||
|
print("error", err)
|
||||||
raise # make sure the error is visible on the other side of the connection too
|
raise # make sure the error is visible on the other side of the connection too
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,25 @@ communication to the AMP clients connecting to it (by default
|
||||||
these are the Evennia Server and the evennia launcher).
|
these are the Evennia Server and the evennia launcher).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
from twisted.internet import protocol
|
from twisted.internet import protocol
|
||||||
from evennia.server.portal import amp
|
from evennia.server.portal import amp
|
||||||
|
from subprocess import Popen
|
||||||
|
|
||||||
|
|
||||||
|
def getenv():
|
||||||
|
"""
|
||||||
|
Get current environment and add PYTHONPATH.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
env (dict): Environment global dict.
|
||||||
|
|
||||||
|
"""
|
||||||
|
sep = ";" if os.name == 'nt' else ":"
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['PYTHONPATH'] = sep.join(sys.path)
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
class AMPServerFactory(protocol.ServerFactory):
|
class AMPServerFactory(protocol.ServerFactory):
|
||||||
|
|
@ -66,6 +83,20 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
||||||
sessiondata=sessdata)
|
sessiondata=sessdata)
|
||||||
self.factory.portal.sessions.at_server_connection()
|
self.factory.portal.sessions.at_server_connection()
|
||||||
|
|
||||||
|
def start_server(self, server_twistd_cmd):
|
||||||
|
"""
|
||||||
|
(Re-)Launch the Evennia server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_twisted_cmd (list): The server start instruction
|
||||||
|
to pass to POpen to start the server.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# start the server
|
||||||
|
process = Popen(server_twistd_cmd, env=getenv())
|
||||||
|
# store the pid for future reference
|
||||||
|
self.portal.server_process_id = process.pid
|
||||||
|
|
||||||
# sending amp data
|
# sending amp data
|
||||||
|
|
||||||
def send_MsgPortal2Server(self, session, **kwargs):
|
def send_MsgPortal2Server(self, session, **kwargs):
|
||||||
|
|
@ -103,16 +134,25 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
||||||
@amp.catch_traceback
|
@amp.catch_traceback
|
||||||
def portal_receive_status(self, status):
|
def portal_receive_status(self, status):
|
||||||
"""
|
"""
|
||||||
Check if Server is running
|
Returns run-status for the server/portal.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status (str): Not used.
|
||||||
|
Returns:
|
||||||
|
status (dict): The status is a tuple
|
||||||
|
(portal_running, server_running, portal_pid, server_pid).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# check if the server is connected
|
# check if the server is connected
|
||||||
server_connected = any(1 for prtcl in self.factory.broadcasts
|
server_connected = any(1 for prtcl in self.factory.broadcasts
|
||||||
if prtcl is not self and prtcl.transport.connected)
|
if prtcl is not self and prtcl.transport.connected)
|
||||||
# return portal|server RUNNING/NOT RUNNING
|
server_pid = self.factory.portal.server_process_id
|
||||||
|
portal_pid = os.getpid()
|
||||||
|
|
||||||
if server_connected:
|
if server_connected:
|
||||||
return {"status": "RUNNING|RUNNING"}
|
return {"status": amp.dumps((True, True, portal_pid, server_pid))}
|
||||||
else:
|
else:
|
||||||
return {"status": "RUNNING|NOT RUNNING"}
|
return {"status": amp.dumps((True, False, portal_pid, server_pid))}
|
||||||
|
|
||||||
@amp.MsgLauncher2Portal.responder
|
@amp.MsgLauncher2Portal.responder
|
||||||
@amp.catch_traceback
|
@amp.catch_traceback
|
||||||
|
|
@ -140,10 +180,10 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
||||||
print("AMP SERVER arguments: %s" % (amp.loads(arguments)))
|
print("AMP SERVER arguments: %s" % (amp.loads(arguments)))
|
||||||
return {"result": "Received."}
|
return {"result": "Received."}
|
||||||
|
|
||||||
if operation == amp.SSTART: # portal start (server start or reload)
|
if operation == amp.SSTART: # portal start
|
||||||
# first, check if server is already running
|
# first, check if server is already running
|
||||||
if server_connected:
|
if server_connected:
|
||||||
return {"result": "Server already running (PID {}).".format(0)} # TODO store and send PID
|
return {"result": "Server already running at PID={}"}
|
||||||
else:
|
else:
|
||||||
self.start_server(amp.loads(arguments))
|
self.start_server(amp.loads(arguments))
|
||||||
return {"result": "Server started with PID {}.".format(0)} # TODO
|
return {"result": "Server started with PID {}.".format(0)} # TODO
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ class Portal(object):
|
||||||
self.amp_protocol = None # set by amp factory
|
self.amp_protocol = None # set by amp factory
|
||||||
self.sessions = PORTAL_SESSIONS
|
self.sessions = PORTAL_SESSIONS
|
||||||
self.sessions.portal = self
|
self.sessions.portal = self
|
||||||
|
self.process_id = os.getpid()
|
||||||
|
self.server_process_id = None
|
||||||
|
|
||||||
# set a callback if the server is killed abruptly,
|
# set a callback if the server is killed abruptly,
|
||||||
# by Ctrl-C, reboot etc.
|
# by Ctrl-C, reboot etc.
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ class Evennia(object):
|
||||||
self.amp_protocol = None # set by amp factory
|
self.amp_protocol = None # set by amp factory
|
||||||
self.sessions = SESSIONS
|
self.sessions = SESSIONS
|
||||||
self.sessions.server = self
|
self.sessions.server = self
|
||||||
|
self.process_id = os.getpid()
|
||||||
|
|
||||||
# Database-specific startup optimizations.
|
# Database-specific startup optimizations.
|
||||||
self.sqlite3_prep()
|
self.sqlite3_prep()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue