Skip to content

Instantly share code, notes, and snippets.

@keymon
Created January 15, 2012 04:17
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save keymon/1614287 to your computer and use it in GitHub Desktop.
Save keymon/1614287 to your computer and use it in GitHub Desktop.
MYSQL script to backup the DB using LVM
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Requirements
# - Data files must be in lvm
# - Optionally in xfs (xfs_freeze)
# - User must have LOCK TABLES and RELOAD privilieges::
#
# grant LOCK TABLES, RELOAD on *.*
# to backupuser@localhost
# identified by 'backupassword';
#
import MySQLdb
import sys
import os
from datetime import datetime
# DB Configuration
MYSQL_HOST = "localhost" # Where the slave is
MYSQL_PORT = 3306
MYSQL_USER = "backupuser"
MYSQL_PASSWD = "backupassword"
MYSQL_DB = "appdb"
# Datafiles location and LVM information
DATA_FILES_PATH = "/mysql/data" # do not add / at the end
DATA_FILES_LV = "/dev/datavg/datalv"
SNAPSHOT_SIZE = "10G" # tune de size as needed.
SNAPSHOT_MOUNTPOINT = "/mysql/data.snapshot" # do not add / at the end
# Backup target conf
BACKUP_DESTINATION = "/mysql/data.backup"
#----------------------------------------------------------------
# Commands
# Avoids sudo ask the password
#SUDO = "SUDO_ASKPASS=/bin/true /usr/bin/sudo -A "
SUDO = "sudo"
LVCREATE_CMD = "%s /sbin/lvcreate" % SUDO
LVREMOVE_CMD = "%s /sbin/lvremove" % SUDO
MOUNT_CMD = "%s /bin/mount" % SUDO
UMOUNT_CMD = "%s /bin/umount" % SUDO
# There is a bug in this command with the locale, we set LANG=
XFS_FREEZE_CMD = "LANG= %s /usr/sbin/xfs_freeze" % SUDO
RSYNC_CMD = "%s /usr/bin/rsync" % SUDO
#----------------------------------------------------------------
# MySQL functions
def mysql_connect():
dbconn = MySQLdb.connect (host = MYSQL_HOST,
port = MYSQL_PORT,
user = MYSQL_USER,
passwd = MYSQL_PASSWD,
db = MYSQL_DB)
return dbconn
def mysql_lock_tables(dbconn):
sqlcmd = "FLUSH TABLES WITH READ LOCK"
print "Locking tables: %s" % sqlcmd
cursor = dbconn.cursor()
cursor.execute(sqlcmd)
cursor.close()
def mysql_unlock_tables(dbconn):
sqlcmd = "UNLOCK TABLES"
print "Unlocking tables: %s" % sqlcmd
cursor = dbconn.cursor()
cursor.execute(sqlcmd)
cursor.close()
#----------------------------------------------------------------
# LVM operations
class FailedLvmOperation(Exception):
pass
# Get the fs type with a common shell script
def get_fs_type(fs_path):
p = os.popen('mount | grep $(df %s |grep /dev |'\
'cut -f 1 -d " ") | cut -f 3,5 -d " "' % fs_path)
(fs_mountpoint, fs_type) = p.readline().strip().split(' ')
p.close()
return (fs_mountpoint, fs_type)
def lvm_create_snapshot():
# XFS filesystem supports freezing. That is convenient before the snapshot
(fs_mountpoint, fs_type) = get_fs_type(DATA_FILES_PATH)
if fs_type == 'xfs':
print "Freezing '%s'" % fs_mountpoint
os.system('%s -f %s' % (XFS_FREEZE_CMD, fs_mountpoint))
newlv_name = "%s_backup_%ilv" % \
(DATA_FILES_LV.split('/')[-1], os.getpid())
cmdline = "%s --snapshot %s -L%s --name %s" % \
(LVCREATE_CMD, DATA_FILES_LV, SNAPSHOT_SIZE, newlv_name)
print "Creating the snapshot backup LV '%s' from '%s'" % \
(newlv_name, DATA_FILES_LV)
print " # %s" % cmdline
ret = os.system(cmdline)
# Always unfreeze!!
if fs_type == 'xfs':
print "Unfreezing '%s'" % fs_mountpoint
os.system('%s -u %s' % (XFS_FREEZE_CMD, fs_mountpoint))
if ret != 0: raise FailedLvmOperation
# Return the path to the device
return '/'.join(DATA_FILES_LV.split('/')[:-1]+[newlv_name])
def lvm_remove_snapshot(lv_name):
cmdline = "%s -f %s" % \
(LVREMOVE_CMD, lv_name)
print "Removing the snapshot backup LV '%s'" % lv_name
print " # %s" % cmdline
ret = os.system(cmdline)
if ret != 0:
raise FailedLvmOperation
#----------------------------------------------------------------
# Mount the filesystem
class FailedMountOperation(Exception):
pass
def mount_snapshot(lv_name):
# XFS filesystem needs nouuid option to mount snapshots
(fs_mountpoint, fs_type) = get_fs_type(DATA_FILES_PATH)
if fs_type == 'xfs':
cmdline = "%s -o nouuid %s %s" % \
(MOUNT_CMD, lv_name, SNAPSHOT_MOUNTPOINT)
else:
cmdline = "%s %s %s" % (MOUNT_CMD, lv_name, SNAPSHOT_MOUNTPOINT)
print "Mounting the snapshot backup LV '%s' on '%s'" % \
(lv_name, SNAPSHOT_MOUNTPOINT)
print " # %s" % cmdline
ret = os.system(cmdline)
if ret != 0:
raise FailedMountOperation
def umount_snapshot(lv_name):
cmdline = "%s %s" % (UMOUNT_CMD, SNAPSHOT_MOUNTPOINT)
print "Unmounting the snapshot backup LV '%s' from '%s'" % \
(lv_name, SNAPSHOT_MOUNTPOINT)
print " # %s" % cmdline
ret = os.system(cmdline)
if ret != 0:
raise FailedMountOperation
#----------------------------------------------------------------
# Perform the backup process. For instance, an rsync
class FailedBackupOperation(Exception):
pass
def do_backup():
cmdline = "%s --progress -av %s/ %s" % \
(RSYNC_CMD, DATA_FILES_PATH, BACKUP_DESTINATION)
print "Executing the backup"
print " # %s" % cmdline
ret = os.system(cmdline)
if ret != 0:
raise FailedBackupOperation
def main():
dbconn = mysql_connect()
mysql_lock_tables(dbconn)
start_time = datetime.now()
# Critical, tables are locked!
snapshotlv = ''
try:
snapshotlv = lvm_create_snapshot()
except:
print "Backup failed."
raise
finally:
mysql_unlock_tables(dbconn)
dbconn.close()
print "Tables had been locked for %s" % str(datetime.now()-start_time)
try:
mount_snapshot(snapshotlv)
do_backup()
umount_snapshot(snapshotlv)
lvm_remove_snapshot(snapshotlv)
except:
print "Backup failed. Snapshot LV '%s' still exists. " % snapshotlv
raise
print "Backup completed. Elapsed time %s" % str(datetime.now()-start_time)
if __name__ == '__main__':
main()
@glaszig
Copy link

glaszig commented Oct 20, 2020

according to the tldp lvm howto, xfs_freeze has not been necessary for 15 years now:

Revision 0.15 | 2005-06-09 | Revised by: ajl
Removed references to xfs_freeze - it is no longer needed

@john-jinghai-ma
Copy link

I think there is a serious bug: Line 171 does backup from DATA_FILES_PATH, the snapshot is ignored completely.
I am actually using this script. A review from the author will be greatly appreciated.

@glaszig
Copy link

glaszig commented Jan 25, 2023

I think there is a serious bug: Line 171 does backup from DATA_FILES_PATH, the snapshot is ignored completely. I am actually using this script. A review from the author will be greatly appreciated.

you're exactly right. it should make a backup from SNAPSHOT_MOUNTPOINT:

    cmdline = "%s --progress -av %s/ %s" % \
                (RSYNC_CMD, SNAPSHOT_MOUNTPOINT, BACKUP_DESTINATION)

@keymon
Copy link
Author

keymon commented Jan 26, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment