Skip to content

Commit

Permalink
Added mysql support command mostly for dumping the database.
Browse files Browse the repository at this point in the history
Added rackspace support for uploading to and listing rackspace containers.
Added backup command to integrate mysql dumping and rackspace uploading.
  • Loading branch information
Derrick Petzold committed Feb 23, 2012
1 parent 1d2af9e commit 5a9ddea
Show file tree
Hide file tree
Showing 3 changed files with 371 additions and 0 deletions.
141 changes: 141 additions & 0 deletions django_extensions/management/commands/backup.py
@@ -0,0 +1,141 @@
from __future__ import print_function

from optparse import make_option

import datetime
import re
import socket
import subprocess
import sys
import os

import django
from django.core.management.base import CommandError, BaseCommand
from django.conf import settings

from django_extensions.management.commands.mysql import MysqlCommand
from django_extensions.management.commands.rackspace import RackspaceCommand

class BackupCommand(object):

def __init__(self, router, container_name, verbose=False):
self.mysql = MysqlCommand(router, verbose)
self.rackspace = RackspaceCommand(container_name, verbose)
self.verbose = verbose
self.start_time = datetime.datetime.now()

def _run_cmd(self, cmd, filepath, ext=None):
if self.verbose:
print(cmd)
subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
if ext is not None:
filepath += ext
filesize = os.stat(filepath).st_size
if filesize == 0:
raise baku_exception.BadFileSize('Bad filesize for "%s"' % (filepath))
return filepath, filesize

def tar_directory(self, directory, prefix=None):
root, name = os.path.split(directory)
name = '%s.%s-%s.tar.bz2' % \
(name, self.start_time.strftime('%Y%m%d_%H%M%S'), socket.gethostname())
if prefix is not None:
backup_dir = '%s/%s' % (settings.BACKUP_DIR, prefix)
else:
backup_dir = settings.BACKUP_DIR
if not os.path.exists(backup_dir):
os.mkdir(backup_dir)
filepath = '%s/%s' % (backup_dir, name)
cmd = '/bin/tar cfj %s %s -C %s' % (filepath, directory, root)
return self._run_cmd(cmd, filepath)

def cull(self):
culled = []
files = os.listdir(settings.BACKUP_DIR)
for filename in files:
for date in self.rackspace.culls:
if self.verbose:
print('Checking %s %s' % (date, filename))
search = re.search('\.%s_' % (date), filename)
if search is not None:
filepath = '%s/%s/' % (settings.BACKUP_DIR, filename)
if self.verbose:
print('Deleting %s' % (filepath))
if not self.dry_run:
os.unlink(filepath)
culled.append(filepath)
return culled

def backup_database(self):
dbfile = self.mysql.dump()
uploaded = self.rackspace.upload(dbfile, 'db')
if self.verbose:
print(uploaded.name)
return uploaded

def backup_site(self):
filepath, filesize = self.tar_directory(settings.SITE_ROOT, 'site')
if self.verbose:
print('%s %s' % (filepath, filesize))
uploaded = self.rackspace.upload(filepath, 'site')
if self.verbose:
print(uploaded.name)
return uploaded

def backup_all(self):
db_upload = self.backup_database()
site_upload = self.backup_site()
deletes = self.rackspace.cull()
if self.verbose:
for deleted in deletes:
print('Deleted: %s' % (deleted.name))
deletes = self.cull()
if self.verbose:
for deleted in deletes:
print('Deleted: %s' % (deleted.name))

class Command(BaseCommand):

option_list = BaseCommand.option_list + (
make_option('-D', '--db', action='store_true',
dest='db', default=False,
help='Backup the database.'),
make_option('-C', '--contianer', action='store',
dest='container',
default=settings.RACKSPACE_BACKUP_CONTAINER,
help='The container to use.'),
make_option('--cull', action='store_true',
dest='cull', default=False,
help='Cull the backup files.'),
make_option('-R', '--router', action='store',
dest='router', default='default',
help='Use this router and not the default.'),
make_option('-S', '--site', action='store_true',
dest='site', default=False,
help='Backup the site directory.'),
make_option('-U', '--upload', action='store',
dest='upload', help='Upload the file to rackspace.'),
make_option('-V', '--verbose', action='store_true',
dest='verbose', default=False,
help='Be verbose.'),
)
help = """Backups the database and the site directory."""

requires_model_validation = False
can_import_settings = True

def handle(self, *args, **options):

backup = BackupCommand(
options.get('router'),
options.get('container'),
options.get('verbose'))

if options.get('cull'):
backup = backup.cull()
elif options.get('db'):
backup = backup.backup_database()
elif options.get('site'):
backup = backup.backup_site()
else:
backup.backup_all()
97 changes: 97 additions & 0 deletions django_extensions/management/commands/mysql.py
@@ -0,0 +1,97 @@
from __future__ import print_function

from optparse import make_option

import datetime
import socket
import sys
import os

import django
from django.core.management.base import CommandError, BaseCommand
from django.conf import settings

class MysqlCommand(object):

def __init__(self, router, verbose=False):

self.db = settings.DATABASES[router]
self.verbose = verbose

cmd = '%s '
if self.db['HOST'] != '':
cmd += '-h %s' % (self.db['HOST'])
cmd += '-p %s' % (self.db['PORT'])
cmd += '-u {user} --password={password}'.format(
user=self.db['USER'],
password=self.db['PASSWORD'])
self.mysqlbase = cmd

@property
def mysql(self):
return self.mysqlbase % ('mysql')

@property
def mysqladmin(self):
return self.mysqlbase % ('mysqladmin')

@property
def mysqldump(self):
return self.mysqlbase % ('mysqldump')

def drop(self):
os.system('{mysqladmin} -f DROP {database}'.format(
mysqladmin=self.mysqladmin,
database=self.db['NAME']))

def create(self):
os.sytem('{mysqladmin} CREATE {database}'.format(
mysqladmin=self.mysqladmin,
database=self.db['NAME']))

def source(self):
os.system('{mysql} {database} < {db_dump}'.format(
mysql=self.mysql,
database=self['NAME'],
db_dump=db_dump))

def dump(self, prefix='db'):

backup_dir = '%s/%s' % (settings.BACKUP_DIR, prefix)
if not os.path.exists(backup_dir):
os.mkdir(backup_dir)
dbfile = '{backup_dir}/{database}.{date}-{hostname}.gz'.format(
backup_dir=backup_dir,
database=self.db['NAME'],
date=datetime.datetime.now().strftime('%Y%m%d_%H%M%S'),
hostname=socket.gethostname())
output = os.system('{mysqldump} {database} | gzip > {db_file}'.format(
mysqldump=self.mysqldump,
db_file=dbfile,
database=self.db['NAME']))
return dbfile

class Command(BaseCommand):

option_list = BaseCommand.option_list + (
make_option('-D', '--dump', action='store_true',
dest='dump', default=False,
help='Dump the database to the backup dir.'),
make_option('-R', '--router', action='store',
dest='router', default='default',
help='Use this router-database other then defined in settings.py'),
make_option('-V', '--verbose', action='store_true',
dest='verbose', default=False,
help='Be verbose.'),
)
help = """Backups the database and the site directory."""

requires_model_validation = False
can_import_settings = True

def handle(self, *args, **options):

mysql = MysqlCommand(options.get('router'))
if options.get('dump'):
dbfile = mysql.dump()
print(dbfile)
133 changes: 133 additions & 0 deletions django_extensions/management/commands/rackspace.py
@@ -0,0 +1,133 @@
from __future__ import print_function

from optparse import make_option

import datetime
import re
import socket
import sys
import os

import django
from django.core.management.base import CommandError, BaseCommand
from django.conf import settings

import cloudfiles

class RackspaceCommand(object):

def __init__(self, container_name, verbose, dry_run=False, forward=0):
self.container_name = container_name
self.verbose = verbose
self.dry_run = dry_run
self.rp_conn = cloudfiles.get_connection(
settings.RACKSPACE_USER,
settings.RACKSPACE_API_KEY)
self.start_time = datetime.datetime.now()

if forward == 0:
self.start_time = datetime.datetime.now()
else:
self.start_time = datetime.datetime.now() + datetime.timedelta(days=forward)

@property
def culls(self):
dates = []
lastweeks = self.start_time - datetime.timedelta(weeks=1)

# delete last weeks except mondays
if lastweeks.weekday() != 1 and lastweeks.day != 1:
dates.append(lastweeks.strftime('%Y%m%d'))

# keep 8 weeks of mondays
lastmonths = self.start_time - datetime.timedelta(weeks=8)
if lastmonths.weekday() == 1 and lastmonths.day != 1:
dates.append(lastmonths.strftime('%Y%m%d'))
return dates

def cull(self):
culled = []
container = self.rp_conn.get_container(self.container_name)
for obj in container.get_objects():
for date in self.culls:
if self.verbose:
print('Checking %s %s' % (date, obj.name))
search = re.search('\.%s_' % (date), obj.name)
if search is not None:
if self.verbose:
print('Deleting %s' % (obj.name))
if not self.dry_run:
container.delete_object(obj.name)
culled.append(obj)
return culled

def list(self):
container = self.rp_conn.get_container(self.container_name)
for obj in container.get_objects():
print('%s %s' % (obj.name, obj.size))

def upload(self, filepath, prefix=None):
root, filename = os.path.split(filepath)
container = self.rp_conn.get_container(self.container_name)
if prefix:
filename = '%s/%s' % (prefix, filename)
obj = container.create_object(filename)
obj.load_from_filename(filepath)
return obj

def delete(self, filename, prefix=None):
container = self.rp_conn.get_container(self.container_name)
return container.delete_object(filename)

class Command(BaseCommand):

option_list = BaseCommand.option_list + (
make_option('--cull', action='store_true',
dest='cull', default=False,
help='Cull the remote backup files.'),
make_option('-C', '--contianer', action='store',
dest='container',
default=settings.RACKSPACE_BACKUP_CONTAINER,
help='The container to use.'),
make_option('-D', '--delete', action='store', dest='delete',
help='Delete the specified file from container.'),
make_option('-F', '--forward', action='store',
dest='forward', default=0,
help='Pretend to run the specified days forward.'),
make_option('-L', '--list', action='store_true',
dest='list', default=False,
help='List the backup dir.'),
make_option('-N', '--dry-run', action='store_true',
dest='dry_run', default=False,
help='Dry run.'),
make_option('-U', '--upload', action='store',
dest='upload', help='Upload the file to rackspace.'),
make_option('-V', '--verbose', action='store_true',
dest='verbose', default=False,
help='Be verbose.'),
)
help = """Backups the database and the site directory."""

requires_model_validation = False
can_import_settings = True

def handle(self, *args, **options):

rackspace = RackspaceCommand(
options.get('container'),
options.get('verbose'),
options.get('dry_run'),
int(options.get('forward')))

if options.get('cull'):
deletes = rackspace.cull()
for deleted in deletes:
print('Deleted: %s' % (deleted.name))
elif options.get('delete'):
rackspace.delete(options.get('delete'))
elif options.get('list'):
rackspace.list()
elif options.get('upload'):
backup = rackspace.upload(options.get('upload'))
print(backup)

0 comments on commit 5a9ddea

Please sign in to comment.