Parallel start/stop/reload systems, for testing

This commit is contained in:
Griatch 2018-01-14 14:02:34 +01:00
parent ff887a07ab
commit 5741eef9bc
5 changed files with 109 additions and 39 deletions

View file

@ -58,7 +58,7 @@ class CmdReload(COMMAND_DEFAULT_CLASS):
if self.args:
reason = "(Reason: %s) " % self.args.rstrip(".")
SESSIONS.announce_all(" Server restart initiated %s..." % reason)
SESSIONS.server.shutdown(mode='reload')
SESSIONS.portal_restart_server()
class CmdReset(COMMAND_DEFAULT_CLASS):
@ -91,7 +91,7 @@ class CmdReset(COMMAND_DEFAULT_CLASS):
Reload the system.
"""
SESSIONS.announce_all(" Server resetting/restarting ...")
SESSIONS.server.shutdown(mode='reset')
SESSIONS.portal_reset_server()
class CmdShutdown(COMMAND_DEFAULT_CLASS):

View file

@ -96,6 +96,16 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
"""
# sending AMP data
def connectionMade(self):
"""
Called when a new connection is established.
"""
super(AMPServerClientProtocol, self).connectionMade()
# first thing we do is to request the Portal to sync all sessions
# back with the Server side
self.send_AdminServer2Portal(amp.DUMMYSESSION, operation=amp.PSYNC)
def send_MsgServer2Portal(self, session, **kwargs):
"""
Access method - executed on the Server for sending data
@ -118,7 +128,7 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
operation (char, optional): Identifier for the server
operation, as defined by the global variables in
`evennia/server/amp.py`.
data (str or dict, optional): Data going into the adminstrative.
kwargs (dict, optional): Data going into the adminstrative.
"""
return self.data_out(amp.AdminServer2Portal, session.sessid, operation=operation, **kwargs)
@ -180,12 +190,17 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
server_sessionhandler.portal_disconnect_all()
elif operation == amp.PSYNC: # portal_session_sync
# force a resync of sessions when portal reconnects to
# server (e.g. after a server reboot) the data kwarg
# contains a dict {sessid: {arg1:val1,...}}
# representing the attributes to sync for each
# session.
# force a resync of sessions from the portal side
server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata"))
elif operation == amp.SRELOAD: # server reload
# shut down in reload mode
server_sessionhandler.server.shutdown(mode='reload')
elif operation == amp.SRESET:
# shut down in reset mode
server_sessionhandler.server.shutdown(mode='reset')
elif operation == amp.SSHUTD: # server shutdown
# shutdown in stop mode
server_sessionhandler.server.shutdown(mode='shutdown')
else:
raise Exception("operation %(op)s not recognized." % {'op': operation})
return {}

View file

@ -37,11 +37,12 @@ SSYNC = chr(8) # server session sync
SCONN = chr(11) # server creating new connection (for irc bots and etc)
PCONNSYNC = chr(12) # portal post-syncing a session
PDISCONNALL = chr(13) # portal session disconnect all
SRELOAD = chr(14) # server reloading (have portal start a new server)
SRELOAD = chr(14) # server shutdown in reload mode
SSTART = chr(15) # server start (portal must already be running anyway)
PSHUTD = chr(16) # portal (+server) shutdown
SSHUTD = chr(17) # server-only shutdown
SSHUTD = chr(17) # server shutdown
PSTATUS = chr(18) # ping server or portal status
SRESET = chr(19) # server shutdown in reset mode
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
@ -271,9 +272,22 @@ class AMPMultiConnectionProtocol(amp.AMP):
else:
super(AMPMultiConnectionProtocol, self).dataReceived(data)
def makeConnection(self, transport):
"""
Swallow connection log message here. Copied from original
in the amp protocol.
"""
# copied from original, removing the log message
if not self._ampInitialized:
amp.AMP.__init__(self)
self._transportPeer = transport.getPeer()
self._transportHost = transport.getHost()
amp.BinaryBoxProtocol.makeConnection(self, transport)
def connectionMade(self):
"""
This is called when an AMP connection is (re-)established AMP calls it on both sides.
This is called when an AMP connection is (re-)established. AMP calls it on both sides.
"""
self.factory.broadcasts.append(self)

View file

@ -9,6 +9,7 @@ import sys
from twisted.internet import protocol
from evennia.server.portal import amp
from subprocess import Popen
from evennia.utils import logger
def getenv():
@ -69,20 +70,6 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
Protocol subclass for the AMP-server run by the Portal.
"""
def connectionMade(self):
"""
Called when a new connection is established.
"""
super(AMPServerProtocol, self).connectionMade()
if len(self.factory.broadcasts) < 2:
sessdata = self.factory.portal.sessions.get_all_sync_data()
self.send_AdminPortal2Server(amp.DUMMYSESSION,
amp.PSYNC,
sessiondata=sessdata)
self.factory.portal.sessions.at_server_connection()
def start_server(self, server_twistd_cmd):
"""
(Re-)Launch the Evennia server.
@ -92,10 +79,29 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
to pass to POpen to start the server.
"""
# 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
self.factory.portal.server_process_id = process.pid
self.factory.portal.server_twistd_cmd = server_twistd_cmd
return process.pid
def stop_server(self, mode='reload'):
"""
Shut down server in one or more modes.
Args:
mode (str): One of 'shutdown', 'reload' or 'reset'.
"""
if mode == 'reload':
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SRELOAD)
return self.start_server(self.factory.portal.server_twistd_cmd)
if mode == 'reset':
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SRESET)
return self.start_server(self.factory.portal.server_twistd_cmd)
if mode == 'shutdown':
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SSHUTD)
# sending amp data
@ -173,28 +179,44 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
launcher. It can obviously only accessed when the Portal is already up and running.
"""
def _retval(success, txt):
return {"result": amp.dumps((success, txt))}
server_connected = any(1 for prtcl in self.factory.broadcasts
if prtcl is not self and prtcl.transport.connected)
server_pid = self.factory.portal.server_process_id
print("AMP SERVER operation == %s received" % (ord(operation)))
print("AMP SERVER arguments: %s" % (amp.loads(arguments)))
return {"result": "Received."}
logger.log_msg("AMP SERVER operation == %s received" % (ord(operation)))
logger.log_msg("AMP SERVER arguments: %s" % (amp.loads(arguments)))
if operation == amp.SSTART: # portal start
# first, check if server is already running
if server_connected:
return {"result": "Server already running at PID={}"}
return _retval(False,
"Server already running at PID={spid}".format(spid=server_pid))
else:
self.start_server(amp.loads(arguments))
return {"result": "Server started with PID {}.".format(0)} # TODO
spid = self.start_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
elif operation == amp.SRELOAD: # reload server
if server_connected:
self.reload_server(amp.loads(arguments))
spid = self.reload_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
else:
self.start_server(amp.loads(arguments))
spid = self.start_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
elif operation == amp.SRESET: # reload server
if server_connected:
spid = self.reload_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
else:
self.start_server(amp.loads(arguments))
spid = self.start_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
elif operation == amp.PSHUTD: # portal + server shutdown
if server_connected:
self.stop_server(amp.loads(arguments))
self.stop_server()
return _retval(True, "Server stopped.")
self.factory.portal.shutdown(restart=False)
else:
raise Exception("operation %(op)s not recognized." % {'op': operation})
@ -257,6 +279,14 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
elif operation == amp.SRELOAD: # server reload
self.factory.portal.server_reload(**kwargs)
elif operation == amp.PSYNC: # portal sync
# Server has (re-)connected and wants the session data from portal
sessdata = self.factory.portal.sessions.get_all_sync_data()
self.send_AdminPortal2Server(amp.DUMMYSESSION,
amp.PSYNC,
sessiondata=sessdata)
self.factory.portal.sessions.at_server_connection()
elif operation == amp.SSYNC: # server_session_sync
# server wants to save session data to the portal,
# maybe because it's about to shut down.

View file

@ -59,7 +59,11 @@ SCONN = chr(11) # server portal connection (for bots)
PCONNSYNC = chr(12) # portal post-syncing session
PDISCONNALL = chr(13) # portal session discnnect all
SRELOAD = chr(14) # server reloading (have portal start a new server)
SSTART = chr(15) # server start (portal must already be running anyway)
PSHUTD = chr(16) # portal (+server) shutdown
SSHUTD = chr(17) # server shutdown
PSTATUS = chr(18) # ping server or portal status
SRESET = chr(19) # server shutdown in reset mode
# i18n
from django.utils.translation import ugettext as _
@ -439,10 +443,19 @@ class ServerSessionHandler(SessionHandler):
Called by server when reloading. We tell the portal to start a new server instance.
"""
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SRELOAD)
def portal_reset_server(self):
"""
Called by server when reloading. We tell the portal to start a new server instance.
"""
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SRESET)
def portal_shutdown(self):
"""
Called by server when shutting down the portal (usually because server is going down too).
Called by server when it's time to shut down (the portal will shut us down and then shut
itself down)
"""
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
@ -572,8 +585,6 @@ class ServerSessionHandler(SessionHandler):
sessiondata=session_data,
clean=False)
def disconnect_all_sessions(self, reason="You have been disconnected."):
"""
Cleanly disconnect all of the connected sessions.