예제 #1
0
    def get_replication_slot(self, slot_name):
        """
        Retrieve from the PostgreSQL server a physical replication slot
        with a specific slot_name.

        This method returns a dictionary containing the following data:

         * slot_name
         * active
         * restart_lsn

        :param str slot_name: the replication slot name
        :rtype: psycopg2.extras.DictRow
        """
        if self.server_version < 90400:
            # Raise exception if replication slot are not supported
            # by PostgreSQL version
            raise PostgresUnsupportedFeature('9.4')
        else:
            cur = self._cursor(cursor_factory=NamedTupleCursor)
            try:
                cur.execute("SELECT slot_name, "
                            "active, "
                            "restart_lsn "
                            "FROM pg_replication_slots "
                            "WHERE slot_type = 'physical' "
                            "AND slot_name = '%s'" % slot_name)
                # Retrieve the replication slot information
                return cur.fetchone()
            except (PostgresConnectionError, psycopg2.Error) as e:
                _logger.debug("Error retrieving replication_slots: %s",
                              str(e).strip())
                raise
예제 #2
0
    def get_synchronous_standby_names(self):
        """
        Retrieve the list of named synchronous standby servers from PostgreSQL

        This method returns a list of names

        :return list: synchronous standby names
        """
        if self.server_version < 90100:
            # Raise exception if synchronous replication is not supported
            raise PostgresUnsupportedFeature('9.1')
        else:
            synchronous_standby_names = (
                self.get_setting('synchronous_standby_names'))
            # Normalise the list of sync standby names
            # On PostgreSQL 9.6 it is possible to specify the number of
            # required synchronous standby using this format:
            # n (name1, name2, ... nameN).
            # We only need the name list, so we discard everything else.

            # The name list starts after the first parenthesis or at pos 0
            names_start = synchronous_standby_names.find('(') + 1
            names_end = synchronous_standby_names.rfind(')')
            if names_end < 0:
                names_end = len(synchronous_standby_names)
            names_list = synchronous_standby_names[names_start:names_end]
            return [x.strip() for x in names_list.split(',')]
예제 #3
0
    def test_replication_status(self, capsys):
        """
        Test management of pg_stat_archiver view output

        :param MagicMock connect_mock: mock the database connection
        :param capsys: retrieve output from consolle

        """

        # Build a fake get_replication_stats record
        replication_stats_data = dict(
            pid=93275,
            usesysid=10,
            usename='postgres',
            application_name='replica',
            client_addr=None,
            client_hostname=None,
            client_port=-1,
            slot_name=None,
            backend_start=datetime.datetime(
                2016, 5, 6, 9, 29, 20, 98534,
                tzinfo=FixedOffsetTimezone(offset=120)),
            backend_xmin='940',
            state='streaming',
            sent_location='0/3005FF0',
            write_location='0/3005FF0',
            flush_location='0/3005FF0',
            replay_location='0/3005FF0',
            current_location='0/3005FF0',
            sync_priority=0,
            sync_state='async'
        )
        replication_stats_class = namedtuple("Record",
                                             replication_stats_data.keys())
        replication_stats_record = replication_stats_class(
            **replication_stats_data)

        # Prepare the server
        server = build_real_server(main_conf={'archiver': 'on'})
        server.postgres = MagicMock()
        server.postgres.get_replication_stats.return_value = [
            replication_stats_record]
        server.postgres.current_xlog_location = "AB/CDEF1234"

        # Execute the test (ALL)
        server.postgres.reset_mock()
        server.replication_status('all')
        (out, err) = capsys.readouterr()
        assert err == ''
        server.postgres.get_replication_stats.assert_called_once_with(
            PostgreSQLConnection.ANY_STREAMING_CLIENT)

        # Execute the test (WALSTREAMER)
        server.postgres.reset_mock()
        server.replication_status('wal-streamer')
        (out, err) = capsys.readouterr()
        assert err == ''
        server.postgres.get_replication_stats.assert_called_once_with(
            PostgreSQLConnection.WALSTREAMER)

        # Execute the test (failure: PostgreSQL too old)
        server.postgres.reset_mock()
        server.postgres.get_replication_stats.side_effect = \
            PostgresUnsupportedFeature('9.1')
        server.replication_status('all')
        (out, err) = capsys.readouterr()
        assert 'Requires PostgreSQL 9.1 or higher' in out
        assert err == ''
        server.postgres.get_replication_stats.assert_called_once_with(
            PostgreSQLConnection.ANY_STREAMING_CLIENT)

        # Execute the test (failure: superuser required)
        server.postgres.reset_mock()
        server.postgres.get_replication_stats.side_effect = \
            PostgresSuperuserRequired
        server.replication_status('all')
        (out, err) = capsys.readouterr()
        assert 'Requires superuser rights' in out
        assert err == ''
        server.postgres.get_replication_stats.assert_called_once_with(
            PostgreSQLConnection.ANY_STREAMING_CLIENT)
예제 #4
0
    def get_replication_stats(self, client_type=STANDBY):
        """
        Returns streaming replication information
        """
        try:
            cur = self._cursor(cursor_factory=NamedTupleCursor)

            # Without superuser rights, this function is useless
            # TODO: provide a simplified version for non-superusers
            if not self.is_superuser:
                raise PostgresSuperuserRequired()

            # pg_stat_replication is a system view that contains one
            # row per WAL sender process with information about the
            # replication status of a standby server. It has been
            # introduced in PostgreSQL 9.1. Current fields are:
            #
            # - pid (procpid in 9.1)
            # - usesysid
            # - usename
            # - application_name
            # - client_addr
            # - client_hostname
            # - client_port
            # - backend_start
            # - backend_xmin (9.4+)
            # - state
            # - sent_location
            # - write_location
            # - flush_location
            # - replay_location
            # - sync_priority
            # - sync_state
            #

            if self.server_version < 90100:
                raise PostgresUnsupportedFeature('9.1')

            from_repslot = ""
            if self.server_version >= 90500:
                # Current implementation (9.5+)
                what = "r.*, rs.slot_name"
                # Look for replication slot name
                from_repslot = "LEFT JOIN pg_replication_slots rs " \
                               "ON (r.pid = rs.active_pid) "
            elif self.server_version >= 90400:
                # PostgreSQL 9.4
                what = "*"
            elif self.server_version >= 90200:
                # PostgreSQL 9.2/9.3
                what = "pid," \
                    "usesysid," \
                    "usename," \
                    "application_name," \
                    "client_addr," \
                    "client_hostname," \
                    "client_port," \
                    "backend_start," \
                    "CAST (NULL AS xid) AS backend_xmin," \
                    "state," \
                    "sent_location," \
                    "write_location," \
                    "flush_location," \
                    "replay_location," \
                    "sync_priority," \
                    "sync_state "
            else:
                # PostgreSQL 9.1
                what = "procpid AS pid," \
                    "usesysid," \
                    "usename," \
                    "application_name," \
                    "client_addr," \
                    "client_hostname," \
                    "client_port," \
                    "backend_start," \
                    "CAST (NULL AS xid) AS backend_xmin," \
                    "state," \
                    "sent_location," \
                    "write_location," \
                    "flush_location," \
                    "replay_location," \
                    "sync_priority," \
                    "sync_state "

            # Streaming client
            if client_type == self.STANDBY:
                # Standby server
                where = 'WHERE replay_location IS NOT NULL '
            elif client_type == self.WALSTREAMER:
                # WAL streamer
                where = 'WHERE replay_location IS NULL '
            else:
                where = ''

            # Execute the query
            cur.execute("SELECT %s, "
                        "pg_is_in_recovery() AS is_in_recovery,"
                        "CASE WHEN pg_is_in_recovery() "
                        "  THEN {pg_last_wal_receive_lsn}() "
                        "  ELSE {pg_current_wal_lsn}() "
                        "END AS current_location "
                        "FROM pg_stat_replication r "
                        "%s"
                        "%s"
                        "ORDER BY sync_state DESC, sync_priority".format(
                            **self.name_map) % (what, from_repslot, where))

            # Generate a list of standby objects
            return cur.fetchall()
        except (PostgresConnectionError, psycopg2.Error) as e:
            _logger.debug("Error retrieving status of standby servers: %s",
                          str(e).strip())
            return None