예제 #1
0
    def setUp(self):
        super(MasterFallbackTestCase, self).setUp()

        self.pgbouncer_fixture = PGBouncerFixture()

        # The PGBouncerFixture will set the PGPORT environment variable,
        # causing all DB connections to go via pgbouncer unless an
        # explicit port is provided.
        dbname = DatabaseLayer._db_fixture.dbname
        # Pull the direct db connection string, including explicit port.
        conn_str_direct = self.pgbouncer_fixture.databases[dbname]
        # Generate a db connection string that will go via pgbouncer.
        conn_str_pgbouncer = 'dbname=%s host=localhost' % dbname

        # Configure slave connections via pgbouncer, so we can shut them
        # down. Master connections direct so they are unaffected.
        config_key = 'master-slave-separation'
        config.push(
            config_key,
            dedent('''\
            [database]
            rw_main_master: %s
            rw_main_slave: %s
            ''' % (conn_str_direct, conn_str_pgbouncer)))
        self.addCleanup(lambda: config.pop(config_key))

        self.useFixture(self.pgbouncer_fixture)
예제 #2
0
 def test_disconnectionerror_view_integration(self):
     # Test setup.
     self.useFixture(Urllib2Fixture())
     bouncer = PGBouncerFixture()
     # XXX gary bug=974617, bug=1011847, bug=504291 2011-07-03:
     # In parallel tests, we are rarely encountering instances of
     # bug 504291 while running this test.  These cause the tests
     # to fail entirely (the store.rollback() described in comment
     # 11 does not fix the insane state) despite nultiple retries.
     # As mentioned in that bug, we are trying aborts to see if they
     # eliminate the problem.  If this works, we can find which of
     # these two aborts are actually needed.
     transaction.abort()
     self.useFixture(bouncer)
     transaction.abort()
     # Verify things are working initially.
     url = "http://launchpad.dev/"
     self.retryConnection(url, bouncer)
     # Now break the database, and we get an exception, along with
     # our view.
     bouncer.stop()
     error = self.getHTTPError(url)
     self.assertEqual(503, error.code)
     self.assertThat(error.read(), Contains(DisconnectionErrorView.reason))
     # We keep seeing the correct exception on subsequent requests.
     error = self.getHTTPError(url)
     self.assertEqual(503, error.code)
     self.assertThat(error.read(), Contains(DisconnectionErrorView.reason))
     # When the database is available again, requests succeed.
     bouncer.start()
     self.retryConnection(url, bouncer)
예제 #3
0
    def setUp(self):
        super(TestFastDowntimeRollout, self).setUp()

        self.master_dbname = DatabaseLayer._db_fixture.dbname
        self.slave_dbname = self.master_dbname + '_slave'

        self.pgbouncer_fixture = PGBouncerFixture()
        self.pgbouncer_fixture.databases[self.slave_dbname] = (
            self.pgbouncer_fixture.databases[self.master_dbname])

        # Configure master and slave connections to go via different
        # pgbouncer aliases.
        config_key = 'master-slave-separation'
        config.push(
            config_key,
            dedent('''\
            [database]
            rw_main_master: dbname=%s host=localhost
            rw_main_slave: dbname=%s host=localhost
            ''' % (self.master_dbname, self.slave_dbname)))
        self.addCleanup(lambda: config.pop(config_key))

        self.useFixture(self.pgbouncer_fixture)

        self.pgbouncer_con = psycopg2.connect(
            'dbname=pgbouncer user=pgbouncer host=localhost')
        self.pgbouncer_con.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
        self.pgbouncer_cur = self.pgbouncer_con.cursor()

        transaction.abort()
예제 #4
0
    def setUp(self):
        super(MasterFallbackTestCase, self).setUp()

        self.pgbouncer_fixture = PGBouncerFixture()

        # The PGBouncerFixture will set the PGPORT environment variable,
        # causing all DB connections to go via pgbouncer unless an
        # explicit port is provided.
        dbname = DatabaseLayer._db_fixture.dbname
        # Pull the direct db connection string, including explicit port.
        conn_str_direct = self.pgbouncer_fixture.databases[dbname]
        # Generate a db connection string that will go via pgbouncer.
        conn_str_pgbouncer = "dbname=%s host=localhost" % dbname

        # Configure slave connections via pgbouncer, so we can shut them
        # down. Master connections direct so they are unaffected.
        config_key = "master-slave-separation"
        config.push(
            config_key,
            dedent(
                """\
            [database]
            rw_main_master: %s
            rw_main_slave: %s
            """
                % (conn_str_direct, conn_str_pgbouncer)
            ),
        )
        self.addCleanup(lambda: config.pop(config_key))

        self.useFixture(self.pgbouncer_fixture)
예제 #5
0
    def test_install_fixture(self):
        self.assertTrue(self.is_db_available())

        with PGBouncerFixture() as pgbouncer:
            self.assertTrue(self.is_db_available())

            pgbouncer.stop()
            self.assertFalse(self.is_db_available())

        # This confirms that we are again connecting directly to the
        # database, as the pgbouncer process was shutdown.
        self.assertTrue(self.is_db_available())
예제 #6
0
    def test_stop_and_start(self):
        # Database is working.
        assert self.is_connected()

        # And database with the fixture is working too.
        pgbouncer = PGBouncerFixture()
        with PGBouncerFixture() as pgbouncer:
            assert self.is_connected()

            # pgbouncer is transparant. To confirm we are connecting via
            # pgbouncer, we need to shut it down and confirm our
            # connections are dropped.
            pgbouncer.stop()
            assert not self.is_connected()

            # If we restart it, things should be back to normal.
            pgbouncer.start()
            assert self.is_connected()

        # Database is still working.
        assert self.is_connected()
예제 #7
0
 def test_disconnectionerror_view_integration(self):
     # Test setup.
     self.useFixture(Urllib2Fixture())
     bouncer = PGBouncerFixture()
     # XXX gary bug=974617, bug=1011847, bug=504291 2011-07-03:
     # In parallel tests, we are rarely encountering instances of
     # bug 504291 while running this test.  These cause the tests
     # to fail entirely (the store.rollback() described in comment
     # 11 does not fix the insane state) despite nultiple retries.
     # As mentioned in that bug, we are trying aborts to see if they
     # eliminate the problem.  If this works, we can find which of
     # these two aborts are actually needed.
     transaction.abort()
     self.useFixture(bouncer)
     transaction.abort()
     # Verify things are working initially.
     url = 'http://launchpad.dev/'
     self.retryConnection(url, bouncer)
     # Now break the database, and we get an exception, along with
     # our view.
     bouncer.stop()
     error = self.getHTTPError(url)
     self.assertEqual(503, error.code)
     self.assertThat(error.read(), Contains(DisconnectionErrorView.reason))
     # We keep seeing the correct exception on subsequent requests.
     error = self.getHTTPError(url)
     self.assertEqual(503, error.code)
     self.assertThat(error.read(), Contains(DisconnectionErrorView.reason))
     # When the database is available again, requests succeed.
     bouncer.start()
     self.retryConnection(url, bouncer)
예제 #8
0
    def test_install_fixture_with_restart(self):
        self.assertTrue(self.is_db_available())

        with PGBouncerFixture() as pgbouncer:
            self.assertTrue(self.is_db_available())

            pgbouncer.stop()
            self.assertFalse(self.is_db_available())

            pgbouncer.start()
            self.assertTrue(self.is_db_available())

        # Note that because pgbouncer was left running, we can't confirm
        # that we are now connecting directly to the database.
        self.assertTrue(self.is_db_available())
예제 #9
0
    def setUp(cls):
        # Fixture to hold other fixtures.
        cls._fixture = Fixture()
        cls._fixture.setUp()

        cls.pgbouncer_fixture = PGBouncerFixture()
        # Install the PGBouncer fixture so we shut it down to
        # create database outages.
        cls._fixture.useFixture(cls.pgbouncer_fixture)

        # Bring up the Librarian, which will be connecting via
        # pgbouncer.
        cls.librarian_fixture = LibrarianServerFixture(
            BaseLayer.config_fixture)
        cls._fixture.useFixture(cls.librarian_fixture)
예제 #10
0
    def test_stop_no_start(self):
        # Database is working.
        assert self.is_connected()

        # And database with the fixture is working too.
        with PGBouncerFixture() as pgbouncer:
            assert self.is_connected()

            # pgbouncer is transparant. To confirm we are connecting via
            # pgbouncer, we need to shut it down and confirm our
            # connections are dropped.
            pgbouncer.stop()
            assert not self.is_connected()

        # Database is working again.
        assert self.is_connected()
예제 #11
0
    def test_starts_with_db_down(self):
        pgbouncer = self.useFixture(PGBouncerFixture())

        # Start with the database down.
        pgbouncer.stop()

        self.spawn()

        for count in range(5):
            out = self.request('foo')
            self.assertEqual(out, 'NULL')

        pgbouncer.start()

        out = self.request('foo')
        self.assertEndsWith(out, '/foo')
예제 #12
0
    def test_stop_and_start(self):
        # Database is working.
        assert self.is_connected()

        # And database with the fixture is working too.
        pgbouncer = PGBouncerFixture()
        with PGBouncerFixture() as pgbouncer:
            assert self.is_connected()

            # pgbouncer is transparant. To confirm we are connecting via
            # pgbouncer, we need to shut it down and confirm our
            # connections are dropped.
            pgbouncer.stop()
            assert not self.is_connected()

            # If we restart it, things should be back to normal.
            pgbouncer.start()
            assert self.is_connected()

        # Database is still working.
        assert self.is_connected()
예제 #13
0
    def test_reconnects_when_disconnected(self):
        pgbouncer = self.useFixture(PGBouncerFixture())

        self.spawn()

        # Everything should be working, and we get valid output.
        out = self.request('foo')
        self.assertEndsWith(out, '/foo')

        pgbouncer.stop()

        # Now with pgbouncer down, we should get NULL messages and
        # stderr spam, and this keeps happening. We test more than
        # once to ensure that we will keep trying to reconnect even
        # after several failures.
        for count in range(5):
            out = self.request('foo')
            self.assertEqual(out, 'NULL')

        pgbouncer.start()

        # Everything should be working, and we get valid output.
        out = self.request('foo')
        self.assertEndsWith(out, '/foo')
예제 #14
0
    def test_disconnectionerror_view_integration(self):
        # Test setup.
        self.useFixture(FakeLogger('SiteError', level=logging.CRITICAL))
        self.useFixture(Urllib2Fixture())
        bouncer = PGBouncerFixture()
        # XXX gary bug=974617, bug=1011847, bug=504291 2011-07-03:
        # In parallel tests, we are rarely encountering instances of
        # bug 504291 while running this test.  These cause the tests
        # to fail entirely (the store.rollback() described in comment
        # 11 does not fix the insane state) despite nultiple retries.
        # As mentioned in that bug, we are trying aborts to see if they
        # eliminate the problem.  If this works, we can find which of
        # these two aborts are actually needed.
        transaction.abort()
        self.useFixture(bouncer)
        transaction.abort()
        # Verify things are working initially.
        url = 'http://launchpad.dev/'
        self.retryConnection(url, bouncer)
        # Now break the database, and we get an exception, along with
        # our view and several OOPSes from the retries.
        bouncer.stop()

        class Disconnects(Equals):
            def __init__(self, message):
                super(Disconnects, self).__init__(
                    ('DisconnectionError', message))

        with CaptureOops() as oopses:
            error = self.getHTTPError(url)
        self.assertEqual(503, error.code)
        self.assertThat(error.read(),
                        Contains(DisconnectionErrorView.reason))
        self.assertThat(
            [(oops['type'], oops['value'].split('\n')[0])
             for oops in oopses.oopses],
            MatchesListwise(
                [MatchesAny(
                    # libpq < 9.5.
                    Disconnects('error with no message from the libpq'),
                    # libpq >= 9.5.
                    Disconnects('server closed the connection unexpectedly'))]
                * 2 +
                [Disconnects(
                    'could not connect to server: Connection refused')]
                * 6))

        # We keep seeing the correct exception on subsequent requests.
        with CaptureOops() as oopses:
            error = self.getHTTPError(url)
        self.assertEqual(503, error.code)
        self.assertThat(error.read(),
                        Contains(DisconnectionErrorView.reason))
        self.assertThat(
            [(oops['type'], oops['value'].split('\n')[0])
             for oops in oopses.oopses],
            MatchesListwise(
                [Disconnects(
                    'could not connect to server: Connection refused')]
                * 8))

        # When the database is available again, requests succeed.
        bouncer.start()
        self.retryConnection(url, bouncer)

        # If we ask pgbouncer to disable the database, requests fail and
        # get the same error page, but we don't log OOPSes except for
        # the initial connection terminations. Disablement is always
        # explicit maintenance, and we don't need lots of ongoing OOPSes
        # to tell us about maintenance that we're doing.
        dbname = DatabaseLayer._db_fixture.dbname
        conn = psycopg2.connect("dbname=pgbouncer")
        conn.autocommit = True
        cur = conn.cursor()
        cur.execute("DISABLE " + dbname)
        cur.execute("KILL " + dbname)
        cur.execute("RESUME " + dbname)

        with CaptureOops() as oopses:
            error = self.getHTTPError(url)
        self.assertEqual(503, error.code)
        self.assertThat(error.read(),
                        Contains(DisconnectionErrorView.reason))
        self.assertThat(
            [(oops['type'], oops['value'].split('\n')[0])
             for oops in oopses.oopses],
            MatchesListwise([Disconnects('database removed')]))

        # A second request doesn't log any OOPSes.
        with CaptureOops() as oopses:
            error = self.getHTTPError(url)
        self.assertEqual(503, error.code)
        self.assertThat(error.read(),
                        Contains(DisconnectionErrorView.reason))
        self.assertEqual(
            [],
            [(oops['type'], oops['value'].split('\n')[0])
             for oops in oopses.oopses])

        # When the database is available again, requests succeed.
        cur.execute("ENABLE %s" % DatabaseLayer._db_fixture.dbname)
        self.retryConnection(url, bouncer)
예제 #15
0
class MasterFallbackTestCase(TestCase):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(MasterFallbackTestCase, self).setUp()

        self.pgbouncer_fixture = PGBouncerFixture()

        # The PGBouncerFixture will set the PGPORT environment variable,
        # causing all DB connections to go via pgbouncer unless an
        # explicit port is provided.
        dbname = DatabaseLayer._db_fixture.dbname
        # Pull the direct db connection string, including explicit port.
        conn_str_direct = self.pgbouncer_fixture.databases[dbname]
        # Generate a db connection string that will go via pgbouncer.
        conn_str_pgbouncer = 'dbname=%s host=localhost' % dbname

        # Configure slave connections via pgbouncer, so we can shut them
        # down. Master connections direct so they are unaffected.
        config_key = 'master-slave-separation'
        config.push(
            config_key,
            dedent('''\
            [database]
            rw_main_master: %s
            rw_main_slave: %s
            ''' % (conn_str_direct, conn_str_pgbouncer)))
        self.addCleanup(lambda: config.pop(config_key))

        self.useFixture(self.pgbouncer_fixture)

    def test_can_shutdown_slave_only(self):
        '''Confirm that this TestCase's test infrastructure works as needed.
        '''
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        # Both Stores work when pgbouncer is up.
        master_store.get(Person, 1)
        slave_store.get(Person, 1)

        # Slave Store breaks when pgbouncer is torn down. Master Store
        # is fine.
        self.pgbouncer_fixture.stop()
        master_store.get(Person, 2)
        self.assertRaises(DisconnectionError, slave_store.get, Person, 2)

    def test_startup_with_no_slave(self):
        '''An attempt is made for the first time to connect to a slave.'''
        self.pgbouncer_fixture.stop()

        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        # The master and slave Stores are the same object.
        self.assertIs(master_store, slave_store)

    def test_slave_shutdown_during_transaction(self):
        '''Slave is shutdown while running, but we can recover.'''
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        self.assertIsNot(master_store, slave_store)

        self.pgbouncer_fixture.stop()

        # The transaction fails if the slave store is used. Robust
        # processes will handle this and retry (even if just means exit
        # and wait for the next scheduled invocation).
        self.assertRaises(DisconnectionError, slave_store.get, Person, 1)

        transaction.abort()

        # But in the next transaction, we get the master Store if we ask
        # for the slave Store so we can continue.
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        self.assertIs(master_store, slave_store)

    def test_slave_shutdown_between_transactions(self):
        '''Slave is shutdown in between transactions.'''
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIsNot(master_store, slave_store)

        transaction.abort()
        self.pgbouncer_fixture.stop()

        # The process doesn't notice the slave going down, and things
        # will fail the next time the slave is used.
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIsNot(master_store, slave_store)
        self.assertRaises(DisconnectionError, slave_store.get, Person, 1)

        # But now it has been discovered the socket is no longer
        # connected to anything, next transaction we get a master
        # Store when we ask for a slave.
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIs(master_store, slave_store)

    def test_slave_reconnect_after_outage(self):
        '''The slave is again used once it becomes available.'''
        self.pgbouncer_fixture.stop()

        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIs(master_store, slave_store)

        self.pgbouncer_fixture.start()
        transaction.abort()

        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIsNot(master_store, slave_store)
예제 #16
0
class MasterFallbackTestCase(TestCase):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(MasterFallbackTestCase, self).setUp()

        self.pgbouncer_fixture = PGBouncerFixture()

        # The PGBouncerFixture will set the PGPORT environment variable,
        # causing all DB connections to go via pgbouncer unless an
        # explicit port is provided.
        dbname = DatabaseLayer._db_fixture.dbname
        # Pull the direct db connection string, including explicit port.
        conn_str_direct = self.pgbouncer_fixture.databases[dbname]
        # Generate a db connection string that will go via pgbouncer.
        conn_str_pgbouncer = "dbname=%s host=localhost" % dbname

        # Configure slave connections via pgbouncer, so we can shut them
        # down. Master connections direct so they are unaffected.
        config_key = "master-slave-separation"
        config.push(
            config_key,
            dedent(
                """\
            [database]
            rw_main_master: %s
            rw_main_slave: %s
            """
                % (conn_str_direct, conn_str_pgbouncer)
            ),
        )
        self.addCleanup(lambda: config.pop(config_key))

        self.useFixture(self.pgbouncer_fixture)

    def test_can_shutdown_slave_only(self):
        """Confirm that this TestCase's test infrastructure works as needed.
        """
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        # Both Stores work when pgbouncer is up.
        master_store.get(Person, 1)
        slave_store.get(Person, 1)

        # Slave Store breaks when pgbouncer is torn down. Master Store
        # is fine.
        self.pgbouncer_fixture.stop()
        master_store.get(Person, 2)
        self.assertRaises(DisconnectionError, slave_store.get, Person, 2)

    def test_startup_with_no_slave(self):
        """An attempt is made for the first time to connect to a slave."""
        self.pgbouncer_fixture.stop()

        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        # The master and slave Stores are the same object.
        self.assertIs(master_store, slave_store)

    def test_slave_shutdown_during_transaction(self):
        """Slave is shutdown while running, but we can recover."""
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        self.assertIsNot(master_store, slave_store)

        self.pgbouncer_fixture.stop()

        # The transaction fails if the slave store is used. Robust
        # processes will handle this and retry (even if just means exit
        # and wait for the next scheduled invocation).
        self.assertRaises(DisconnectionError, slave_store.get, Person, 1)

        transaction.abort()

        # But in the next transaction, we get the master Store if we ask
        # for the slave Store so we can continue.
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)

        self.assertIs(master_store, slave_store)

    def test_slave_shutdown_between_transactions(self):
        """Slave is shutdown in between transactions."""
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIsNot(master_store, slave_store)

        transaction.abort()
        self.pgbouncer_fixture.stop()

        # The process doesn't notice the slave going down, and things
        # will fail the next time the slave is used.
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIsNot(master_store, slave_store)
        self.assertRaises(DisconnectionError, slave_store.get, Person, 1)

        # But now it has been discovered the socket is no longer
        # connected to anything, next transaction we get a master
        # Store when we ask for a slave.
        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIs(master_store, slave_store)

    def test_slave_reconnect_after_outage(self):
        """The slave is again used once it becomes available."""
        self.pgbouncer_fixture.stop()

        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIs(master_store, slave_store)

        self.pgbouncer_fixture.start()
        transaction.abort()

        master_store = IMasterStore(Person)
        slave_store = ISlaveStore(Person)
        self.assertIsNot(master_store, slave_store)