A wrapper for managing database instances. Currently implementations for managing database instances exist for:
* MySQL * PostgreSQL * SQLite (via "Dummy" manager) Signed-off-by: Cleber Rosa <[email protected]> --- installation_support/database_manager/__init__.py | 74 ++++++++++++ installation_support/database_manager/base.py | 93 +++++++++++++++ installation_support/database_manager/dummy.py | 45 ++++++++ installation_support/database_manager/mysql.py | 132 ++++++++++++++++++++++ 4 files changed, 344 insertions(+) create mode 100644 installation_support/__init__.py create mode 100644 installation_support/database_manager/__init__.py create mode 100644 installation_support/database_manager/base.py create mode 100644 installation_support/database_manager/dummy.py create mode 100644 installation_support/database_manager/mysql.py diff --git a/installation_support/__init__.py b/installation_support/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/installation_support/database_manager/__init__.py b/installation_support/database_manager/__init__.py new file mode 100644 index 0000000..8062333 --- /dev/null +++ b/installation_support/database_manager/__init__.py @@ -0,0 +1,74 @@ +""" +Abstraction layer for managing different database systems + +This is not a reinvention or reimplementation of Python's DB API, but a simple +API for for creating, populating and removing instances transparently. +""" + +import logging + +from autotest.client.shared import settings + +from mysql import MySQLDatabaseManager +from dummy import DummyDatabaseManager + + +# +# FIXME: this should not be a static registry assigment but a dynamic import +# because the way it's done now, it requires all python database interface +# modules to be present +# +RDBMS_REGISTRY = {'mysql': MySQLDatabaseManager} + + +def get_manager_class(rdbms_type): + ''' + Returns a manager class for a given RDBMS type + ''' + klass = RDBMS_REGISTRY.get(rdbms_type, None) + if klass is None: + logging.info('There\'s no database manager specific for "%s"', + rdbms_type) + klass = DummyDatabaseManager + return klass + + +def engine_to_rdbms_type(django_engine): + ''' + Returns a RDBMS type for a given django engine name + ''' + rdbms_type = django_engine.split('.')[-1] + + if rdbms_type == 'afe': + rdbms_type = 'mysql' + elif rdbms_type.startswith('afe_'): + rdbms_type = rdbms_type[4:] + return rdbms_type + + +def get_manager_class_from_engine(django_engine): + ''' + Returns a manager class for a given django engine + ''' + rdbms_type = engine_to_rdbms_type(django_engine) + return get_manager_class(rdbms_type) + + +def get_manager_from_config(admin_password=None, rdbms_type=None): + ''' + Returns a manager instance from the information on the configuration file + ''' + sect = 'AUTOTEST_WEB' + name = settings.settings.get_value(sect, 'database') + user = settings.settings.get_value(sect, 'user') + password = settings.settings.get_value(sect, 'password') + host = settings.settings.get_value(sect, 'host') + + if rdbms_type is None: + rdbms_type = settings.settings.get_value(sect, 'db_type') + + klass = get_manager_class(rdbms_type) + manager = klass(name, admin_password=admin_password, user=user, + password=password, host=host) + + return manager diff --git a/installation_support/database_manager/base.py b/installation_support/database_manager/base.py new file mode 100644 index 0000000..d45cc23 --- /dev/null +++ b/installation_support/database_manager/base.py @@ -0,0 +1,93 @@ +""" +Abstraction layer for managing different database systems + +This is not a reinvention or reimplementation of Python's DB API, but a simple +API for for creating, populating and removing instances transparently. + +This module implements the base interface that database specific modules should +implement. +""" + + +class BaseDatabaseManager(object): + ''' + Base class for mananging database instances + + Different RDMS have different ways of crearing instances, checking for + their existance, etc. + ''' + def __init__(self, name, admin=None, admin_password=None, user=None, + password=None, host=None): + ''' + Creates a new instance + ''' + self.name = name + + self.admin = admin + + if admin_password is None: + self.admin_password = '' + else: + self.admin_password = admin_password + + if user is not None: + self.user = user + else: + self.user = self.admin + + if password is not None: + self.password = password + else: + self.password = admin_password + + self.host = host + + + def config_as_dict(self): + ''' + Returns relevant database configuration as a dictionary + ''' + return {'name' : self.name, + 'admin' : self.admin, + 'admin_password' : self.admin_password, + 'user' : self.user, + 'password' : self.password, + 'host' : self.host} + + + def exists(self): + ''' + Checks if the database instance exists + ''' + raise NotImplementedError + + + def admin_credentials_valid(self): + ''' + Checks if the admin user credentials are valid system wide + + What this means is that we won't try to connect to a specific database + instance, but to the RDBMS as a whole (where appropriate) + ''' + raise NotImplementedError + + + def create_instance(self): + ''' + Creates the database instance + ''' + raise NotImplementedError + + + def grant_privileges(self): + ''' + Grants necessary privileges to the database user + ''' + return True + + + def setup(self): + ''' + Performs all the steps neede to completely setup a database instance + ''' + raise NotImplementedError diff --git a/installation_support/database_manager/dummy.py b/installation_support/database_manager/dummy.py new file mode 100644 index 0000000..8012ef3 --- /dev/null +++ b/installation_support/database_manager/dummy.py @@ -0,0 +1,45 @@ +""" +Dummy implementation of the database manager interface +""" + +import logging + +from autotest.installation_support.database_manager import base + + +class DummyDatabaseManager(base.BaseDatabaseManager): + ''' + Dummy class that manages no database + ''' + def exists(self): + ''' + Checks if the database instance exists + ''' + return False + + + def admin_credentials_valid(self): + ''' + Checks if the admin user credentials are valid system wide + + What this means is that we won't try to connect to a specific database + instance, but to the RDBMS as a whole (where appropriate) + ''' + return True + + + def create_instance(self): + ''' + Creates the database instance + ''' + return True + + + def grant_privileges(self): + return True + + def setup(self): + ''' + Performs all the steps neede to completely setup a database instance + ''' + return True diff --git a/installation_support/database_manager/mysql.py b/installation_support/database_manager/mysql.py new file mode 100644 index 0000000..258029a --- /dev/null +++ b/installation_support/database_manager/mysql.py @@ -0,0 +1,132 @@ +""" +MySQL implementation of the database manager interface +""" + +import logging + +import MySQLdb + +from autotest.installation_support.database_manager import base + + +class MySQLDatabaseManager(base.BaseDatabaseManager): + ''' + Class that manages MySQL database instances + ''' + def __init__(self, name, admin='root', admin_password=None, user=None, + password=None, host=None): + ''' + Creates a new instance + ''' + super(MySQLDatabaseManager, self).__init__(name, admin, admin_password, + user, password, host) + + if self.admin_credentials_valid(): + self.admin_connection = MySQLdb.connect(user=self.admin, + passwd=self.admin_password) + else: + self.admin_connection = None + logging.error("Failed to logon as the database admin user") + + + def exists(self): + ''' + Checks if the database instance exists + ''' + try: + MySQLdb.connect(user=self.admin, + passwd=self.admin_password, + db=self.name, + host=self.host) + return True + except MySQLdb.OperationalError: + return False + + + def admin_credentials_valid(self): + ''' + Checks if the admin user credentials are valid system wide + + What this means is that we won't try to connect to a specific database + instance, but to the RDBMS as a whole (where appropriate) + ''' + try: + MySQLdb.connect(user=self.admin, + passwd=self.admin_password) + return True + except MySQLdb.OperationalError: + return False + + + def create_instance(self): + ''' + Creates the database instance + ''' + if self.admin_connection is None: + return False + + if self.exists(): + logging.info("Database already exists, doing nothing") + return True + + cursor = self.admin_connection.cursor() + try: + cursor.execute("CREATE DATABASE %s" % self.name) + except (MySQLdb.ProgrammingError, MySQLdb.OperationalError): + self.admin_connection.rollback() + return False + + return True + + + def grant_privileges(self): + ''' + Attempts to create database AND users AND set privileges + ''' + grant_cmds = ["grant all privileges on %(name)s.* TO " + "'%(user)s'@'localhost' identified by '%(password)s'", + "grant SELECT on %(name)s.* TO 'nobody'@'%%'", + "grant SELECT on %(name)s.* TO 'nobody'@'localhost'", + "FLUSH PRIVILEGES"] + + if self.admin_connection is None: + return False + + if not self.exists(): + return False + + self.admin_connection.begin() + cursor = self.admin_connection.cursor() + + for cmd in grant_cmds: + cmd_ = cmd % self.config_as_dict() + try: + cursor.execute(cmd_) + except (MySQLdb.ProgrammingError, MySQLdb.OperationalError): + self.admin_connection.rollback() + return False + + self.admin_connection.commit() + return True + + + def setup(self): + ''' + Performs all the steps neede to completely setup a database instance + ''' + if self.admin_connection is None: + logging.error("Failed to logon as the database admin user") + return False + + self.admin_connection.begin() + + if not self.create_instance(): + self.admin_connection.rollback() + return False + + if not self.grant_privileges(): + self.admin_connection.rollback() + return False + + self.admin_connection.commit() + return True -- 1.7.11.7 _______________________________________________ Autotest-kernel mailing list [email protected] https://www.redhat.com/mailman/listinfo/autotest-kernel
