Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7488ed8
Fix for issue #120
timvaillancourt May 4, 2017
362447c
Merge pull request #122 from timvaillancourt/username_flag_fix
timvaillancourt May 4, 2017
ed287e2
Update pip cache dir flag (--download-cache was removed)
timvaillancourt May 4, 2017
6b727c9
Merge pull request #123 from timvaillancourt/pip_cache_flag_fix
timvaillancourt May 4, 2017
520d159
Support readPreference on mongodump 3.4.0+, set to secondary to enfor…
timvaillancourt May 4, 2017
698c08a
Merge pull request #124 from timvaillancourt/mongodump_read_concern
timvaillancourt May 4, 2017
73706ae
Dockerfile to install 3.4 psmdb mongodump for 3.4.x support
timvaillancourt May 5, 2017
4668f16
Merge pull request #125 from timvaillancourt/docker_mongodump3.4
timvaillancourt May 5, 2017
4f27f0a
ignore bson unicode decode error
islue May 11, 2017
a11f06e
Fix secondary quorum errors on large sets
timvaillancourt May 11, 2017
01980de
Increment count by 1 for PRIMARY
timvaillancourt May 11, 2017
0ceab90
Merge pull request #128 from timvaillancourt/1.0.2_replset_scoring_fix
timvaillancourt May 11, 2017
198a4b9
Support both types of pip cache flags
timvaillancourt May 11, 2017
8352feb
Merge pull request #129 from timvaillancourt/pip_cache_dir_compat
timvaillancourt May 11, 2017
73a0e2b
Merge pull request #130 from islue/unicode_decode_error_handler
timvaillancourt May 11, 2017
561c865
adding blog to links
timvaillancourt May 11, 2017
f35703e
Merge pull request #131 from timvaillancourt/1.0.0_link_update
timvaillancourt May 11, 2017
b6501f7
Fix compression decision code for mongodump, fix build step caching w…
timvaillancourt May 11, 2017
08be509
Conf comment fixes
timvaillancourt May 11, 2017
ba04d92
Merge pull request #132 from timvaillancourt/mongodump_compression_fixv1
timvaillancourt May 11, 2017
ff6c63a
Raise ResolverThread.py exceptions to Resolver.py (and thus Main.py)
timvaillancourt May 11, 2017
d8cb0cd
Write password to mongodump stdin if >= 3.0.2
timvaillancourt May 11, 2017
1ccbc24
forgotten cleanup
timvaillancourt May 11, 2017
b919328
Merge pull request #133 from timvaillancourt/mongodump_no_cmdline_passwd
timvaillancourt May 11, 2017
803ec9d
Warn users if their password is on the command line
timvaillancourt May 11, 2017
dd8e65a
Merge pull request #134 from timvaillancourt/mongodump_no_cmdline_passwd
timvaillancourt May 11, 2017
0f6aa82
'stopped' var should be set to False until stop is ran
timvaillancourt May 12, 2017
7c7da82
Merge pull request #135 from timvaillancourt/issue_121_test1
timvaillancourt May 12, 2017
075a0b3
Support 3.4.x CSRS-based balancer
timvaillancourt May 15, 2017
c60178f
Merge pull request #137 from timvaillancourt/34_balancer_lock_fix
timvaillancourt May 15, 2017
cc0731e
Ensure we use a mongos connection for 3.4 balancer commands. Even tho…
timvaillancourt May 15, 2017
412aadd
Merge pull request #138 from timvaillancourt/34_balancer_lock_fix_v2
timvaillancourt May 15, 2017
fcc8bb7
Make log dir if not exists
timvaillancourt May 15, 2017
a4373d1
Merge pull request #139 from timvaillancourt/log_dir_fixv1
timvaillancourt May 15, 2017
2abc8bb
Write log if cannot get lock
timvaillancourt May 15, 2017
7a01f79
Merge pull request #140 from timvaillancourt/log_when_locked
timvaillancourt May 15, 2017
76ac5b6
version increment to 1.0.2
timvaillancourt May 15, 2017
a80d833
Merge pull request #142 from timvaillancourt/1.0.2_version
timvaillancourt May 15, 2017
5d05a9a
Handle TimeoutError from .get() on Popen results
timvaillancourt May 16, 2017
0b6bbaa
Make poll/sleep secs a var
timvaillancourt May 16, 2017
10f4d01
Merge pull request #144 from timvaillancourt/resolverthread_wait_timeout
timvaillancourt May 16, 2017
455e621
Add backwards-compatable --user flag
timvaillancourt May 16, 2017
9e27e4c
Warn if the log dir was created
timvaillancourt May 16, 2017
afa63fe
Merge pull request #148 from timvaillancourt/review_fixes_1.0.2
timvaillancourt May 16, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FROM centos:centos7
MAINTAINER Tim Vaillancourt <tim.vaillancourt@percona.com>
RUN yum install -y https://www.percona.com/redir/downloads/percona-release/redhat/latest/percona-release-0.1-4.noarch.rpm epel-release && \
yum install -y Percona-Server-MongoDB-32-tools zbackup && yum clean all && \
curl -Lo /usr/bin/mongodb-consistent-backup https://github.com/Percona-Lab/mongodb_consistent_backup/releases/download/1.0.1/mongodb-consistent-backup.el7.centos.x86_64 && \
yum install -y Percona-Server-MongoDB-34-tools zbackup && yum clean all && \
curl -Lo /usr/bin/mongodb-consistent-backup https://github.com/Percona-Lab/mongodb_consistent_backup/releases/download/1.0.2/mongodb-consistent-backup.el7.centos.x86_64 && \
chmod +x /usr/bin/mongodb-consistent-backup
ENTRYPOINT ["mongodb-consistent-backup"]
CMD ["--help"]
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ Links

- https://www.percona.com/blog/2016/07/25/mongodb-consistent-backups/
- https://www.percona.com/blog/2017/01/09/mongodb-pit-backups-part-2/
- https://www.percona.com/blog/2017/05/10/percona-lab-mongodb_consistent_backup-1-0-release-explained/
- https://docs.mongodb.com/manual/reference/program/mongodump/
- https://docs.mongodb.com/manual/reference/program/mongorestore/
- http://zbackup.org
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.1
1.0.2
10 changes: 5 additions & 5 deletions conf/mongodb-consistent-backup.example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ production:
name: default
location: /var/lib/mongodb-consistent-backup
# mongodump:
# binary: [path] (default: /usr/bin/mongodump)
# compression: [none|gzip] (default: true - if supported)
# threads: [1-16] (default: auto-generated - shards/cpu)
# binary: [path] (default: /usr/bin/mongodump)
# compression: [auto|none|gzip] (default: auto - enable gzip if supported)
# threads: [1-16] (default: auto-generated - shards/cpu)
#replication:
# max_lag_secs: [1+] (default: 5)
# min_priority: [0-999] (default: 0)
Expand All @@ -23,14 +23,14 @@ production:
# wait_secs: [1+] (default: 300)
# ping_secs: [1+] (default: 3)
#oplog:
# compression: [none|gzip] (default: true - if used by backup stage)
# compression: [none|gzip] (default: gzip - if gzip is used by backup stage)
# resolver_threads: [1+] (default: 2 per CPU)
# tailer:
# status_interval: 30
archive:
method: tar
# tar:
# compression: [none|gzip] (default: gzip, none if backup is compressed)
# compression: [none|gzip] (default: gzip - none if backup is already compressed)
# threads: [1+] (default: 1 per CPU)
# zbackup:
# binary: [path] (default: /usr/bin/zbackup)
Expand Down
16 changes: 11 additions & 5 deletions mongodb_consistent_backup/Backup/Mongodump/Mongodump.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self, manager, config, timer, base_dir, backup_dir, replsets, shard
self.replsets = replsets
self.sharding = sharding

self.compression_supported = ['none', 'gzip']
self.compression_supported = ['auto', 'none', 'gzip']
self.version = 'unknown'
self.threads_max = 16
self.config_replset = False
Expand All @@ -40,11 +40,15 @@ def __init__(self, manager, config, timer, base_dir, backup_dir, replsets, shard
with hide('running', 'warnings'), settings(warn_only=True):
self.version = local("%s --version|awk 'NR >1 {exit}; /version/{print $NF}'" % self.binary, capture=True)

self.choose_compression()

def choose_compression(self):
if self.can_gzip():
if self.compression() == 'none':
if self.compression() == 'auto':
logging.info("Mongodump binary supports gzip compression, auto-enabling gzip compression")
self.compression('gzip')
elif self.compression() == 'gzip':
logging.warning("mongodump gzip compression requested on binary that does not support gzip!")
raise OperationError("mongodump gzip compression requested on binary that does not support gzip!")

def can_gzip(self):
if os.path.isfile(self.binary) and os.access(self.binary, os.X_OK):
Expand Down Expand Up @@ -121,8 +125,9 @@ def run(self):
self.authdb,
self.backup_dir,
self.binary,
self.version,
self.threads(),
self.do_gzip,
self.do_gzip(),
self.verbose
)
self.dump_threads.append(thread)
Expand Down Expand Up @@ -152,8 +157,9 @@ def run(self):
self.authdb,
self.backup_dir,
self.binary,
self.version,
self.threads(),
self.do_gzip,
self.do_gzip(),
self.verbose
)]
self.dump_threads[0].start()
Expand Down
66 changes: 47 additions & 19 deletions mongodb_consistent_backup/Backup/Mongodump/MongodumpThread.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,28 @@

# noinspection PyStringFormat
class MongodumpThread(Process):
def __init__(self, state, uri, timer, user, password, authdb, base_dir, binary,
def __init__(self, state, uri, timer, user, password, authdb, base_dir, binary, version,
threads=0, dump_gzip=False, verbose=False):
Process.__init__(self)
self.state = state
self.uri = uri
self.timer = timer
self.user = user
self.password = password
self.authdb = authdb
self.base_dir = base_dir
self.binary = binary
self.threads = threads
self.dump_gzip = dump_gzip
self.verbose = verbose

self.timer_name = "%s-%s" % (self.__class__.__name__, self.uri.replset)
self.exit_code = 1
self._command = None
self.state = state
self.uri = uri
self.timer = timer
self.user = user
self.password = password
self.authdb = authdb
self.base_dir = base_dir
self.binary = binary
self.version = version
self.threads = threads
self.dump_gzip = dump_gzip
self.verbose = verbose

self.timer_name = "%s-%s" % (self.__class__.__name__, self.uri.replset)
self.exit_code = 1
self._command = None
self.do_stdin_passwd = False
self.stdin_passwd_sent = False

self.backup_dir = os.path.join(self.base_dir, self.uri.replset)
self.dump_dir = os.path.join(self.backup_dir, "dump")
self.oplog_file = os.path.join(self.dump_dir, "oplog.bson")
Expand Down Expand Up @@ -60,6 +64,18 @@ def parse_mongodump_line(self, line):
except:
return None

def is_password_prompt(self, line):
if self.do_stdin_passwd and ("Enter Password:" in line or "reading password from standard input" in line):
return True
return False

def handle_password_prompt(self):
if self.do_stdin_passwd and not self.stdin_passwd_sent:
logging.debug("Received password prompt from mongodump, writing password to stdin")
self._process.stdin.write(self.password + "\n")
self._process.stdin.flush()
self.stdin_passwd_sent = True

def wait(self):
try:
while self._process.stderr:
Expand All @@ -68,7 +84,11 @@ def wait(self):
for fd in poll[0]:
read = self._process.stderr.readline()
line = self.parse_mongodump_line(read)
if line:
if not line:
continue
elif self.is_password_prompt(read):
self.handle_password_prompt()
else:
logging.info(line)
if self._process.poll() != None:
break
Expand All @@ -85,11 +105,19 @@ def mongodump_cmd(self):
mongodump_flags.extend(["--numParallelCollections="+str(self.threads)])
if self.dump_gzip:
mongodump_flags.extend(["--gzip"])
if tuple("3.4.0".split(".")) <= tuple(self.version.split(".")):
mongodump_flags.extend(["--readPreference=secondary"])
if self.authdb and self.authdb != "admin":
logging.debug("Using database %s for authentication" % self.authdb)
mongodump_flags.extend(["--authenticationDatabase", self.authdb])
if self.user and self.password:
mongodump_flags.extend(["-u", self.user, "-p", self.password])
# >= 3.0.2 supports password input via stdin to mask from ps
if tuple("3.0.2".split(".")) <= tuple(self.version.split(".")):
mongodump_flags.extend(["-u", self.user, "-p", '""'])
self.do_stdin_passwd = True
else:
logging.warning("Mongodump is too old to set password securely! Upgrade to mongodump >= 3.2.0 to resolve this")
mongodump_flags.extend(["-u", self.user, "-p", self.password])
mongodump_cmd.extend(mongodump_flags)
return mongodump_cmd

Expand All @@ -106,7 +134,7 @@ def run(self):
rmtree(self.dump_dir)
os.makedirs(self.dump_dir)
logging.debug("Running mongodump cmd: %s" % mongodump_cmd)
self._process = Popen(mongodump_cmd, stderr=PIPE)
self._process = Popen(mongodump_cmd, stdin=PIPE, stderr=PIPE)
self.wait()
self.exit_code = self._process.returncode
if self.exit_code > 0:
Expand Down
4 changes: 2 additions & 2 deletions mongodb_consistent_backup/Backup/Mongodump/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ def config(parser):
parser.add_argument("--backup.mongodump.binary", dest="backup.mongodump.binary",
help="Path to 'mongodump' binary (default: /usr/bin/mongodump)", default='/usr/bin/mongodump')
parser.add_argument("--backup.mongodump.compression", dest="backup.mongodump.compression",
help="Compression method to use on backup (default: gzip)", default="gzip",
choices=["none", "gzip"])
help="Compression method to use on backup (default: auto)", default="auto",
choices=["auto", "none", "gzip"])
parser.add_argument("--backup.mongodump.threads", dest="backup.mongodump.threads",
help="Number of threads to use for each mongodump process. There is 1 x mongodump per shard, be careful! (default: shards/CPUs)",
default=0, type=int)
Expand Down
5 changes: 5 additions & 0 deletions mongodb_consistent_backup/Backup/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from Backup import Backup


def config(parser):
parser.add_argument("-n", "--backup.name", dest="backup.name", help="Name of the backup set (required)", type=str)
parser.add_argument("-l", "--backup.location", dest="backup.location", help="Base path to store the backup data (required)", type=str)
parser.add_argument("-m", "--backup.method", dest="backup.method", help="Method to be used for backup (default: mongodump)", default='mongodump', choices=['mongodump'])
return parser
5 changes: 1 addition & 4 deletions mongodb_consistent_backup/Common/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,9 @@ def makeParser(self):
parser.add_argument("-v", "--verbose", dest="verbose", help="Verbose output", default=False, action="store_true")
parser.add_argument("-H", "--host", dest="host", help="MongoDB Hostname, IP address or '<replset>/<host:port>,<host:port>,..' URI (default: localhost)", default="localhost", type=str)
parser.add_argument("-P", "--port", dest="port", help="MongoDB Port (default: 27017)", default=27017, type=int)
parser.add_argument("-u", "--user", dest="user", help="MongoDB Authentication Username (for optional auth)", type=str)
parser.add_argument("-u", "--user", "--username", dest="username", help="MongoDB Authentication Username (for optional auth)", type=str)
parser.add_argument("-p", "--password", dest="password", help="MongoDB Authentication Password (for optional auth)", type=str)
parser.add_argument("-a", "--authdb", dest="authdb", help="MongoDB Auth Database (for optional auth - default: admin)", default='admin', type=str)
parser.add_argument("-n", "--backup.name", dest="backup.name", help="Name of the backup set (required)", type=str)
parser.add_argument("-l", "--backup.location", dest="backup.location", help="Base path to store the backup data (required)", type=str)
parser.add_argument("-m", "--backup.method", dest="backup.method", help="Method to be used for backup (default: mongodump)", default='mongodump', choices=['mongodump'])
parser.add_argument("-L", "--log-dir", dest="log_dir", help="Path to write log files to (default: disabled)", default='', type=str)
parser.add_argument("--lock-file", dest="lock_file", help="Location of lock file (default: /tmp/mongodb-consistent-backup.lock)", default='/tmp/mongodb-consistent-backup.lock', type=str)
parser.add_argument("--sharding.balancer.wait_secs", dest="sharding.balancer.wait_secs", help="Maximum time to wait for balancer to stop, in seconds (default: 300)", default=300, type=int)
Expand Down
2 changes: 1 addition & 1 deletion mongodb_consistent_backup/Common/DB.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def connect(self):
conn['admin'].command({"ping":1})
except (ConnectionFailure, OperationFailure, ServerSelectionTimeoutError), e:
logging.error("Unable to connect to %s! Error: %s" % (self.uri, e))
raise OperationError(e)
raise DBConnectionError(e)
if conn is not None:
self._conn = conn
return self._conn
Expand Down
26 changes: 14 additions & 12 deletions mongodb_consistent_backup/Logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ def __init__(self, config, backup_time):

self.do_file_log = False
if self.config.log_dir is not '':
if os.path.isdir(self.config.log_dir):
self.do_file_log = True
else:
print("ERROR: Log directory: %s does not exist! Skipping file-based logging" % self.config.log_dir)
self.do_file_log = True
if not os.path.isdir(self.config.log_dir):
print "WARNING: Creating logging directory: %s" % self.config.log_dir
os.mkdir(self.config.log_dir)

self.log_format = '[%(asctime)s] [%(levelname)s] [%(processName)s] [%(module)s:%(funcName)s:%(lineno)d] %(message)s'
self.file_log = None
Expand All @@ -41,7 +41,6 @@ def start_file_logger(self):
self.file_log.setLevel(self.log_level)
self.file_log.setFormatter(logging.Formatter(self.log_format))
logging.getLogger('').addHandler(self.file_log)
self.update_symlink()
except OSError, e:
logging.warning("Could not start file log handler, writing to stdout only")
pass
Expand All @@ -50,18 +49,21 @@ def close(self):
if self.file_log:
self.file_log.close()

def compress(self):
def compress(self, current=False):
gz_log = None
try:
if not os.path.isfile(self.last_log) or self.last_log == self.backup_log_file:
return
logging.info("Compressing previous log file")
gz_file = "%s.gz" % self.last_log
compress_file = self.backup_log_file
if not current:
compress_file = self.last_log
if not os.path.isfile(self.last_log) or self.last_log == self.backup_log_file:
return
logging.info("Compressing log file: %s" % compress_file)
gz_file = "%s.gz" % compress_file
gz_log = GzipFile(gz_file, "w+")
with open(self.last_log) as f:
with open(compress_file) as f:
for line in f:
gz_log.write(line)
os.remove(self.last_log)
os.remove(compress_file)
finally:
if gz_log:
gz_log.close()
Expand Down
4 changes: 3 additions & 1 deletion mongodb_consistent_backup/Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __init__(self, prog_name="mongodb-consistent-backup"):
self.setup_logger()
self.setup_signal_handlers()
self.get_lock()
self.logger.start_file_logger()
self.logger.update_symlink()
self.init()
self.set_backup_dirs()
self.get_db_conn()
Expand All @@ -74,6 +74,7 @@ def setup_logger(self):
try:
self.logger = Logger(self.config, self.backup_time)
self.logger.start()
self.logger.start_file_logger()
except Exception, e:
self.exception("Could not start logger: %s" % e, e)

Expand Down Expand Up @@ -117,6 +118,7 @@ def get_lock(self):
self.lock = Lock(self.config.lock_file)
except Exception:
logging.fatal("Could not acquire lock: '%s'! Is another %s process running? Exiting" % (self.config.lock_file, self.program_name))
self.logger.compress(True)
sys.exit(1)

def release_lock(self):
Expand Down
3 changes: 2 additions & 1 deletion mongodb_consistent_backup/Oplog/Oplog.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from gzip import GzipFile
from bson import BSON, decode_file_iter
from bson.codec_options import CodecOptions

from mongodb_consistent_backup.Errors import OperationError

Expand Down Expand Up @@ -44,7 +45,7 @@ def load(self):
try:
oplog = self.open()
logging.debug("Reading oplog file %s" % self.oplog_file)
for change in decode_file_iter(oplog):
for change in decode_file_iter(oplog, CodecOptions(unicode_decode_error_handler="ignore")):
if 'ts' in change:
self._last_ts = change['ts']
if self._first_ts is None and self._last_ts is not None:
Expand Down
Loading