From 5741eef9bc044d2fb41d85ba70e0913b2809a7a7 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 14 Jan 2018 14:02:34 +0100 Subject: [PATCH] Parallel start/stop/reload systems, for testing --- evennia/commands/default/system.py | 4 +- evennia/server/amp_client.py | 27 +++++++--- evennia/server/portal/amp.py | 20 ++++++-- evennia/server/portal/amp_server.py | 78 ++++++++++++++++++++--------- evennia/server/sessionhandler.py | 19 +++++-- 5 files changed, 109 insertions(+), 39 deletions(-) diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index fe5efa337d..7933245ae2 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -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): diff --git a/evennia/server/amp_client.py b/evennia/server/amp_client.py index a76245912b..bf6b3d9fc8 100644 --- a/evennia/server/amp_client.py +++ b/evennia/server/amp_client.py @@ -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 {} diff --git a/evennia/server/portal/amp.py b/evennia/server/portal/amp.py index 2887aab8ec..8f08b46b0f 100644 --- a/evennia/server/portal/amp.py +++ b/evennia/server/portal/amp.py @@ -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) diff --git a/evennia/server/portal/amp_server.py b/evennia/server/portal/amp_server.py index 20a73a70a7..8193ac0663 100644 --- a/evennia/server/portal/amp_server.py +++ b/evennia/server/portal/amp_server.py @@ -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. diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 9862f03421..20e4cd677e 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -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.