Files
entropy/libraries/entropy/client/services/system/interfaces.py
T

284 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""
@author: Fabio Erculiani <lxnay@sabayon.org>
@contact: lxnay@sabayon.org
@copyright: Fabio Erculiani
@license: GPL-2
B{Entropy Client Services Base Interface}.
"""
import time
from entropy.const import const_isstring, etpUi
from entropy.exceptions import ConnectionError
from entropy.i18n import _
from entropy.misc import TimeScheduled
class Client:
ssl_connection = True
def __init__(self, OutputInterface, MethodsInterface = None,
ClientCommandsInterface = None, quiet = True, show_progress = False,
do_cache_connection = False, do_cache_session = False,
socket_timeout = 120.0):
if not hasattr(OutputInterface, 'output'):
mytxt = _("OutputInterface does not have an output method")
raise AttributeError("%s, (! %s !)" % (
OutputInterface, mytxt,))
elif not hasattr(OutputInterface.output, '__call__'):
mytxt = _("OutputInterface does not have an output method")
raise AttributeError("%s, (! %s !)" % (
OutputInterface, mytxt,))
from entropy.client.services.system.commands import Client as ClientCommands
if not issubclass(ClientCommandsInterface, ClientCommands):
mytxt = _("A valid entropy.client.services.system.commands.Client class/subclass is needed")
raise AttributeError(mytxt)
from entropy.client.services.system.methods import BaseMixin
if not issubclass(MethodsInterface, BaseMixin):
mytxt = _("A valid entropy.client.services.system.methods.BaseMixin class/subclass is needed")
raise AttributeError(mytxt)
self.ClientCommandsInterface = ClientCommandsInterface
import socket, struct
self.socket, self.struct = socket, struct
from datetime import datetime
self.datetime = datetime
import threading
self.threading = threading
self.Output = OutputInterface
self.hostname = None
self.hostport = None
self.username = None
self.password = None
if quiet and etpUi['debug']: # enforce quiet value if debug is enabled
quiet = False
self.quiet = quiet
self.socket_timeout = socket_timeout
self.do_cache_connection = do_cache_connection
self.show_progress = show_progress
self.ClientCommandsInterface = ClientCommandsInterface
self.Methods = MethodsInterface(self)
self.session_cache = {}
self.SessionCacheLock = self.threading.Lock()
self.connection_cache = {}
self.CacheLock = self.threading.Lock()
self.shutdown = False
self.connection_killer = None
# NOTE actually session cache doesn't work when the connection is closed and re-opened
# when the server is spawning requests under a child process (fork_requests = True)
# this should be fixed by pushing the cache to disk but triggers a possible security issue
# since sessions and their password are stored in memory and kept alive there until those
# expires
self.do_cache_session = do_cache_session
if self.do_cache_connection:
self.connection_killer = TimeScheduled(2,
self.connection_killer_handler)
self.connection_killer.start()
def __del__(self):
if hasattr(self, 'shutdown'):
self.shutdown = True
if hasattr(self, 'connection_killer'):
if self.connection_killer != None:
self.connection_killer.kill()
def _validate_credentials(self):
if not const_isstring(self.hostname):
raise AttributeError("hostname: %s. %s" % (
_('not a string'), _('Please use setup_connection() properly'),))
if not const_isstring(self.username):
raise AttributeError("username: %s. %s" % (
_('not a string'), _('Please use setup_connection() properly'),))
if not const_isstring(self.password):
raise AttributeError("password: %s. %s" % (
_('not a string'), _('Please use setup_connection() properly'),))
if not isinstance(self.hostport, int):
raise AttributeError("port: %s. %s" % (
_('not an int'), _('Please use setup_connection() properly'),))
if not isinstance(self.ssl_connection, bool):
raise AttributeError("ssl_connection: %s. %s" % (
_('not a bool'), _('Please use setup_connection() properly'),))
def get_session_cache(self, cmd_tuple):
if self.do_cache_session:
with self.SessionCacheLock:
return self.session_cache.get(cmd_tuple)
def set_session_cache(self, cmd_tuple, session_id):
if self.do_cache_session:
with self.SessionCacheLock:
self.session_cache[cmd_tuple] = session_id
def remove_session_cache(self, cmd_tuple):
if self.do_cache_session:
with self.SessionCacheLock:
del self.session_cache[cmd_tuple]
def get_connection_cache_key(self):
return hash((self.hostname, self.hostport, self.username, self.password, self.ssl_connection,))
def get_connection_cache(self):
if self.do_cache_connection:
key = self.get_connection_cache_key()
srv = self.connection_cache.get(key)
# NOTE: if you enable cache connection, you should also
# consider to clear the socket buffer
# srv.sock_conn
# srv.real_sock_conn
return srv
def cache_connection(self, srv):
if self.do_cache_connection:
key = self.get_connection_cache_key()
self.connection_cache[key] = {
'conn': srv,
'ts': self.get_ts(),
}
def update_connection_ts(self):
if self.do_cache_connection:
key = self.get_connection_cache_key()
if key not in self.connection_cache:
return
self.connection_cache[key]['ts'] = self.get_ts()
def kill_all_connections(self):
if self.do_cache_connection:
self.CacheLock.acquire()
try:
keys = list(self.connection_cache.keys())
for key in keys:
data = self.connection_cache.pop(key)
data['conn'].disconnect()
finally:
self.CacheLock.release()
def connection_killer_handler(self):
if not self.do_cache_connection:
return
if self.shutdown:
return
if not self.connection_cache:
return
keys = list(self.connection_cache.keys())
for key in keys:
curr_ts = self.get_ts()
ts = self.connection_cache[key]['ts']
delta = curr_ts - ts
if delta.seconds < 60:
continue
self.CacheLock.acquire()
try:
data = self.connection_cache.pop(key)
finally:
self.CacheLock.release()
srv = data['conn']
srv.disconnect()
def get_ts(self):
return self.datetime.fromtimestamp(time.time())
def setup_connection(self, hostname, port, username, password, ssl):
self.hostname = hostname
self.hostport = port
self.username = username
self.password = password
self.ssl_connection = ssl
self._validate_credentials()
def connect_to_service(self, timeout = None):
self._validate_credentials()
args = [self.Output, self.ClientCommandsInterface]
kwargs = {
'ssl': self.ssl_connection,
'quiet': self.quiet,
'show_progress': self.show_progress
}
if timeout is None:
kwargs['socket_timeout'] = self.socket_timeout
else:
kwargs['socket_timeout'] = timeout
from entropy.services.ugc.interfaces import Client
srv = Client(*args, **kwargs)
srv.connect(self.hostname, self.hostport)
return srv
def get_service_connection(self, timeout = None):
try:
srv = self.connect_to_service(timeout = timeout)
except (ConnectionError, self.socket.error, self.struct.error,):
return None
return srv
def logout(self, srv, session_id):
self._validate_credentials()
return srv.CmdInterface.service_logout(self.username, session_id)
def login(self, srv, session_id):
self._validate_credentials()
return srv.CmdInterface.service_login(self.username, self.password, session_id)
# eval(func) must have session as first param
def do_cmd(self, login_required, func, args, kwargs):
with self.CacheLock:
srv = self.get_connection_cache()
if srv is None:
srv = self.get_service_connection(timeout = self.socket_timeout)
if srv != None:
self.cache_connection(srv)
else:
srv = srv['conn']
if srv is None:
return False, 'no connection'
cmd_tuple = (login_required, func,)
new_session = False
session = self.get_session_cache(cmd_tuple)
if session is None:
new_session = True
session = srv.open_session()
if session is None:
return False, 'no session'
else:
if not srv.is_session_alive(session):
new_session = True
session = srv.open_session()
if session is None:
return False, 'no session'
self.set_session_cache(cmd_tuple, session)
self.update_connection_ts()
args.insert(0, session)
if login_required and new_session:
logged, error = self.login(srv, session)
if not logged:
srv.close_session(session)
self.remove_session_cache(cmd_tuple)
if not self.do_cache_connection:
srv.disconnect()
return False, error
cmd_func = getattr(srv.CmdInterface, func)
rslt = cmd_func(*args, **kwargs)
if not self.do_cache_session:
if login_required:
self.logout(srv, session)
srv.close_session(session)
if not self.do_cache_connection:
srv.disconnect()
return rslt
def get_available_client_commands(self):
return self.Methods.available_commands.copy()