From 0aed819d4796dcfbcfcde1196c1da46cebd5b794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 22 May 2018 23:42:35 +0200 Subject: [PATCH] Add hostname configuration to list database issue7375 review44991002 --- CHANGELOG | 1 + trytond/admin.py | 5 +++ trytond/backend/database.py | 4 +- trytond/backend/postgresql/database.py | 54 ++++++++++++++++---------- trytond/backend/postgresql/init.sql | 1 + trytond/backend/sqlite/database.py | 36 +++++++++-------- trytond/backend/sqlite/init.sql | 3 +- trytond/commandline.py | 2 + trytond/ir/configuration.py | 1 + trytond/protocols/dispatcher.py | 5 ++- 10 files changed, 70 insertions(+), 42 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 39b8519a59..435ae9082c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add hostname configuration to list database * Add tree mixin * Rename "install-dependences" into "activate-dependencies" * Use recursive common table expression for child_of/parent_of operators diff --git a/trytond/admin.py b/trytond/admin.py index c12eb88dc9..53d0821155 100644 --- a/trytond/admin.py +++ b/trytond/admin.py @@ -73,6 +73,7 @@ def run(options): with Transaction().start(db_name, 0) as transaction: pool = Pool() User = pool.get('res.user') + Configuration = pool.get('ir.configuration') with transaction.set_context(active_test=False): admin, = User.search([('login', '=', 'admin')]) @@ -112,3 +113,7 @@ def run(options): admin.save() if options.reset_password: User.reset_password([admin]) + if options.hostname is not None: + configuration = Configuration(1) + configuration.hostname = options.hostname or None + configuration.save() diff --git a/trytond/backend/database.py b/trytond/backend/database.py index 22b0b89d45..43111fc2ff 100644 --- a/trytond/backend/database.py +++ b/trytond/backend/database.py @@ -69,7 +69,7 @@ class DatabaseInterface(object): ''' raise NotImplementedError - def list(self): + def list(self, hostname=None): ''' Get the list of database @@ -83,7 +83,7 @@ class DatabaseInterface(object): ''' raise NotImplementedError - def test(self): + def test(self, hostname=None): ''' Test if it is a Tryton database. ''' diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py index 7b834b4ee2..1a0dac6339 100644 --- a/trytond/backend/postgresql/database.py +++ b/trytond/backend/postgresql/database.py @@ -71,8 +71,8 @@ class Database(DatabaseInterface): _lock = RLock() _databases = {} _connpool = None - _list_cache = None - _list_cache_timestamp = None + _list_cache = {} + _list_cache_timestamp = {} _version_cache = {} _search_path = None _current_user = None @@ -153,12 +153,12 @@ class Database(DatabaseInterface): cursor.execute('CREATE DATABASE "' + database_name + '" ' 'TEMPLATE "' + template + '" ENCODING \'unicode\'') connection.commit() - cls._list_cache = None + cls._list_cache.clear() def drop(self, connection, database_name): cursor = connection.cursor() cursor.execute('DROP DATABASE "' + database_name + '"') - self.__class__._list_cache = None + self.__class__._list_cache.clear() def get_version(self, connection): if self.name not in self._version_cache: @@ -170,11 +170,12 @@ class Database(DatabaseInterface): self._version_cache[self.name] = (major, minor, patch) return self._version_cache[self.name] - def list(self): + def list(self, hostname=None): now = time.time() timeout = config.getint('session', 'timeout') - res = self.__class__._list_cache - if res and abs(self.__class__._list_cache_timestamp - now) < timeout: + res = self.__class__._list_cache.get(hostname) + timestamp = self.__class__._list_cache_timestamp.get(hostname, now) + if res and abs(timestamp - now) < timeout: return res connection = self.get_connection() @@ -186,15 +187,15 @@ class Database(DatabaseInterface): for db_name, in cursor: try: with connect(self.dsn(db_name)) as conn: - if self._test(conn): + if self._test(conn, hostname=hostname): res.append(db_name) except Exception: continue finally: self.put_connection(connection) - self.__class__._list_cache = res - self.__class__._list_cache_timestamp = now + self.__class__._list_cache[hostname] = res + self.__class__._list_cache_timestamp[hostname] = now return res def init(self): @@ -230,22 +231,33 @@ class Database(DatabaseInterface): finally: self.put_connection(connection) - def test(self): + def test(self, hostname=None): connection = self.get_connection() - is_tryton_database = self._test(connection) - self.put_connection(connection) + try: + is_tryton_database = self._test(connection, hostname=hostname) + except Exception: + is_tryton_database = False + finally: + self.put_connection(connection) return is_tryton_database @classmethod - def _test(cls, connection): + def _test(cls, connection, hostname=None): cursor = connection.cursor() - cursor.execute('SELECT 1 FROM information_schema.tables ' - 'WHERE table_name IN %s', - (('ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu', - 'res_user', 'res_group', 'ir_module', - 'ir_module_dependency', 'ir_translation', - 'ir_lang'),)) - return len(cursor.fetchall()) != 0 + tables = ('ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu', + 'res_user', 'res_group', 'ir_module', 'ir_module_dependency', + 'ir_translation', 'ir_lang', 'ir_configuration') + cursor.execute('SELECT table_name FROM information_schema.tables ' + 'WHERE table_name IN %s', (tables,)) + if len(cursor.fetchall()) != len(tables): + return False + if hostname: + cursor.execute( + 'SELECT hostname FROM ir_configuration') + hostnames = {h for h, in cursor.fetchall() if h} + if hostnames and hostname not in hostnames: + return False + return True def nextid(self, connection, table): cursor = connection.cursor() diff --git a/trytond/backend/postgresql/init.sql b/trytond/backend/postgresql/init.sql index 9b367f6f3d..506beeef05 100644 --- a/trytond/backend/postgresql/init.sql +++ b/trytond/backend/postgresql/init.sql @@ -3,6 +3,7 @@ CREATE SEQUENCE ir_configuration_id_seq; CREATE TABLE ir_configuration ( id INTEGER DEFAULT NEXTVAL('ir_configuration_id_seq') NOT NULL, language VARCHAR, + hostname VARCHAR, PRIMARY KEY(id) ); diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py index 1749cf0434..7408b17028 100644 --- a/trytond/backend/sqlite/database.py +++ b/trytond/backend/sqlite/database.py @@ -327,7 +327,7 @@ class Database(DatabaseInterface): os.remove(os.path.join(config.get('database', 'path'), database_name + '.sqlite')) - def list(self): + def list(self, hostname=None): res = [] listdir = [':memory:'] try: @@ -344,7 +344,7 @@ class Database(DatabaseInterface): database = Database(db_name).connect() except Exception: continue - if database.test(): + if database.test(hostname=hostname): res.append(db_name) database.close() return res @@ -384,29 +384,33 @@ class Database(DatabaseInterface): cursor.execute(*insert) conn.commit() - def test(self): + def test(self, hostname=None): + tables = ['ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu', + 'res_user', 'res_group', 'ir_module', 'ir_module_dependency', + 'ir_translation', 'ir_lang', 'ir_configuration'] sqlite_master = Table('sqlite_master') select = sqlite_master.select(sqlite_master.name) select.where = sqlite_master.type == 'table' - select.where &= sqlite_master.name.in_([ - 'ir_model', - 'ir_model_field', - 'ir_ui_view', - 'ir_ui_menu', - 'res_user', - 'res_group', - 'ir_module', - 'ir_module_dependency', - 'ir_translation', - 'ir_lang', - ]) + select.where &= sqlite_master.name.in_(tables) with self._conn as conn: cursor = conn.cursor() try: cursor.execute(*select) except Exception: return False - return len(cursor.fetchall()) != 0 + if len(cursor.fetchall()) != len(tables): + return False + if hostname: + configuration = Table('ir_configuration') + try: + cursor.execute(*configuration.select( + configuration.hostname)) + except Exception: + return False + hostnames = {h for h, in cursor.fetchall() if h} + if hostnames and hostname not in hostnames: + return False + return True def lastid(self, cursor): # This call is not thread safe diff --git a/trytond/backend/sqlite/init.sql b/trytond/backend/sqlite/init.sql index 941697218b..eac0ef67c5 100644 --- a/trytond/backend/sqlite/init.sql +++ b/trytond/backend/sqlite/init.sql @@ -1,6 +1,7 @@ CREATE TABLE ir_configuration ( id INTEGER PRIMARY KEY AUTOINCREMENT, - language VARCHAR + language VARCHAR, + hostname VARCHAR ); CREATE TABLE ir_model ( diff --git a/trytond/commandline.py b/trytond/commandline.py index 640f3e05db..89a0d63e8f 100644 --- a/trytond/commandline.py +++ b/trytond/commandline.py @@ -59,6 +59,8 @@ def get_parser_admin(): dest="update_modules_list", help="Update list of tryton modules") parser.add_argument("-l", "--language", dest="languages", nargs='+', default=[], metavar='CODE', help="Load language translations") + parser.add_argument("--hostname", dest="hostname", default=None, + help="Limit database listing to the hostname") parser.epilog = ('The first time a database is initialized ' 'or when the password is set, the admin password is read ' diff --git a/trytond/ir/configuration.py b/trytond/ir/configuration.py index ed1b09e00b..d11eb39b41 100644 --- a/trytond/ir/configuration.py +++ b/trytond/ir/configuration.py @@ -11,6 +11,7 @@ class Configuration(ModelSingleton, ModelSQL): 'Configuration' __name__ = 'ir.configuration' language = fields.Char('language') + hostname = fields.Char("Hostname") _get_language_cache = Cache('ir_configuration.get_language') @staticmethod diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py index c874caf1e6..78021909c7 100644 --- a/trytond/protocols/dispatcher.py +++ b/trytond/protocols/dispatcher.py @@ -10,7 +10,7 @@ from sql import Table from trytond import security from trytond import backend -from trytond.config import config +from trytond.config import config, get_hostname from trytond import __version__ from trytond.transaction import Transaction from trytond.exceptions import ( @@ -100,10 +100,11 @@ def db_list(request, *args): if not config.getboolean('database', 'list'): raise Exception('AccessDenied') context = {'_request': request.context} + hostname = get_hostname(request.host) with Transaction().start( None, 0, context=context, close=True, _nocache=True ) as transaction: - return transaction.database.list() + return transaction.database.list(hostname=hostname) @app.auth_required