示例#1
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()
示例#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(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)
示例#4
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())
示例#5
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()
示例#6
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())
    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)
示例#8
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()
示例#9
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')
示例#10
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')
示例#11
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)