Ticket #2304: backup.patch
| File backup.patch, 8.5 KB (added by Shane Caraveo <shanec@…>, 3 years ago) |
|---|
-
trac/db/api.py
diff --git a/trac/db/api.py b/trac/db/api.py index 829b7e6..715c32d 100644
a b 16 16 17 17 import os 18 18 import urllib 19 import time 19 20 20 21 from trac.config import Option, IntOption 21 22 from trac.core import * … … class IDatabaseConnector(Interface): 48 49 def to_sql(table): 49 50 """Return the DDL statements necessary to create the specified table, 50 51 including indices.""" 52 53 def backup(dest): 54 """Backup the database to a location defined by trac.backup_dir""" 51 55 52 56 53 57 class DatabaseManager(Component): … … class DatabaseManager(Component): 59 63 [wiki:TracEnvironment#DatabaseConnectionStrings string] for this 60 64 project""") 61 65 66 backup_dir = Option('trac', 'backup_dir', 'db', 67 """Database backup location""") 68 62 69 timeout = IntOption('trac', 'timeout', '20', 63 70 """Timeout value for database connection, in seconds. 64 71 Use '0' to specify ''no timeout''. ''(Since 0.11)''""") … … class DatabaseManager(Component): 81 88 self._cnx_pool.shutdown(tid) 82 89 if not tid: 83 90 self._cnx_pool = None 91 92 def backup(self, dest=None): 93 connector, args = self._get_connector() 94 if not dest: 95 backup_dir = self.backup_dir 96 if backup_dir[0] != "/": 97 backup_dir = os.path.join(self.env.path, backup_dir) 98 db_str = self.config.get('trac', 'database') 99 db_name, db_path = db_str.split(":",1) 100 dest_name = '%s.%i.%d.bak' % (db_name, self.env.get_version(),int(time.time())) 101 dest = os.path.join(backup_dir, dest_name) 102 else: 103 backup_dir = os.path.dirname(dest) 104 if not os.path.exists(backup_dir): 105 os.makedirs(backup_dir) 106 connector.backup(dest) 84 107 85 108 def _get_connector(self): ### FIXME: Make it public? 86 109 scheme, args = _parse_db_str(self.connection_uri) -
trac/db/mysql_backend.py
diff --git a/trac/db/mysql_backend.py b/trac/db/mysql_backend.py index e69a83c..724030c 100644
a b class MySQLConnector(Component): 142 142 '_'.join(index.columns), table.name, 143 143 self._collist(table, index.columns)) 144 144 145 def backup(self, dest): 146 raise TracError("MySQL backup not implemented") 145 147 146 148 class MySQLConnection(ConnectionWrapper): 147 149 """Connection wrapper for MySQL.""" -
trac/db/postgres_backend.py
diff --git a/trac/db/postgres_backend.py b/trac/db/postgres_backend.py index 9c7b3b2..2df6e02 100644
a b 14 14 # 15 15 # Author: Christopher Lenz <cmlenz@gmx.de> 16 16 17 import re 17 import re, sys, os, time 18 18 19 19 from trac.core import * 20 from trac.db.api import IDatabaseConnector 20 from trac.config import Option 21 from trac.db.api import IDatabaseConnector, _parse_db_str 21 22 from trac.db.util import ConnectionWrapper 22 23 from trac.util import get_pkginfo 24 from subprocess import Popen, PIPE 23 25 24 26 psycopg = None 25 27 PgSQL = None … … class PostgreSQLConnector(Component): 33 35 34 36 implements(IDatabaseConnector) 35 37 38 pg_dump_bin = Option('trac', 'pg_dump_bin', 'pg_dump', 39 """Location of pg_dump for Postgres database backups""") 40 compression = Option('trac', 'backup_compression', '8', 41 """Gzip backup compression level (if supported by backend)""") 42 36 43 def __init__(self): 37 44 self._version = None 38 45 … … class PostgreSQLConnector(Component): 65 72 cnx = self.get_connection(path, user, password, host, port, params) 66 73 cursor = cnx.cursor() 67 74 if cnx.schema: 68 cursor.execute('CREATE SCHEMA "%s"' % cnx.schema) 69 cursor.execute('SET search_path TO %s', (cnx.schema,)) 75 try: 76 cursor.execute('CREATE SCHEMA "%s"' % cnx.schema) 77 cursor.execute('SET search_path TO %s', (cnx.schema,)) 78 cnx.commit() 79 except Exception, e: 80 cnx.rollback() 70 81 from trac.db_default import schema 71 82 for table in schema: 72 83 for stmt in self.to_sql(table): … … class PostgreSQLConnector(Component): 93 104 yield 'CREATE %s INDEX "%s_%s_idx" ON "%s" ("%s")' % (unique, table.name, 94 105 '_'.join(index.columns), table.name, '","'.join(index.columns)) 95 106 107 def backup(self, dest_file): 108 # pg_dump -n schemaname dbname | gzip > filename.gz 109 db_url = self.env.config.get('trac', 'database') 110 scheme, db_prop = _parse_db_str(db_url) 111 db_name = os.path.basename(db_prop['path']) 112 args = [self.pg_dump_bin, '-C', '-d', '-x', '-Z', self.compression, 113 '-U', db_prop['user'], 114 '-h', db_prop['host'], 115 '-p', str(db_prop['port'])] 116 117 if 'schema' in db_prop['params']: 118 args.extend(['-n', db_prop['params']['schema'], db_name]) 119 else: 120 args.extend([db_name]) 121 122 dest_file = "%s.gz" % (dest_file,) 123 args.extend(['>', dest_file]) 124 if sys.platform == 'win': 125 # XXX TODO verify on windows 126 args = ['cmd', '/c', ' '.join(args)] 127 else: 128 args = ['bash', '-c', ' '.join(args)] 129 130 environ = os.environ.copy() 131 environ['PGPASSWORD'] = db_prop['password'] 132 #print >> sys.stderr, "backup command %r" % (args,) 133 #print >> sys.stderr, "backup props %r" % (db_prop,) 134 #print >> sys.stderr, "backup to %s" % dest_file 135 p = Popen(args, env=environ, shell=False, bufsize=0, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=True) 136 p.wait() 137 p.stdout.close() 138 p.stderr.close() 139 if not os.path.exists(dest_file): 140 raise TracError("Backup attempt failed") 96 141 97 142 class PostgreSQLConnection(ConnectionWrapper): 98 143 """Connection wrapper for PostgreSQL.""" -
trac/db/sqlite_backend.py
diff --git a/trac/db/sqlite_backend.py b/trac/db/sqlite_backend.py index 84a8dd9..bc69ee1 100644
a b class SQLiteConnector(Component): 146 146 def to_sql(cls, table): 147 147 return _to_sql(table) 148 148 149 def backup(self, dest_file): 150 """Simple SQLite-specific backup of the database. 151 152 @param dest: Destination file; if not specified, the backup is stored in 153 a file called db_name.trac_version.bak 154 """ 155 import shutil 156 db_str = self.config.get('trac', 'database') 157 db_name = os.path.join(self.env.path, db_str[7:]) 158 shutil.copy(db_name, dest_file) 159 if not os.path.exists(dest_file): 160 raise TracError("Backup attempt failed") 149 161 150 162 class SQLiteConnection(ConnectionWrapper): 151 163 """Connection wrapper for SQLite.""" -
trac/env.py
diff --git a/trac/env.py b/trac/env.py index a579397..c0ec4c2 100644
a b class Environment(Component, ComponentManager): 414 414 @param dest: Destination file; if not specified, the backup is stored in 415 415 a file called db_name.trac_version.bak 416 416 """ 417 import shutil 418 419 db_str = self.config.get('trac', 'database') 420 if not db_str.startswith('sqlite:'): 421 raise TracError(_('Can only backup sqlite databases')) 422 db_name = os.path.join(self.path, db_str[7:]) 423 if not dest: 424 dest = '%s.%i.bak' % (db_name, self.get_version()) 425 shutil.copy (db_name, dest) 417 DatabaseManager(self).backup(dest) 426 418 427 419 def needs_upgrade(self): 428 420 """Return whether the environment needs to be upgraded.""" -
trac/tests/functional/testenv.py
diff --git a/trac/tests/functional/testenv.py b/trac/tests/functional/testenv.py index eca0c34..f1921c7 100755
a b class FunctionalTestEnvironment(object): 59 59 if os.environ.has_key('TRAC_TEST_DB_URI'): 60 60 dburi = os.environ['TRAC_TEST_DB_URI'] 61 61 if dburi.startswith("postgres") and dburi.find("?schema=") < 0: 62 return "%s?schema=t ractest" % dburi62 return "%s?schema=testenv%s" % (dburi, self.port) 63 63 return dburi 64 64 return 'sqlite:db/trac.db' 65 65 dburi = property(get_dburi) 66 66 67 67 def destroy(self): 68 68 """Remove all of the test environment data.""" 69 if self.dburi.startswith("postgres"):69 if os.path.exists(self.dirname) and self.dburi.startswith("postgres"): 70 70 # if we're using a schema, destroy it now. initenv, called later, 71 71 # will fail otherwise, and this is more consistent with testing 72 72 # against sqlite
