def test_is_wal_file(self): assert xlog.is_wal_file('000000000000000200000001') assert xlog.is_wal_file('test/000000000000000200000001') assert not xlog.is_wal_file('00000001000000000000000A.00000020.backup') assert not xlog.is_wal_file('00000002.history') assert not xlog.is_wal_file('00000000000000000000000') assert not xlog.is_wal_file('0000000000000000000000000') assert not xlog.is_wal_file('000000000000X00000000000') assert not xlog.is_wal_file('00000001000000000000000A.backup') assert not xlog.is_any_xlog_file( 'test.00000001000000000000000A.00000020.backup') assert not xlog.is_wal_file('00000001000000000000000A.history') assert not xlog.is_wal_file('00000001000000000000000A.partial')
def test_is_wal_file(self): assert xlog.is_wal_file("000000000000000200000001") assert xlog.is_wal_file("test/000000000000000200000001") assert not xlog.is_wal_file("00000001000000000000000A.00000020.backup") assert not xlog.is_wal_file("00000002.history") assert not xlog.is_wal_file("00000000000000000000000") assert not xlog.is_wal_file("0000000000000000000000000") assert not xlog.is_wal_file("000000000000X00000000000") assert not xlog.is_wal_file("00000001000000000000000A.backup") assert not xlog.is_any_xlog_file( "test.00000001000000000000000A.00000020.backup") assert not xlog.is_wal_file("00000001000000000000000A.history") assert not xlog.is_wal_file("00000001000000000000000A.partial")
def missing_wals(server, args): warn = args.warning crit = args.critical from barman.xlog import is_wal_file from barman.infofile import WalFileInfo wals_directory = server.config.wals_directory missing_wals = 0 with server.xlogdb() as fxlogdb: for line in fxlogdb: #name, size, time, compression = server.xlogdb_parse_line(line) wal_info = WalFileInfo.from_xlogdb_line(line) name = wal_info.name directory = name[0:16] if is_wal_file(name): file_path = os.path.join(wals_directory, directory, name) if not os.path.exists(file_path): missing_wals = missing_wals + 1 exit_check(missing_wals, warn, crit, "There are %d missing wals for the last backup." % missing_wals, perfdata_key="missing", perfdata_min=0)
def get_remote_status(self): '''Get the status of the remote server''' pg_settings = ('archive_mode', 'archive_command', 'data_directory') pg_query_keys = ('server_txt_version', 'current_xlog') result = dict.fromkeys(pg_settings + pg_query_keys, None) try: with self.pg_connect() as conn: for name in pg_settings: result[name] = self.get_pg_setting(name) try: cur = conn.cursor() cur.execute("SELECT version()") result['server_txt_version'] = cur.fetchone()[0].split()[1] except: result['server_txt_version'] = None try: cur = conn.cursor() cur.execute('SELECT pg_xlogfile_name(pg_current_xlog_location())') result['current_xlog'] = cur.fetchone()[0]; except: result['current_xlog'] = None except: pass cmd = Command(self.ssh_command, self.ssh_options) result['last_shipped_wal'] = None if result['data_directory'] and result['archive_command']: archive_dir = os.path.join(result['data_directory'], 'pg_xlog', 'archive_status') out = cmd.getoutput(None, 'ls', '-tr', archive_dir)[0] for line in out.splitlines(): if line.endswith('.done'): name = line[:-5] if xlog.is_wal_file(name): result['last_shipped_wal'] = line[:-5] return result
def get_wal_until_next_backup(self, backup): """Get the xlog files between backup and the next :param backup: a backup object, the starting point to retrieve wals """ begin = backup.begin_wal next_end = None if self.get_next_backup(backup.backup_id): next_end = self.get_next_backup(backup.backup_id).end_wal backup_tli, _, _ = xlog.decode_segment_name(begin) with self.xlogdb() as fxlogdb: for line in fxlogdb: name, size, _, _ = self.xlogdb_parse_line(line) if name < begin: continue tli, _, _ = xlog.decode_segment_name(name) if tli > backup_tli: continue if not xlog.is_wal_file(name): continue if next_end and name > next_end: break # count yield (name, size)
def get_remote_status(self): """Get the status of the remote server""" pg_settings = ("archive_mode", "archive_command", "data_directory") pg_query_keys = ("server_txt_version", "current_xlog") result = dict.fromkeys(pg_settings + pg_query_keys, None) try: with self.pg_connect() as conn: for name in pg_settings: result[name] = self.get_pg_setting(name) try: cur = conn.cursor() cur.execute("SELECT version()") result["server_txt_version"] = cur.fetchone()[0].split()[1] except: result["server_txt_version"] = None try: cur = conn.cursor() cur.execute("SELECT pg_xlogfile_name(pg_current_xlog_location())") result["current_xlog"] = cur.fetchone()[0] except: result["current_xlog"] = None except: pass cmd = Command(self.ssh_command, self.ssh_options) result["last_shipped_wal"] = None if result["data_directory"] and result["archive_command"]: archive_dir = os.path.join(result["data_directory"], "pg_xlog", "archive_status") out = cmd.getoutput(None, "ls", "-tr", archive_dir)[0] for line in out.splitlines(): if line.endswith(".done"): name = line[:-5] if xlog.is_wal_file(name): result["last_shipped_wal"] = line[:-5] return result
def get_wal_until_next_backup(self, backup, include_history=False): """ Get the xlog files between backup and the next :param BackupInfo backup: a backup object, the starting point to retrieve WALs :param bool include_history: option for the inclusion of include_history files into the output """ begin = backup.begin_wal next_end = None if self.get_next_backup(backup.backup_id): next_end = self.get_next_backup(backup.backup_id).end_wal backup_tli, _, _ = xlog.decode_segment_name(begin) with self.xlogdb() as fxlogdb: for line in fxlogdb: wal_info = WalFileInfo.from_xlogdb_line(line) # Handle .history files: add all of them to the output, # regardless of their age, if requested (the 'include_history' # parameter is True) if xlog.is_history_file(wal_info.name): if include_history: yield wal_info continue if wal_info.name < begin: continue tli, _, _ = xlog.decode_segment_name(wal_info.name) if tli > backup_tli: continue if not xlog.is_wal_file(wal_info.name): continue if next_end and wal_info.name > next_end: break yield wal_info
def get_latest_archived_wal(self): """ Return the WalFileInfo of the last WAL file in the archive, or None if the archive doesn't contain any WAL file. :rtype: WalFileInfo|None """ # TODO: consider timeline? from os.path import isdir, join root = self.config.wals_directory # If the WAL archive directory doesn't exists the archive is empty if not isdir(root): return None # Traverse all the directory in the archive in reverse order, # returning the first WAL file found for name in sorted(os.listdir(root), reverse=True): fullname = join(root, name) # All relevant files are in subdirectories, so # we skip any non-directory entry if isdir(fullname): hash_dir = fullname # Inspect contained files in reverse order for wal_name in sorted(os.listdir(hash_dir), reverse=True): fullname = join(hash_dir, wal_name) # Return the first file that has the correct name if not isdir(fullname) and xlog.is_wal_file(fullname): return WalFileInfo.from_file(fullname) # If we get here, no WAL files have been found return None
def _reset_streaming_status(self, postgres_status, streaming_status): """ Reset the status of receive-wal by removing the .partial file that is marking the current position and creating one that is current with the PostgreSQL insert location """ current_wal = xlog.location_to_xlogfile_name_offset( postgres_status["current_lsn"], streaming_status["timeline"], postgres_status["xlog_segment_size"], )["file_name"] restart_wal = current_wal if (postgres_status["replication_slot"] and postgres_status["replication_slot"].restart_lsn): restart_wal = xlog.location_to_xlogfile_name_offset( postgres_status["replication_slot"].restart_lsn, streaming_status["timeline"], postgres_status["xlog_segment_size"], )["file_name"] restart_path = os.path.join(self.config.streaming_wals_directory, restart_wal) restart_partial_path = restart_path + ".partial" wal_files = sorted(glob( os.path.join(self.config.streaming_wals_directory, "*")), reverse=True) # Pick the newer file last = None for last in wal_files: if xlog.is_wal_file(last) or xlog.is_partial_file(last): break # Check if the status is already up-to-date if not last or last == restart_partial_path or last == restart_path: output.info("Nothing to do. Position of receive-wal is aligned.") return if os.path.basename(last) > current_wal: output.error( "The receive-wal position is ahead of PostgreSQL " "current WAL lsn (%s > %s)", os.path.basename(last), postgres_status["current_xlog"], ) return output.info("Resetting receive-wal directory status") if xlog.is_partial_file(last): output.info("Removing status file %s" % last) os.unlink(last) output.info("Creating status file %s" % restart_partial_path) open(restart_partial_path, "w").close()
def get_latest_archived_wals_info(self): """ Return a dictionary of timelines associated with the WalFileInfo of the last WAL file in the archive, or None if the archive doesn't contain any WAL file. :rtype: dict[str, WalFileInfo]|None """ from os.path import isdir, join root = self.config.wals_directory comp_manager = self.compression_manager # If the WAL archive directory doesn't exists the archive is empty if not isdir(root): return dict() # Traverse all the directory in the archive in reverse order, # returning the first WAL file found timelines = {} for name in sorted(os.listdir(root), reverse=True): fullname = join(root, name) # All relevant files are in subdirectories, so # we skip any non-directory entry if isdir(fullname): # Extract the timeline. If it is not valid, skip this directory try: timeline = name[0:8] int(timeline, 16) except ValueError: continue # If this timeline already has a file, skip this directory if timeline in timelines: continue hash_dir = fullname # Inspect contained files in reverse order for wal_name in sorted(os.listdir(hash_dir), reverse=True): fullname = join(hash_dir, wal_name) # Return the first file that has the correct name if not isdir(fullname) and xlog.is_wal_file(fullname): timelines[timeline] = comp_manager.get_wal_file_info( fullname) break # Return the timeline map return timelines
def get_next_batch(self): """ Returns the next batch of WAL files that have been archived via streaming replication (in the 'streaming' directory) This method always leaves one file in the "streaming" directory, because the 'pg_receivexlog' process needs at least one file to detect the current streaming position after a restart. :return: WalArchiverBatch: list of WAL files """ # List and sort all files in the incoming directory file_names = glob( os.path.join(self.config.streaming_wals_directory, '*')) file_names.sort() # Process anything that looks like a valid WAL file, # including partial ones. # Anything else is treated like an error/anomaly files = [] skip = [] errors = [] for file_name in file_names: if xlog.is_wal_file(file_name) and os.path.isfile(file_name): files.append(file_name) elif xlog.is_partial_file(file_name) and os.path.isfile(file_name): skip.append(file_name) else: errors.append(file_name) # In case of more than a partial file, keep the last # and treat the rest as errors if len(skip) > 1: errors.extend(skip[:-1]) skip = skip[-1:] # Keep the last full WAL file in case no partial file is present elif len(skip) == 0 and files: skip.append(files.pop()) # Build the list of WalFileInfo wal_files = [WalFileInfo.from_file(f, compression=None) for f in files] return WalArchiverBatch(wal_files, errors=errors, skip=skip)
def get_next_batch(self): """ Returns the next batch of WAL files that have been archived via streaming replication (in the 'streaming' directory) This method always leaves one file in the "streaming" directory, because the 'pg_receivexlog' process needs at least one file to detect the current streaming position after a restart. :return: WalArchiverBatch: list of WAL files """ # List and sort all files in the incoming directory file_names = glob(os.path.join( self.config.streaming_wals_directory, '*')) file_names.sort() # Process anything that looks like a valid WAL file, # including partial ones. # Anything else is treated like an error/anomaly files = [] skip = [] errors = [] for file_name in file_names: if xlog.is_wal_file(file_name) and os.path.isfile(file_name): files.append(file_name) elif xlog.is_partial_file(file_name) and os.path.isfile(file_name): skip.append(file_name) else: errors.append(file_name) # In case of more than a partial file, keep the last # and treat the rest as errors if len(skip) > 1: errors.extend(skip[:-1]) skip = skip[-1:] # Keep the last full WAL file in case no partial file is present elif len(skip) == 0 and files: skip.append(files.pop()) # Build the list of WalFileInfo wal_files = [WalFileInfo.from_file(f, compression=None) for f in files] return WalArchiverBatch(wal_files, errors=errors, skip=skip)
def get_wal_until_next_backup(self, backup): '''Get the xlog files between backup and the next :param backup: a backup object, the starting point to retrieve wals ''' begin = backup.begin_wal next_end = None if self.get_next_backup(backup.backup_id): next_end = self.get_next_backup(backup.backup_id).end_wal backup_tli, _, _ = xlog.decode_segment_name(begin) with self.xlogdb() as fxlogdb: for line in fxlogdb: name, size, _, _ = self.xlogdb_parse_line(line) if name < begin: continue tli, _, _ = xlog.decode_segment_name(name) if tli > backup_tli: continue if not xlog.is_wal_file(name): continue if next_end and name > next_end: break # count yield (name, size)
def get_remote_status(self): '''Get the status of the remote server''' pg_settings = ('archive_mode', 'archive_command', 'data_directory') pg_query_keys = ('server_txt_version', 'current_xlog') result = dict.fromkeys(pg_settings + pg_query_keys, None) try: with self.pg_connect() as conn: for name in pg_settings: result[name] = self.get_pg_setting(name) try: cur = conn.cursor() cur.execute("SELECT version()") result['server_txt_version'] = cur.fetchone()[0].split()[1] except: result['server_txt_version'] = None try: cur = conn.cursor() cur.execute( 'SELECT pg_xlogfile_name(pg_current_xlog_location())') result['current_xlog'] = cur.fetchone()[0] except: result['current_xlog'] = None except: pass cmd = Command(self.ssh_command, self.ssh_options) result['last_shipped_wal'] = None if result['data_directory'] and result['archive_command']: archive_dir = os.path.join(result['data_directory'], 'pg_xlog', 'archive_status') out = cmd.getoutput(None, 'ls', '-tr', archive_dir)[0] for line in out.splitlines(): if line.endswith('.done'): name = line[:-5] if xlog.is_wal_file(name): result['last_shipped_wal'] = line[:-5] return result
def rebuild_xlogdb(self): """ Rebuild the whole xlog database guessing it from the archive content. """ from os.path import isdir, join output.info("Rebuilding xlogdb for server %s", self.config.name) root = self.config.wals_directory comp_manager = self.compression_manager wal_count = label_count = history_count = 0 # lock the xlogdb as we are about replacing it completely with self.server.xlogdb("w") as fxlogdb: xlogdb_dir = os.path.dirname(fxlogdb.name) with tempfile.TemporaryFile(mode="w+", dir=xlogdb_dir) as fxlogdb_new: for name in sorted(os.listdir(root)): # ignore the xlogdb and its lockfile if name.startswith(self.server.XLOG_DB): continue fullname = join(root, name) if isdir(fullname): # all relevant files are in subdirectories hash_dir = fullname for wal_name in sorted(os.listdir(hash_dir)): fullname = join(hash_dir, wal_name) if isdir(fullname): _logger.warning( "unexpected directory " "rebuilding the wal database: %s", fullname, ) else: if xlog.is_wal_file(fullname): wal_count += 1 elif xlog.is_backup_file(fullname): label_count += 1 elif fullname.endswith(".tmp"): _logger.warning( "temporary file found " "rebuilding the wal database: %s", fullname, ) continue else: _logger.warning( "unexpected file " "rebuilding the wal database: %s", fullname, ) continue wal_info = comp_manager.get_wal_file_info( fullname) fxlogdb_new.write(wal_info.to_xlogdb_line()) else: # only history files are here if xlog.is_history_file(fullname): history_count += 1 wal_info = comp_manager.get_wal_file_info(fullname) fxlogdb_new.write(wal_info.to_xlogdb_line()) else: _logger.warning( "unexpected file rebuilding the wal database: %s", fullname, ) fxlogdb_new.flush() fxlogdb_new.seek(0) fxlogdb.seek(0) shutil.copyfileobj(fxlogdb_new, fxlogdb) fxlogdb.truncate() output.info( "Done rebuilding xlogdb for server %s " "(history: %s, backup_labels: %s, wal_file: %s)", self.config.name, history_count, label_count, wal_count, )
def rebuild_xlogdb(self): """ Rebuild the whole xlog database guessing it from the archive content. """ from os.path import isdir, join output.info("Rebuilding xlogdb for server %s", self.config.name) root = self.config.wals_directory default_compression = self.config.compression wal_count = label_count = history_count = 0 # lock the xlogdb as we are about replacing it completely with self.server.xlogdb('w') as fxlogdb: xlogdb_new = fxlogdb.name + ".new" with open(xlogdb_new, 'w') as fxlogdb_new: for name in sorted(os.listdir(root)): # ignore the xlogdb and its lockfile if name.startswith(self.server.XLOG_DB): continue fullname = join(root, name) if isdir(fullname): # all relevant files are in subdirectories hash_dir = fullname for wal_name in sorted(os.listdir(hash_dir)): fullname = join(hash_dir, wal_name) if isdir(fullname): _logger.warning( 'unexpected directory ' 'rebuilding the wal database: %s', fullname) else: if xlog.is_wal_file(fullname): wal_count += 1 elif xlog.is_backup_file(fullname): label_count += 1 else: _logger.warning( 'unexpected file ' 'rebuilding the wal database: %s', fullname) continue wal_info = WalFileInfo.from_file( fullname, default_compression=default_compression) fxlogdb_new.write(wal_info.to_xlogdb_line()) else: # only history files are here if xlog.is_history_file(fullname): history_count += 1 wal_info = WalFileInfo.from_file( fullname, default_compression=default_compression) fxlogdb_new.write(wal_info.to_xlogdb_line()) else: _logger.warning( 'unexpected file ' 'rebuilding the wal database: %s', fullname) os.fsync(fxlogdb_new.fileno()) shutil.move(xlogdb_new, fxlogdb.name) fsync_dir(os.path.dirname(fxlogdb.name)) output.info('Done rebuilding xlogdb for server %s ' '(history: %s, backup_labels: %s, wal_file: %s)', self.config.name, history_count, label_count, wal_count)
def rebuild_xlogdb(self): """ Rebuild the whole xlog database guessing it from the archive content. """ from os.path import isdir, join output.info("Rebuilding xlogdb for server %s", self.config.name) root = self.config.wals_directory comp_manager = self.compression_manager wal_count = label_count = history_count = 0 # lock the xlogdb as we are about replacing it completely with self.server.xlogdb('w') as fxlogdb: xlogdb_new = fxlogdb.name + ".new" with open(xlogdb_new, 'w') as fxlogdb_new: for name in sorted(os.listdir(root)): # ignore the xlogdb and its lockfile if name.startswith(self.server.XLOG_DB): continue fullname = join(root, name) if isdir(fullname): # all relevant files are in subdirectories hash_dir = fullname for wal_name in sorted(os.listdir(hash_dir)): fullname = join(hash_dir, wal_name) if isdir(fullname): _logger.warning( 'unexpected directory ' 'rebuilding the wal database: %s', fullname) else: if xlog.is_wal_file(fullname): wal_count += 1 elif xlog.is_backup_file(fullname): label_count += 1 elif fullname.endswith('.tmp'): _logger.warning( 'temporary file found ' 'rebuilding the wal database: %s', fullname) continue else: _logger.warning( 'unexpected file ' 'rebuilding the wal database: %s', fullname) continue wal_info = comp_manager.get_wal_file_info( fullname) fxlogdb_new.write(wal_info.to_xlogdb_line()) else: # only history files are here if xlog.is_history_file(fullname): history_count += 1 wal_info = comp_manager.get_wal_file_info(fullname) fxlogdb_new.write(wal_info.to_xlogdb_line()) else: _logger.warning( 'unexpected file ' 'rebuilding the wal database: %s', fullname) os.fsync(fxlogdb_new.fileno()) shutil.move(xlogdb_new, fxlogdb.name) fsync_dir(os.path.dirname(fxlogdb.name)) output.info( 'Done rebuilding xlogdb for server %s ' '(history: %s, backup_labels: %s, wal_file: %s)', self.config.name, history_count, label_count, wal_count)