# recovery/pitr_tester.pyimportpsycopg2importsubprocessimporttimefromdatetimeimportdatetime,timedeltaimportloggingclassPITRTester:def__init__(self,config):self.config=configself.setup_logging()defsetup_logging(self):"""Setup logging."""logging.basicConfig(level=logging.INFO)self.logger=logging.getLogger(__name__)deftest_pitr_recovery(self,target_time):"""Test point-in-time recovery."""try:# Create test dataself.create_test_data()# Perform recoveryrecovery_result=self.perform_recovery(target_time)ifrecovery_result:# Verify recoveryverification_result=self.verify_recovery(target_time)returnverification_resultelse:returnFalseexceptExceptionase:self.logger.error(f"PITR test failed: {e}")returnFalsedefcreate_test_data(self):"""Create test data for recovery testing."""conn=psycopg2.connect(**self.config)withconn.cursor()ascur:# Create test tablecur.execute(""" CREATE TABLE IF NOT EXISTS recovery_test ( id SERIAL PRIMARY KEY, data TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); """)# Insert test dataforiinrange(10):cur.execute(""" INSERT INTO recovery_test (data) VALUES (%s) """,(f"test_data_{i}",))conn.commit()conn.close()defperform_recovery(self,target_time):"""Perform point-in-time recovery."""try:# Stop PostgreSQLsubprocess.run(['systemctl','stop','postgresql'],check=True)# Run recovery scriptcmd=['./scripts/pitr_recovery.sh',target_time]result=subprocess.run(cmd,capture_output=True,text=True)ifresult.returncode==0:self.logger.info("Recovery completed successfully")returnTrueelse:self.logger.error(f"Recovery failed: {result.stderr}")returnFalseexceptExceptionase:self.logger.error(f"Recovery error: {e}")returnFalsedefverify_recovery(self,target_time):"""Verify recovery results."""try:# Connect to recovered databaseconn=psycopg2.connect(**self.config)withconn.cursor()ascur:# Check if data exists at target timecur.execute(""" SELECT COUNT(*) FROM recovery_test WHERE created_at <= %s """,(target_time,))count=cur.fetchone()[0]ifcount>0:self.logger.info(f"Recovery verification successful: {count} records found")returnTrueelse:self.logger.error("Recovery verification failed: No data found")returnFalseexceptExceptionase:self.logger.error(f"Recovery verification error: {e}")returnFalse# Usageif__name__=="__main__":config={'host':'localhost','database':'production','user':'test_user','password':'test_password'}tester=PITRTester(config)target_time='2024-01-15 14:30:00'result=tester.test_pitr_recovery(target_time)print(f"PITR test result: {result}")
# backup/s3_backup.pyimportboto3importosfrompathlibimportPathfromdatetimeimportdatetimeimportloggingclassS3BackupManager:def__init__(self,config):self.config=configself.s3_client=boto3.client('s3')self.setup_logging()defsetup_logging(self):"""Setup logging."""logging.basicConfig(level=logging.INFO)self.logger=logging.getLogger(__name__)defupload_backup(self,backup_file,s3_key):"""Upload backup to S3."""try:self.s3_client.upload_file(backup_file,self.config['bucket'],s3_key,ExtraArgs={'ServerSideEncryption':'AES256','StorageClass':'STANDARD_IA'})self.logger.info(f"Backup uploaded to S3: {s3_key}")returnTrueexceptExceptionase:self.logger.error(f"S3 upload failed: {e}")returnFalsedefdownload_backup(self,s3_key,local_file):"""Download backup from S3."""try:self.s3_client.download_file(self.config['bucket'],s3_key,local_file)self.logger.info(f"Backup downloaded from S3: {s3_key}")returnTrueexceptExceptionase:self.logger.error(f"S3 download failed: {e}")returnFalsedeflist_backups(self,prefix=""):"""List available backups in S3."""try:response=self.s3_client.list_objects_v2(Bucket=self.config['bucket'],Prefix=prefix)backups=[]forobjinresponse.get('Contents',[]):backups.append({'key':obj['Key'],'size':obj['Size'],'last_modified':obj['LastModified']})returnsorted(backups,key=lambdax:x['last_modified'],reverse=True)exceptExceptionase:self.logger.error(f"S3 list failed: {e}")return[]defcleanup_old_backups(self,retention_days):"""Remove old backups from S3."""try:backups=self.list_backups()cutoff_date=datetime.now()-timedelta(days=retention_days)forbackupinbackups:ifbackup['last_modified'].replace(tzinfo=None)<cutoff_date:self.s3_client.delete_object(Bucket=self.config['bucket'],Key=backup['key'])self.logger.info(f"Deleted old backup: {backup['key']}")exceptExceptionase:self.logger.error(f"S3 cleanup failed: {e}")# Usageif__name__=="__main__":config={'bucket':'postgresql-backups','region':'us-west-2'}s3_manager=S3BackupManager(config)# Upload backupbackup_file='/var/backups/postgresql/production_full_20240115.dump.gz's3_key=f"backups/{datetime.now().strftime('%Y/%m/%d')}/production_full.dump.gz"s3_manager.upload_backup(backup_file,s3_key)# List backupsbackups=s3_manager.list_backups()forbackupinbackups:print(f"{backup['key']} - {backup['last_modified']}")
# monitoring/backup_monitor.pyimportpsycopg2importsmtplibfromemail.mime.textimportMIMETextfromdatetimeimportdatetime,timedeltaimportloggingclassBackupMonitor:def__init__(self,config):self.config=configself.setup_logging()defsetup_logging(self):"""Setup logging."""logging.basicConfig(level=logging.INFO)self.logger=logging.getLogger(__name__)defcheck_backup_status(self):"""Check backup status and send alerts if needed."""try:# Check if backup completed todaybackup_status=self.get_backup_status()ifnotbackup_status['completed']:self.send_alert("Backup Failed","Daily backup did not complete")returnFalse# Check backup sizeifbackup_status['size']<self.config['min_backup_size']:self.send_alert("Backup Size Warning",f"Backup size {backup_status['size']} is below minimum")returnFalse# Check backup ageifbackup_status['age_hours']>24:self.send_alert("Backup Age Warning",f"Backup is {backup_status['age_hours']} hours old")returnFalseself.logger.info("Backup status check passed")returnTrueexceptExceptionase:self.logger.error(f"Backup status check failed: {e}")returnFalsedefget_backup_status(self):"""Get current backup status."""backup_dir=Path(self.config['backup_dir'])today=datetime.now().date()# Find today's backupbackup_files=list(backup_dir.glob(f"*_full_{today.strftime('%Y%m%d')}*.dump.gz"))ifnotbackup_files:return{'completed':False,'size':0,'age_hours':24}latest_backup=max(backup_files,key=lambdax:x.stat().st_mtime)return{'completed':True,'size':latest_backup.stat().st_size,'age_hours':(datetime.now()-datetime.fromtimestamp(latest_backup.stat().st_mtime)).total_seconds()/3600}defsend_alert(self,subject,message):"""Send alert email."""try:msg=MIMEText(message)msg['Subject']=subjectmsg['From']=self.config['from_email']msg['To']=self.config['to_email']server=smtplib.SMTP(self.config['smtp_server'],self.config['smtp_port'])server.starttls()server.login(self.config['username'],self.config['password'])server.send_message(msg)server.quit()self.logger.info(f"Alert sent: {subject}")exceptExceptionase:self.logger.error(f"Failed to send alert: {e}")# Usageif__name__=="__main__":config={'backup_dir':'/var/backups/postgresql','min_backup_size':100*1024*1024,# 100MB'from_email':'alerts@company.com','to_email':'dba@company.com','smtp_server':'smtp.company.com','smtp_port':587,'username':'alerts@company.com','password':'smtp_password'}monitor=BackupMonitor(config)monitor.check_backup_status()
# 1. Full backuppg_dump-hlocalhost-Ubackup_user-dproduction--format=custom--compress=9--file=production_full.dump
# 2. Base backup for PITRpg_basebackup-hlocalhost-Ubackup_user-D/var/backups/base_backup--format=tar--gzip
# 3. Restore from backuppg_restore-hlocalhost-Urestore_user-drestored_db--clean--if-existsproduction_full.dump