#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2009 Maximillian Dornseif # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. """ This script replicates databases from one CouchDB server to an other. This is mainly for backup purposes or "priming" a new server before setting up trigger based replication. But you can also use the '--continuous' option to set up automatic replication on newer CouchDB versions. Use 'python manual_replication.py --help' to get more detailed usage instructions. Be careful when using 127.0.0.1 as the source-server or target-server. With pull replication you can use 127.0.0.1 on the target-server. With push replication you can use 127.0.0.1 on the source-server. But I suggest you always use Fully Qualified domain names. """ import couchdb.client import optparse import sys import time import httplib2 def compact(server, dbnames): for dbname in dbnames: sys.stdout.flush() db = server[dbname] db.resource.post('_compact') def main(): usage = '%prog [options]' parser = optparse.OptionParser(usage=usage) parser.add_option('--source-server', action='store', dest='source_url', help='the url of the server to replicate from') parser.add_option('--target-server', action='store', dest='target_url', default="http://127.0.0.1:5984", help='the url of the server to replicate to [%default]') parser.add_option('--database', action='append', dest='dbnames', help='Database to replicate. Can be given more than once. [all databases]') parser.add_option('--no-target-compaction', action='store_false', dest='compact_target', help='do not start compaction of target after replications') parser.add_option('--continuous', action='store_true', dest='continuous', help='trigger continuous replication in cochdb') parser.add_option('--push', action='store_true', help='use push instead of pull replication') parser.add_option('--debug', action='store_true', dest='debug') options, args = parser.parse_args() if not options.target_url or (not options.source_url): parser.error("Need at least --source-server and --target-server") sys.exit(1) if options.debug: httplib2.debuglevel = 1 if not options.source_url.endswith('/'): options.source_url = options.source_url + '/' if not options.target_url.endswith('/'): options.target_url = options.target_url + '/' source_server = couchdb.client.Server(options.source_url) target_server = couchdb.client.Server(options.target_url) if not options.dbnames: dbnames = source_server.resource.get('_all_dbs')[1] dbnames.sort() else: dbnames = options.dbnames for dbname in sorted(dbnames, reverse=True): start = time.time() print dbname, sys.stdout.flush() if dbname not in target_server.resource.get('_all_dbs')[1]: target_server.create(dbname) print "created", sys.stdout.flush() body = {} if options.continuous: body['continuous'] = True if options.push: body.update({'source': dbname, 'target': '%s%s' % (options.target_url, dbname)}) source_server.resource.post('_replicate', body) else: # pull seems to be more reliable than push body.update({'source': '%s%s' % (options.source_url, dbname), 'target': dbname}) target_server.resource.post('_replicate', body) print "%.1f s" % (time.time() - start) if options.compact_target: compact(target_server, dbnames) if __name__ == '__main__': main()