mirror of
https://github.com/evennia/evennia.git
synced 2026-03-29 12:07:17 +02:00
Further work on controlling the portal with AMP
This commit is contained in:
parent
1b9e5f21c4
commit
b2f4348049
5 changed files with 113 additions and 58 deletions
|
|
@ -463,7 +463,7 @@ class MsgLauncher2Portal(amp.Command):
|
|||
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.
|
||||
|
||||
|
|
@ -484,20 +484,15 @@ def send_instruction(operation, arguments, callback=None, errback=None, autostop
|
|||
if callback:
|
||||
callback(result)
|
||||
prot.transport.loseConnection()
|
||||
if autostop:
|
||||
reactor.stop()
|
||||
|
||||
def _errback(fail):
|
||||
if errback:
|
||||
errback(fail)
|
||||
prot.transport.loseConnection()
|
||||
if autostop:
|
||||
reactor.stop()
|
||||
|
||||
if operation == PSTATUS:
|
||||
prot.callRemote(MsgStatus, status="").addCallbacks(_callback, _errback)
|
||||
else:
|
||||
print("callRemote MsgLauncher %s %s" % (ord(operation), arguments))
|
||||
prot.callRemote(
|
||||
MsgLauncher2Portal,
|
||||
operation=operation,
|
||||
|
|
@ -507,8 +502,6 @@ def send_instruction(operation, arguments, callback=None, errback=None, autostop
|
|||
def _on_connect_fail(fail):
|
||||
"This is called if portal is not reachable."
|
||||
errback(fail)
|
||||
if autostop:
|
||||
reactor.stop()
|
||||
|
||||
point = endpoints.TCP4ClientEndpoint(reactor, AMP_HOST, AMP_PORT)
|
||||
deferred = endpoints.connectProtocol(point, amp.AMP())
|
||||
|
|
@ -516,20 +509,55 @@ def send_instruction(operation, arguments, callback=None, errback=None, autostop
|
|||
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):
|
||||
pstatus, sstatus = response['status'].split("|")
|
||||
print("Portal: {}\nServer: {}".format(pstatus, sstatus))
|
||||
pstatus, sstatus, ppid, spid = _parse_status(response)
|
||||
print("Portal: {} (pid {})\nServer: {} (pid {})".format(
|
||||
wmap[pstatus], ppid, wmap[sstatus], spid))
|
||||
reactor.stop()
|
||||
|
||||
def _errback(fail):
|
||||
pstatus, sstatus = "NOT RUNNING", "NOT RUNNING"
|
||||
print("Portal: {}\nServer: {}".format(pstatus, sstatus))
|
||||
print("status fail: %s", fail)
|
||||
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()
|
||||
|
||||
|
||||
|
|
@ -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`.
|
||||
"""
|
||||
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
|
||||
(server_running is None or srun == server_running)):
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# 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):
|
||||
"""
|
||||
This will start Evennia anew by launching the Evennia Portal (which in turn
|
||||
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):
|
||||
print("... Server started.\nEvennia running.")
|
||||
print("... Server started.\nEvennia running.", args)
|
||||
reactor.stop()
|
||||
|
||||
def _portal_started(*args):
|
||||
send_instruction(SSTART, server_cmd, _server_started)
|
||||
|
||||
def _portal_running(response):
|
||||
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
||||
print("Portal is already running as process {pid}. Not restarted.".format(pid=0)) # TODO PID
|
||||
if server_running:
|
||||
print("Server is already running as process {pid}. Not restarted.".format(pid=0)) # TODO PID
|
||||
prun, srun, ppid, spid = _parse_status(response)
|
||||
print("Portal is already running as process {pid}. Not restarted.".format(pid=ppid))
|
||||
if srun:
|
||||
print("Server is already running as process {pid}. Not restarted.".format(pid=spid))
|
||||
reactor.stop()
|
||||
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):
|
||||
print("Portal starting {}...".format("(under cProfile)" if sprofiler else ""))
|
||||
try:
|
||||
Popen(portal_cmd)
|
||||
Popen(portal_cmd, env=getenv())
|
||||
except Exception as e:
|
||||
print(PROCESS_ERROR.format(component="Portal", traceback=e))
|
||||
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.
|
||||
|
||||
"""
|
||||
_, server_cmd = get_twistd_cmdline(False, sprofiler)
|
||||
_, server_cmd = _get_twistd_cmdline(False, sprofiler)
|
||||
|
||||
def _server_restarted(*args):
|
||||
print("... Server re-started.", args)
|
||||
|
|
@ -673,8 +683,8 @@ def reload_evennia(sprofiler=False):
|
|||
reactor.stop()
|
||||
|
||||
def _portal_running(response):
|
||||
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
||||
if server_running:
|
||||
_, srun, _, _ = _parse_status(response)
|
||||
if srun:
|
||||
print("Server reloading ...")
|
||||
send_instruction(SRELOAD, server_cmd, _server_reloaded)
|
||||
else:
|
||||
|
|
@ -700,16 +710,17 @@ def stop_evennia():
|
|||
reactor.stop()
|
||||
|
||||
def _server_stopped(*args):
|
||||
print("... Server stopped.", args)
|
||||
print("... Server stopped.\nStopping Portal ...", args)
|
||||
send_instruction(PSHUTD, {})
|
||||
wait_for_status(False, None, _portal_stopped)
|
||||
|
||||
def _portal_running(response):
|
||||
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
||||
if server_running:
|
||||
prun, srun, ppid, spid = _parse_status(response)
|
||||
if srun:
|
||||
print("Server stopping ...")
|
||||
send_instruction(SSHUTD, {}, _server_stopped)
|
||||
else:
|
||||
print("Server already stopped.\nStopping Portal ...")
|
||||
send_instruction(PSHUTD, {})
|
||||
wait_for_status(False, False, _portal_stopped)
|
||||
|
||||
|
|
@ -1647,7 +1658,7 @@ def main():
|
|||
|
||||
if args.get_status:
|
||||
init_game_directory(CURRENT_DIR, check_db=True)
|
||||
send_status()
|
||||
query_status()
|
||||
sys.exit()
|
||||
|
||||
if args.dummyrunner:
|
||||
|
|
|
|||
|
|
@ -83,11 +83,12 @@ def catch_traceback(func):
|
|||
def decorator(*args, **kwargs):
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
except Exception:
|
||||
except Exception as err:
|
||||
global _LOGGER
|
||||
if not _LOGGER:
|
||||
from evennia.utils import logger as _LOGGER
|
||||
_LOGGER.log_trace()
|
||||
print("error", err)
|
||||
raise # make sure the error is visible on the other side of the connection too
|
||||
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).
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from twisted.internet import protocol
|
||||
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):
|
||||
|
|
@ -66,6 +83,20 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
sessiondata=sessdata)
|
||||
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
|
||||
|
||||
def send_MsgPortal2Server(self, session, **kwargs):
|
||||
|
|
@ -103,16 +134,25 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
@amp.catch_traceback
|
||||
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
|
||||
server_connected = any(1 for prtcl in self.factory.broadcasts
|
||||
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:
|
||||
return {"status": "RUNNING|RUNNING"}
|
||||
return {"status": amp.dumps((True, True, portal_pid, server_pid))}
|
||||
else:
|
||||
return {"status": "RUNNING|NOT RUNNING"}
|
||||
return {"status": amp.dumps((True, False, portal_pid, server_pid))}
|
||||
|
||||
@amp.MsgLauncher2Portal.responder
|
||||
@amp.catch_traceback
|
||||
|
|
@ -140,10 +180,10 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
print("AMP SERVER arguments: %s" % (amp.loads(arguments)))
|
||||
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
|
||||
if server_connected:
|
||||
return {"result": "Server already running (PID {}).".format(0)} # TODO store and send PID
|
||||
return {"result": "Server already running at PID={}"}
|
||||
else:
|
||||
self.start_server(amp.loads(arguments))
|
||||
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.sessions = PORTAL_SESSIONS
|
||||
self.sessions.portal = self
|
||||
self.process_id = os.getpid()
|
||||
self.server_process_id = None
|
||||
|
||||
# set a callback if the server is killed abruptly,
|
||||
# by Ctrl-C, reboot etc.
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ class Evennia(object):
|
|||
self.amp_protocol = None # set by amp factory
|
||||
self.sessions = SESSIONS
|
||||
self.sessions.server = self
|
||||
self.process_id = os.getpid()
|
||||
|
||||
# Database-specific startup optimizations.
|
||||
self.sqlite3_prep()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue