Add ability to ping Portal from launcher over AMP

This commit is contained in:
Griatch 2018-01-13 15:22:51 +01:00
parent d89fbf1943
commit 3fde374703
3 changed files with 137 additions and 16 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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