375 lines
12 KiB
Python
375 lines
12 KiB
Python
"""
|
|
Defines server classes and request handlers for TCP and UDP. Also defined here is a TCP based
|
|
auxiliary server class for supporting externally defined handlers.
|
|
"""
|
|
|
|
import SocketServer
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
from core import logger
|
|
from core.api import coreapi
|
|
from core.enumerations import EventTypes
|
|
from core.enumerations import SessionTlvs
|
|
from core.session import Session
|
|
|
|
|
|
class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
|
|
"""
|
|
TCP server class, manages sessions and spawns request handlers for
|
|
incoming connections.
|
|
"""
|
|
daemon_threads = True
|
|
allow_reuse_address = True
|
|
servers = set()
|
|
|
|
def __init__(self, server_address, handler_class, config=None):
|
|
"""
|
|
Server class initialization takes configuration data and calls
|
|
the SocketServer constructor
|
|
|
|
:param tuple[str, int] server_address: server host and port to use
|
|
:param class handler_class: request handler
|
|
:param dict config: configuration setting
|
|
:return:
|
|
"""
|
|
self.config = config
|
|
self.sessions = {}
|
|
self.udpserver = None
|
|
self.udpthread = None
|
|
self.auxserver = None
|
|
self.auxthread = None
|
|
self._sessions_lock = threading.Lock()
|
|
CoreServer.add_server(self)
|
|
SocketServer.TCPServer.__init__(self, server_address, handler_class)
|
|
|
|
@classmethod
|
|
def add_server(cls, server):
|
|
"""
|
|
Add a core server to the known servers set.
|
|
|
|
:param CoreServer server: server to add
|
|
:return: nothing
|
|
"""
|
|
cls.servers.add(server)
|
|
|
|
@classmethod
|
|
def remove_server(cls, server):
|
|
"""
|
|
Remove a core server from the known servers set.
|
|
|
|
:param CoreServer server: server to remove
|
|
:return: nothing
|
|
"""
|
|
if server in cls.servers:
|
|
cls.servers.remove(server)
|
|
|
|
def shutdown(self):
|
|
"""
|
|
Shutdown the server, all known sessions, and remove server from known servers set.
|
|
|
|
:return: nothing
|
|
"""
|
|
# shutdown all known sessions
|
|
for session in self.sessions.values():
|
|
session.shutdown()
|
|
|
|
# if we are a daemon remove pid file
|
|
if self.config["daemonize"]:
|
|
pid_file = self.config["pidfile"]
|
|
try:
|
|
os.unlink(pid_file)
|
|
except OSError:
|
|
logger.exception("error daemon pid file: %s", pid_file)
|
|
|
|
# remove server from server list
|
|
CoreServer.remove_server(self)
|
|
|
|
def add_session(self, session):
|
|
"""
|
|
Add a session to our dictionary of sessions, ensuring a unique session number.
|
|
|
|
:param core.session.Session session: session to add
|
|
:return: added session
|
|
:raise KeyError: when a session with the same id already exists
|
|
"""
|
|
with self._sessions_lock:
|
|
if session.session_id in self.sessions:
|
|
raise KeyError("non-unique session id %s for %s" % (session.session_id, session))
|
|
self.sessions[session.session_id] = session
|
|
|
|
return session
|
|
|
|
def remove_session(self, session):
|
|
"""
|
|
Remove a session from our dictionary of sessions.
|
|
|
|
:param core.session.Session session: session to remove
|
|
:return: removed session
|
|
:rtype: core.session.Session
|
|
"""
|
|
with self._sessions_lock:
|
|
if session.session_id not in self.sessions:
|
|
logger.info("session id %s not found (sessions=%s)", session.session_id, self.sessions.keys())
|
|
else:
|
|
del self.sessions[session.session_id]
|
|
|
|
return session
|
|
|
|
def get_session_ids(self):
|
|
"""
|
|
Return a list of active session numbers.
|
|
|
|
:return: known session ids
|
|
:rtype: list
|
|
"""
|
|
with self._sessions_lock:
|
|
session_ids = self.sessions.keys()
|
|
|
|
return session_ids
|
|
|
|
def create_session(self, session_id=None):
|
|
"""
|
|
Convenience method for creating sessions with the servers config.
|
|
|
|
:param int session_id: session id for new session
|
|
:return: create session
|
|
:rtype: core.session.Session
|
|
"""
|
|
|
|
# create random id when necessary, seems to be 1 case wanted, based on legacy code
|
|
# creating a value so high, typical client side generation schemes hopefully wont collide
|
|
if not session_id:
|
|
session_id = next(
|
|
session_id for session_id in xrange(60000, 65000)
|
|
if session_id not in self.sessions
|
|
)
|
|
|
|
# create and add session to local manager
|
|
session = Session(session_id, config=self.config)
|
|
self.add_session(session)
|
|
|
|
# add shutdown handler to remove session from manager
|
|
session.shutdown_handlers.append(self.session_shutdown)
|
|
|
|
return session
|
|
|
|
def get_session(self, session_id=None):
|
|
"""
|
|
Create a new session or retrieve an existing one from our
|
|
dictionary of sessions. When the session_id=0 and the use_existing
|
|
flag is set, return on of the existing sessions.
|
|
|
|
:param int session_id: session id of session to retrieve, defaults to returning random session
|
|
:return: session
|
|
:rtype: core.session.Session
|
|
"""
|
|
|
|
with self._sessions_lock:
|
|
# return specified session or none
|
|
if session_id:
|
|
return self.sessions.get(session_id)
|
|
|
|
# retrieving known session
|
|
session = None
|
|
|
|
# find runtime session with highest node count
|
|
for known_session in filter(lambda x: x.state == EventTypes.RUNTIME_STATE.value,
|
|
self.sessions.itervalues()):
|
|
if not session or known_session.get_node_count() > session.get_node_count():
|
|
session = known_session
|
|
|
|
# return first known session otherwise
|
|
if not session:
|
|
for known_session in self.sessions.itervalues():
|
|
session = known_session
|
|
break
|
|
|
|
return session
|
|
|
|
def session_shutdown(self, session):
|
|
"""
|
|
Handler method to be used as a callback when a session has shutdown.
|
|
|
|
:param core.session.Session session: session shutting down
|
|
:return: nothing
|
|
"""
|
|
self.remove_session(session)
|
|
|
|
def to_session_message(self, flags=0):
|
|
"""
|
|
Build CORE API Sessions message based on current session info.
|
|
|
|
:param int flags: message flags
|
|
:return: session message
|
|
"""
|
|
id_list = []
|
|
name_list = []
|
|
file_list = []
|
|
node_count_list = []
|
|
date_list = []
|
|
thumb_list = []
|
|
num_sessions = 0
|
|
|
|
with self._sessions_lock:
|
|
for session_id in self.sessions:
|
|
session = self.sessions[session_id]
|
|
# debug: session.dumpsession()
|
|
num_sessions += 1
|
|
id_list.append(str(session_id))
|
|
|
|
name = session.name
|
|
if not name:
|
|
name = ""
|
|
name_list.append(name)
|
|
|
|
file = session.file_name
|
|
if not file:
|
|
file = ""
|
|
file_list.append(file)
|
|
|
|
node_count_list.append(str(session.get_node_count()))
|
|
|
|
date_list.append(time.ctime(session._state_time))
|
|
|
|
thumb = session.thumbnail
|
|
if not thumb:
|
|
thumb = ""
|
|
thumb_list.append(thumb)
|
|
|
|
session_ids = "|".join(id_list)
|
|
names = "|".join(name_list)
|
|
files = "|".join(file_list)
|
|
node_counts = "|".join(node_count_list)
|
|
dates = "|".join(date_list)
|
|
thumbs = "|".join(thumb_list)
|
|
|
|
if num_sessions > 0:
|
|
tlv_data = ""
|
|
if len(session_ids) > 0:
|
|
tlv_data += coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, session_ids)
|
|
if len(names) > 0:
|
|
tlv_data += coreapi.CoreSessionTlv.pack(SessionTlvs.NAME.value, names)
|
|
if len(files) > 0:
|
|
tlv_data += coreapi.CoreSessionTlv.pack(SessionTlvs.FILE.value, files)
|
|
if len(node_counts) > 0:
|
|
tlv_data += coreapi.CoreSessionTlv.pack(SessionTlvs.NODE_COUNT.value, node_counts)
|
|
if len(dates) > 0:
|
|
tlv_data += coreapi.CoreSessionTlv.pack(SessionTlvs.DATE.value, dates)
|
|
if len(thumbs) > 0:
|
|
tlv_data += coreapi.CoreSessionTlv.pack(SessionTlvs.THUMB.value, thumbs)
|
|
message = coreapi.CoreSessionMessage.pack(flags, tlv_data)
|
|
else:
|
|
message = None
|
|
|
|
return message
|
|
|
|
def dump_sessions(self):
|
|
"""
|
|
Log currently known session information.
|
|
"""
|
|
logger.info("sessions:")
|
|
with self._sessions_lock:
|
|
for session_id in self.sessions:
|
|
logger.info(session_id)
|
|
|
|
# def set_session_master(self, handler):
|
|
# """
|
|
# Call the setmaster() method for every session. Returns True when
|
|
# a session having the given handler was updated.
|
|
# """
|
|
# found = False
|
|
#
|
|
# with self._sessions_lock:
|
|
# for session_id in self.sessions:
|
|
# found = self.sessions[session_id].set_master(handler)
|
|
# if found is True:
|
|
# break
|
|
#
|
|
# return found
|
|
|
|
|
|
class CoreUdpServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
|
|
"""
|
|
UDP server class, manages sessions and spawns request handlers for
|
|
incoming connections.
|
|
"""
|
|
daemon_threads = True
|
|
allow_reuse_address = True
|
|
|
|
def __init__(self, server_address, handler_class, main_server):
|
|
"""
|
|
Server class initialization takes configuration data and calls
|
|
the SocketServer constructor
|
|
|
|
:param tuple[str, int] server_address: server address
|
|
:param class handler_class: class for handling requests
|
|
:param main_server: main server to associate with
|
|
"""
|
|
self.mainserver = main_server
|
|
SocketServer.UDPServer.__init__(self, server_address, handler_class)
|
|
|
|
def start(self):
|
|
"""
|
|
Thread target to run concurrently with the TCP server.
|
|
|
|
:return: nothing
|
|
"""
|
|
self.serve_forever()
|
|
|
|
|
|
class CoreAuxServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
|
|
"""
|
|
An auxiliary TCP server.
|
|
"""
|
|
daemon_threads = True
|
|
allow_reuse_address = True
|
|
|
|
def __init__(self, server_address, handler_class, main_server):
|
|
"""
|
|
Create a CoreAuxServer instance.
|
|
|
|
:param tuple[str, int] server_address: server address
|
|
:param class handler_class: class for handling requests
|
|
:param main_server: main server to associate with
|
|
"""
|
|
|
|
self.mainserver = main_server
|
|
logger.info("auxiliary server started, listening on: %s", server_address)
|
|
SocketServer.TCPServer.__init__(self, server_address, handler_class)
|
|
|
|
def start(self):
|
|
"""
|
|
Start the core auxiliary server.
|
|
|
|
:return: nothing
|
|
"""
|
|
self.serve_forever()
|
|
|
|
def set_session_master(self, handler):
|
|
"""
|
|
Set the session master handler.
|
|
|
|
:param func handler: session master handler
|
|
:return:
|
|
"""
|
|
return self.mainserver.set_session_master(handler)
|
|
|
|
def get_session(self, session_id=None):
|
|
"""
|
|
Retrieve a session.
|
|
|
|
:param int session_id: id of session to retrieve
|
|
:return: core.session.Session
|
|
"""
|
|
return self.mainserver.get_session(session_id)
|
|
|
|
def to_session_message(self, flags=0):
|
|
"""
|
|
Retrieve a session message.
|
|
|
|
:param flags: message flags
|
|
:return: session message
|
|
"""
|
|
return self.mainserver.to_session_message(flags)
|