Skip to content
Snippets Groups Projects
Select Git revision
  • e8821f7dcb3d5aea66daf79d351a00425dab0d9b
  • master default protected
  • L2SS-1914-fix_job_dispatch
  • TMSS-3170
  • TMSS-3167
  • TMSS-3161
  • TMSS-3158-Front-End-Only-Allow-Changing-Again
  • TMSS-3133
  • TMSS-3319-Fix-Templates
  • test-fix-deploy
  • TMSS-3134
  • TMSS-2872
  • defer-state
  • add-custom-monitoring-points
  • TMSS-3101-Front-End-Only
  • TMSS-984-choices
  • SDC-1400-Front-End-Only
  • TMSS-3079-PII
  • TMSS-2936
  • check-for-max-244-subbands
  • TMSS-2927---Front-End-Only-PXII
  • Before-Remove-TMSS
  • LOFAR-Release-4_4_318 protected
  • LOFAR-Release-4_4_317 protected
  • LOFAR-Release-4_4_316 protected
  • LOFAR-Release-4_4_315 protected
  • LOFAR-Release-4_4_314 protected
  • LOFAR-Release-4_4_313 protected
  • LOFAR-Release-4_4_312 protected
  • LOFAR-Release-4_4_311 protected
  • LOFAR-Release-4_4_310 protected
  • LOFAR-Release-4_4_309 protected
  • LOFAR-Release-4_4_308 protected
  • LOFAR-Release-4_4_307 protected
  • LOFAR-Release-4_4_306 protected
  • LOFAR-Release-4_4_304 protected
  • LOFAR-Release-4_4_303 protected
  • LOFAR-Release-4_4_302 protected
  • LOFAR-Release-4_4_301 protected
  • LOFAR-Release-4_4_300 protected
  • LOFAR-Release-4_4_299 protected
41 results

postgres.py

Blame
  • Jorrit Schaap's avatar
    TMSS-272: extracted common data structures into commonschematemplates. updated...
    Jorrit Schaap authored
    TMSS-272: extracted common data structures into commonschematemplates. updated all templates with 's to the commonschematemplates. tweaked and updated using code. fixed tests
    e8821f7d
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    postgres.py 8.07 KiB
    #!/usr/bin/env python3
    
    # Copyright (C) 2012-2015    ASTRON (Netherlands Institute for Radio Astronomy)
    # P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
    #
    # This file is part of the LOFAR software suite.
    # The LOFAR software suite is free software: you can redistribute it and/or
    # modify it under the terms of the GNU General Public License as published
    # by the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # The LOFAR software suite is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License along
    # with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
    
    # $Id$
    import psycopg2
    import os, sys
    import logging
    import uuid
    
    logger = logging.getLogger(__name__)
    
    import testing.postgresql
    from lofar.common.dbcredentials import Credentials
    from lofar.common.postgres import PostgresDatabaseConnection
    from lofar.common.testing.dbcredentials import TemporaryCredentials
    from lofar.common.util import find_free_port
    from datetime import datetime, timedelta
    
    from lofar.common.locking import NamedAtomicLock
    
    class PostgresTestDatabaseInstance():
        ''' A helper class which instantiates a running postgres server (not interfering with any other test/production postgres servers)
        Best used in a 'with'-context so the server is destroyed automagically.
        Derive your own sub-class and implement apply_database_schema with your own sql schema to setup your type of database.
        '''
        _named_lock = NamedAtomicLock('PostgresTestDatabaseInstance', maxLockAge=60)
    
        def __init__(self, user: str = 'test_user', preferred_port: int=5444) -> None:
            self._postgresql = None
            self.tmp_creds = TemporaryCredentials(user=user)
            self.tmp_creds.dbcreds.port = preferred_port
    
        def __enter__(self):
            '''create/instantiate the postgres server'''
            try:
                self.create()
            except Exception as e:
                logger.error(e)
                self.destroy()
                raise
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            '''destroy the running postgres server'''
            self.destroy()
    
        @property
        def dbcreds_id(self):
            return self.tmp_creds.dbcreds_id
    
        @property
        def dbcreds(self):
            return self.tmp_creds.dbcreds
    
        def create(self):
            '''instantiate the isolated postgres server'''
            logger.info('creating test-database instance...')
    
            with self._named_lock:
                start_time = datetime.utcnow()
                while datetime.utcnow()-start_time < timedelta(minutes=1):
                    try:
                        factory = testing.postgresql.PostgresqlFactory(cache_initialized_db=True)
                        factory.settings['port'] = find_free_port(self.tmp_creds.dbcreds.port)
                        self._postgresql = factory()
    
                        # fill credentials with the dynamically created postgress instance (e.g. port changes for each time)
                        dsn = self._postgresql.dsn()
                        self.tmp_creds.dbcreds.host = dsn['host']
                        self.tmp_creds.dbcreds.database = dsn['database']
                        self.tmp_creds.dbcreds.port = dsn['port']
                        self.tmp_creds.create()
    
                        # make the user known in the new test database
                        self._create_superuser(dsn)
    
                        logger.info('Applying test-database schema...')
                        self.apply_database_schema()
                        return
                    except Exception as e:
                        logger.warning("%s could not be started, retrying with next free port. Error: %s %s", self.__class__.__name__, e.__class__.__name__, e)
                raise TimeoutError("%s could not be started within 60 seconds. bailing out..." % self.__class__.__name__)
    
        def _create_superuser(self, dsn):
            try:
                # connect to db as root
                conn = psycopg2.connect(**dsn)
                cursor = conn.cursor()
    
                # create user role
                query = "CREATE USER %s WITH SUPERUSER PASSWORD '%s'" % (self.dbcreds.user, self.dbcreds.password)
                cursor.execute(query)
    
                logger.info('Created test-database instance. It is available at: %s',
                            self.dbcreds.stringWithHiddenPassword())
            finally:
                cursor.close()
                conn.commit()
                conn.close()
    
        def destroy(self):
            '''destroy the running postgres server'''
            try:
                if self._postgresql:
                    logger.info('removing test-database instance at %s', self.dbcreds.stringWithHiddenPassword())
                    self._postgresql.stop()
                    logger.info('test-database instance removed')
            except Exception as e:
                logger.info('error while removing test-database instance at %s: %s', self.dbcreds.stringWithHiddenPassword(), e)
    
            self.tmp_creds.destroy()
    
        def apply_database_schema(self):
            ''' Override and implement this method. Open a connection to the database specified by self.dbcreds, and apply your database's sql schema.'''
            raise NotImplementedError("Please override PostgresTestDatabaseInstance.apply_database_schema and setup your database with an sql schema.")
    
        def create_database_connection(self) -> PostgresDatabaseConnection:
            ''' Factory method to create a PostgresDatabaseConnection to the testing-database.
            Override and implement this method if you want to use your PostgresDatabaseConnection-subclass using the given self.dbcreds, and return it.
            Note: you should connect/disconnect the connection yourself, so recommended usage is in a 'with'-context'''
            return PostgresDatabaseConnection(self.dbcreds)
    
        def print_database_instance_log(self):
            '''print the log of the testing-database instance (can help debugging sql statements)'''
            try:
                if self._postgresql:
                    db_log_file_name = os.path.join(self._postgresql.base_dir, '%s.log' % self._postgresql.name)
                    logger.info('Printing test-postgress-database server log for reference: %s', db_log_file_name)
                    with open(db_log_file_name, 'r') as db_log_file:
                        for line in db_log_file.readlines():
                            print("  postgres log: %s" % line.strip(), file=sys.stderr)
            except Exception as e:
                logger.error("Error while printing test-postgress-database server log: %s", e)
    
    
    class PostgresTestMixin():
        '''
        A common test mixin class from which you can/should derive to get a freshly setup postgres testing instance with your sql setup scripts applied.
        It implements the unittest setUpClass/tearDownClass methods and uses them as a template method pattern to do all the testing-database setup/teardown work for you.
        '''
    
        # class variables are initialized in setUpClass
        _test_db_instance  = None
        db                 = None
    
        @classmethod
        def create_test_db_instance (cls) -> PostgresTestDatabaseInstance:
            raise NotImplementedError("Please implement create_test_db_instance in your subclass and return your preferred PostgresTestDatabaseInstance-subclass")
    
        @classmethod
        def setUpClass(cls):
            # create a running isolated test database instance
            cls._test_db_instance = cls.create_test_db_instance()
            cls._test_db_instance.create()
    
            # create a single PostgresDatabaseConnection for the entire test suite
            logger.info('Creating PostgresDatabaseConnection to test-database...')
            cls.db = cls._test_db_instance.create_database_connection()
            cls.db.connect()
            logger.info('PostgresDatabaseConnection to test-database %s is ready to be used.', cls.db)
    
        @classmethod
        def tearDownClass(cls):
            cls.db.disconnect()
            cls._test_db_instance.print_database_instance_log()
            cls._test_db_instance.destroy()
    
        @property
        def dbcreds(self) -> Credentials:
            return self._test_db_instance.dbcreds