From 3fde374703a3a2ff4c761796dcd5dfc94edfc4c1 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 13 Jan 2018 15:22:51 +0100 Subject: [PATCH] Add ability to ping Portal from launcher over AMP --- evennia/server/evennia_launcher.py | 127 +++++++++++++++++++++++++++- evennia/server/portal/amp.py | 18 ++-- evennia/server/portal/amp_server.py | 8 +- 3 files changed, 137 insertions(+), 16 deletions(-) diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 9ccf664410..94395f09c4 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -20,6 +20,8 @@ import importlib from distutils.version import LooseVersion from argparse import ArgumentParser from subprocess import Popen, check_output, call, CalledProcessError, STDOUT +from twisted.protocols import amp +from twisted.internet import reactor, endpoints import django # Signal processing @@ -49,18 +51,32 @@ CURRENT_DIR = os.getcwd() GAMEDIR = CURRENT_DIR # Operational setup +AMP_PORT = None +AMP_HOST = None +AMP_INTERFACE = None + SERVER_LOGFILE = None PORTAL_LOGFILE = None HTTP_LOGFILE = None + SERVER_PIDFILE = None PORTAL_PIDFILE = None SERVER_RESTART = None PORTAL_RESTART = None + SERVER_PY_FILE = None PORTAL_PY_FILE = None + TEST_MODE = False ENFORCED_SETTING = False +# communication constants + +SRELOAD = chr(14) # server reloading (have portal start a new server) +PSTART = chr(15) # server+portal start +PSHUTD = chr(16) # portal (+server) shutdown +PSTATUS = chr(17) # ping server or portal status + # requirements PYTHON_MIN = '2.7' TWISTED_MIN = '16.0.0' @@ -303,6 +319,15 @@ MENU = \ +---------------------------------------------------------------+ """ +ERROR_AMP_UNCONFIGURED = \ + """ + Can't find server info for connecting. Either run this command from + the game dir (it will then use the game's settings file) or specify + the path to your game's settings file manually with the --settings + option. + + """ + ERROR_LOGDIR_MISSING = \ """ ERROR: One or more log-file directory locations could not be @@ -391,11 +416,94 @@ NOTE_TEST_CUSTOM = \ on the game dir.) """ -#------------------------------------------------------------ + +# ------------------------------------------------------------ # -# Functions +# Protocol Evennia launcher - Portal/Server communication # -#------------------------------------------------------------ +# ------------------------------------------------------------ + + +class MsgStatus(amp.Command): + """ + Ping between AMP services + + """ + key = "AMPPing" + arguments = [('question', amp.String())] + errors = {Exception: 'EXCEPTION'} + response = [('status', amp.String())] + + +class MsgLauncher2Portal(amp.Command): + """ + Message Launcher -> Portal + + """ + key = "MsgLauncher2Portal" + arguments = [('operation', amp.String()), + ('argument', amp.String())] + errors = {Exception: 'EXCEPTION'} + response = [('result', amp.String())] + + +def send_instruction(instruction, argument, callback, errback): + """ + Send instruction and handle the response. + + """ + if None in (AMP_HOST, AMP_PORT, AMP_INTERFACE): + print(ERROR_AMP_UNCONFIGURED) + sys.exit() + + def _on_connect(prot): + """ + This fires with the protocol when connection is established. We + immediately send off the instruction then shut down. + + """ + def _callback(result): + callback(result) + prot.transport.loseConnection() + reactor.stop() + + def _errback(fail): + errback(fail) + prot.transport.loseConnection() + reactor.stop() + + if instruction == PSTATUS: + prot.callRemote(MsgStatus, question="").addCallbacks(_callback, _errback) + else: + prot.callRemote(MsgLauncher2Portal, instruction, argument).addCallbacks( + _callback, _errback) + + point = endpoints.TCP4ClientEndpoint(reactor, AMP_HOST, AMP_PORT) + deferred = endpoints.connectProtocol(point, amp.AMP()) + deferred.addCallbacks(_on_connect, errback) + reactor.run() + + +def send_status(): + """ + Send ping to portal + + """ + import time + t0 = time.time() + def _callback(status): + print("STATUS returned: %s (%gms)" % (status, (time.time()-t0) * 1000)) + + def _errback(err): + print("STATUS returned: %s" % err) + + send_instruction(PSTATUS, None, _callback, _errback) + +# ------------------------------------------------------------ +# +# Helper functions +# +# ------------------------------------------------------------ def evennia_version(): @@ -869,12 +977,17 @@ def init_game_directory(path, check_db=True): check_database() # set up the Evennia executables and log file locations + global AMP_PORT, AMP_HOST, AMP_INTERFACE global SERVER_PY_FILE, PORTAL_PY_FILE global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE global SERVER_PIDFILE, PORTAL_PIDFILE global SERVER_RESTART, PORTAL_RESTART global EVENNIA_VERSION + AMP_PORT = settings.AMP_PORT + AMP_HOST = settings.AMP_HOST + AMP_INTERFACE = settings.AMP_INTERFACE + SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py") PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "portal", "portal", "portal.py") @@ -1239,6 +1352,9 @@ def main(): "service", metavar="component", nargs='?', default="all", help=("Which component to operate on: " "'server', 'portal' or 'all' (default if not set).")) + parser.add_argument( + "--status", action='store_true', dest='get_status', + default=None, help='Get current server status.') parser.epilog = ( "Common usage: evennia start|stop|reload. Django-admin database commands:" "evennia migration|flush|shell|dbshell (see the django documentation for more django-admin commands.)") @@ -1289,6 +1405,11 @@ def main(): print(ERROR_INITSETTINGS) sys.exit() + if args.get_status: + init_game_directory(CURRENT_DIR, check_db=True) + send_status() + sys.exit() + if args.dummyrunner: # launch the dummy runner init_game_directory(CURRENT_DIR, check_db=True) diff --git a/evennia/server/portal/amp.py b/evennia/server/portal/amp.py index 0d3537cd85..1578b8f1f4 100644 --- a/evennia/server/portal/amp.py +++ b/evennia/server/portal/amp.py @@ -40,7 +40,7 @@ PDISCONNALL = chr(13) # portal session disconnect all SRELOAD = chr(14) # server reloading (have portal start a new server) PSTART = chr(15) # server+portal start PSHUTD = chr(16) # portal (+server) shutdown -PPING = chr(17) # server or portal status +PSTATUS = chr(17) # ping server or portal status AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) BATCH_RATE = 250 # max commands/sec before switching to batch-sending @@ -205,15 +205,15 @@ class AdminServer2Portal(amp.Command): response = [] -class MsgPing(amp.Command): +class MsgStatus(amp.Command): """ - Ping between AMP services + Check Status between AMP services """ key = "AMPPing" - arguments = [('ping', amp.Boolean())] + arguments = [('question', amp.String())] errors = {Exception: 'EXCEPTION'} - response = [('pong', amp.Boolean())] + response = [('status', amp.String())] class FunctionCall(amp.Command): @@ -271,9 +271,7 @@ class AMPMultiConnectionProtocol(amp.AMP): def connectionMade(self): """ - This is called when an AMP connection is (re-)established - between server and portal. AMP calls it on both sides, so we - need to make sure to only trigger resync from the portal side. + This is called when an AMP connection is (re-)established AMP calls it on both sides. """ self.factory.broadcasts.append(self) @@ -344,7 +342,7 @@ class AMPMultiConnectionProtocol(amp.AMP): self.errback, command.key)) return DeferredList(deferreds) - def send_ping(self, port, callback, errback): + def send_status(self, port, callback, errback): """ Ping to the given AMP port. @@ -357,7 +355,7 @@ class AMPMultiConnectionProtocol(amp.AMP): targets = [(protcl, protcl.getHost()[1]) for protcl in self.factory.broadcasts] deferreds = [] for protcl, port in ((protcl, prt) for protcl, prt in targets if prt == port): - deferreds.append(protcl.callRemote(MsgPing, ping=True).addCallback( + deferreds.append(protcl.callRemote(MsgStatus, status=True).addCallback( callback, port).addErrback(errback, port)) return DeferredList(deferreds) diff --git a/evennia/server/portal/amp_server.py b/evennia/server/portal/amp_server.py index 8915feef8a..343680aa89 100644 --- a/evennia/server/portal/amp_server.py +++ b/evennia/server/portal/amp_server.py @@ -104,6 +104,10 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): # receive amp data + @amp.MsgStatus.responder + def portal_receive_status(self, question): + return {"status": "All well"} + @amp.MsgLauncher2Portal.responder @amp.catch_traceback def portal_receive_launcher2portal(self, operation, argument): @@ -123,9 +127,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): evennia launcher. """ - if operation == amp.PPING: # check portal and server status - pass - elif operation == amp.PSTART: # portal start (server start or reload) + if operation == amp.PSTART: # portal start (server start or reload) pass elif operation == amp.SRELOAD: # reload server pass