Skip to content

Commit fe86246

Browse files
Support read_preference tags with Common/DB.py
1 parent 4a0752f commit fe86246

File tree

6 files changed

+120
-35
lines changed

6 files changed

+120
-35
lines changed

conf/mongodb-consistent-backup.example.conf

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ production:
44
#username: [auth username] (default: none)
55
#password: [auth password] (default: none)
66
#authdb: [auth database] (default: admin)
7+
#ssl:
8+
# enabled: [true|false] (default: false)
9+
# insecure: [true|false] (default: false)
10+
# ca_file: [path]
11+
# crl_file: [path]
12+
# client_cert_file: [path]
713
log_dir: /var/log/mongodb-consistent-backup
814
backup:
915
method: mongodump
@@ -14,10 +20,11 @@ production:
1420
# compression: [auto|none|gzip] (default: auto - enable gzip if supported)
1521
# threads: [1-16] (default: auto-generated - shards/cpu)
1622
#replication:
17-
# max_lag_secs: [1+] (default: 10)
18-
# min_priority: [0-999] (default: 0)
19-
# max_priority: [2-1000] (default: 1000)
20-
# hidden_only: [true|false] (default: false)
23+
# max_lag_secs: [1+] (default: 10)
24+
# min_priority: [0-999] (default: 0)
25+
# max_priority: [2-1000] (default: 1000)
26+
# hidden_only: [true|false] (default: false)
27+
# read_pref_tags: [key:value,key:value,...] (default: none)
2128
#sharding:
2229
# balancer:
2330
# wait_secs: [1+] (default: 300)

mongodb_consistent_backup/Backup/Mongodump/MongodumpThread.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from signal import signal, SIGINT, SIGTERM, SIG_IGN
99
from subprocess import Popen, PIPE
1010

11-
from mongodb_consistent_backup.Common import is_datetime
11+
from mongodb_consistent_backup.Common import is_datetime, parse_config_bool
1212
from mongodb_consistent_backup.Oplog import Oplog
1313

1414

@@ -25,10 +25,13 @@ def __init__(self, state, uri, timer, config, base_dir, version, threads=0, dump
2525
self.threads = threads
2626
self.dump_gzip = dump_gzip
2727

28-
self.user = self.config.username
29-
self.password = self.config.password
30-
self.authdb = self.config.authdb
31-
self.binary = self.config.backup.mongodump.binary
28+
self.user = self.config.username
29+
self.password = self.config.password
30+
self.authdb = self.config.authdb
31+
self.ssl_ca_file = self.config.ssl.ca_file
32+
self.ssl_crl_file = self.config.ssl.crl_file
33+
self.ssl_client_cert_file = self.config.ssl.client_cert_file
34+
self.binary = self.config.backup.mongodump.binary
3235

3336
self.timer_name = "%s-%s" % (self.__class__.__name__, self.uri.replset)
3437
self.exit_code = 1
@@ -52,6 +55,12 @@ def close(self, exit_code=None, frame=None):
5255
self._command.close()
5356
sys.exit(self.exit_code)
5457

58+
def do_ssl(self):
59+
return parse_config_bool(self.config.ssl.enabled)
60+
61+
def do_ssl_insecure(self):
62+
return parse_config_bool(self.config.ssl.insecure)
63+
5564
def parse_mongodump_line(self, line):
5665
try:
5766
line = line.rstrip()
@@ -133,6 +142,16 @@ def mongodump_cmd(self):
133142
else:
134143
logging.warning("Mongodump is too old to set password securely! Upgrade to mongodump >= 3.0.2 to resolve this")
135144
mongodump_flags.extend(["-u", self.user, "-p", self.password])
145+
if self.do_ssl():
146+
mongodump_flags.append("--ssl")
147+
if self.ssl_ca_file:
148+
mongodump_flags.extend(["--sslCAFile", self.ssl_ca_file])
149+
if self.ssl_crl_file:
150+
mongodump_flags.extend(["--sslCRLFile", self.ssl_crl_file])
151+
if self.client_cert_file:
152+
mongodump_flags.extend(["--sslPEMKeyFile", self.ssl_cert_file])
153+
if self.do_ssl_insecure():
154+
mongodump_flags.extend(["--sslAllowInvalidCertificates", "--sslAllowInvalidHostnames"])
136155
mongodump_cmd.extend(mongodump_flags)
137156
return mongodump_cmd
138157

mongodb_consistent_backup/Common/Config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88
from yconf.util import NestedDict
99

1010

11+
def parse_config_bool(item):
12+
try:
13+
if isinstance(item, bool):
14+
return item
15+
elif isinstance(item, str):
16+
if item.rstrip().lower() is "true":
17+
return True
18+
return False
19+
except:
20+
return False
21+
22+
1123
class PrintVersions(Action):
1224
def __init__(self, option_strings, dest, nargs=0, **kwargs):
1325
super(PrintVersions, self).__init__(option_strings=option_strings, dest=dest, nargs=nargs, **kwargs)
@@ -54,6 +66,11 @@ def makeParser(self):
5466
parser.add_argument("-u", "--user", "--username", dest="username", help="MongoDB Authentication Username (for optional auth)", type=str)
5567
parser.add_argument("-p", "--password", dest="password", help="MongoDB Authentication Password (for optional auth)", type=str)
5668
parser.add_argument("-a", "--authdb", dest="authdb", help="MongoDB Auth Database (for optional auth - default: admin)", default='admin', type=str)
69+
parser.add_argument("--ssl.enabled", dest="ssl.enabled", help="Use SSL secured database connections to MongoDB hosts (default: false)", default=False, action="store_true")
70+
parser.add_argument("--ssl.insecure", dest="ssl.insecure", help="Do not validate the SSL certificate and hostname of the server (default: false)", default=False, action="store_true")
71+
parser.add_argument("--ssl.ca_file", dest="ssl.ca_file", help="Path to SSL Certificate Authority file in PEM format (default: use OS default CA)", default=None, type=str)
72+
parser.add_argument("--ssl.crl_file", dest="ssl.crl_file", help="Path to SSL Certificate Revocation List file in PEM or DER format (for optional cert revocation)", default=None, type=str)
73+
parser.add_argument("--ssl.client_cert_file", dest="ssl.client_cert_file", help="Path to Client SSL Certificate file in PEM format (for optional client ssl auth)", default=None, type=str)
5774
parser.add_argument("-L", "--log-dir", dest="log_dir", help="Path to write log files to (default: disabled)", default='', type=str)
5875
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)
5976
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)

mongodb_consistent_backup/Common/DB.py

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,88 @@
44
from inspect import currentframe, getframeinfo
55
from pymongo import DESCENDING, CursorType, MongoClient
66
from pymongo.errors import ConnectionFailure, OperationFailure, ServerSelectionTimeoutError
7+
from ssl import CERT_REQUIRED, CERT_NONE
78
from time import sleep
89

10+
from mongodb_consistent_backup.Common import parse_config_bool
911
from mongodb_consistent_backup.Errors import DBAuthenticationError, DBConnectionError, DBOperationError, Error
1012

1113

1214
class DB:
1315
def __init__(self, uri, config, do_replset=False, read_pref='primaryPreferred', do_connect=True, conn_timeout=5000, retries=5):
14-
self.uri = uri
15-
self.username = config.username
16-
self.password = config.password
17-
self.authdb = config.authdb
18-
self.do_replset = do_replset
19-
self.read_pref = read_pref
20-
self.do_connect = do_connect
21-
self.conn_timeout = conn_timeout
22-
self.retries = retries
16+
self.uri = uri
17+
self.config = config
18+
self.do_replset = do_replset
19+
self.read_pref = read_pref
20+
self.do_connect = do_connect
21+
self.conn_timeout = conn_timeout
22+
self.retries = retries
23+
24+
self.username = self.config.username
25+
self.password = self.config.password
26+
self.authdb = self.config.authdb
27+
self.ssl_ca_file = self.config.ssl.ca_file
28+
self.ssl_crl_file = self.config.ssl.crl_file
29+
self.ssl_client_cert_file = self.config.ssl.client_cert_file
30+
self.read_pref_tags = self.config.replication.read_pref_tags.replace(" ", "")
2331

2432
self.replset = None
2533
self._conn = None
2634
self._is_master = None
35+
2736
self.connect()
2837
self.auth_if_required()
2938

39+
def do_ssl(self):
40+
return parse_config_bool(self.config.ssl.enabled)
41+
42+
def do_ssl_insecure(self):
43+
return parse_config_bool(self.config.ssl.insecure)
44+
45+
def client_opts(self):
46+
opts = {
47+
"connect": self.do_connect,
48+
"host": self.uri.hosts(),
49+
"connectTimeoutMS": self.conn_timeout,
50+
"serverSelectionTimeoutMS": self.conn_timeout,
51+
"maxPoolSize": 1,
52+
}
53+
if self.do_replset:
54+
self.replset = self.uri.replset
55+
opts.update({
56+
"replicaSet": self.replset,
57+
"readPreference": self.read_pref,
58+
"readPreferenceTags": self.read_pref_tags,
59+
"w": "majority"
60+
})
61+
if self.do_ssl():
62+
logging.debug("Using SSL-secured mongodb connection (ca_cert=%s, client_cert=%s, crl_file=%s, insecure=%s)" % (
63+
self.ssl_ca_file,
64+
self.ssl_client_cert_file,
65+
self.ssl_crl_file,
66+
self.do_ssl_insecure()
67+
))
68+
opts.update({
69+
"ssl": True,
70+
"ssl_ca_certs": self.ssl_ca_file,
71+
"ssl_crlfile": self.ssl_crl_file,
72+
"ssl_certfile": self.ssl_client_cert_file,
73+
"ssl_cert_reqs": CERT_REQUIRED,
74+
})
75+
if self.do_ssl_insecure():
76+
opts["ssl_cert_reqs"] = CERT_NONE
77+
return opts
78+
3079
def connect(self):
3180
try:
32-
if self.do_replset:
33-
self.replset = self.uri.replset
34-
logging.debug("Getting MongoDB connection to %s (replicaSet=%s, readPreference=%s)" % (
35-
self.uri, self.replset, self.read_pref
81+
logging.debug("Getting MongoDB connection to %s (replicaSet=%s, readPreference=%s, readPreferenceTags=%s, ssl=%s)" % (
82+
self.uri,
83+
self.replset,
84+
self.read_pref,
85+
self.read_pref_tags,
86+
self.do_ssl(),
3687
))
37-
conn = MongoClient(
38-
connect=self.do_connect,
39-
host=self.uri.hosts(),
40-
replicaSet=self.replset,
41-
readPreference=self.read_pref,
42-
connectTimeoutMS=self.conn_timeout,
43-
serverSelectionTimeoutMS=self.conn_timeout,
44-
maxPoolSize=1,
45-
w="majority"
46-
)
88+
conn = MongoClient(**self.client_opts())
4789
if self.do_connect:
4890
conn['admin'].command({"ping": 1})
4991
except (ConnectionFailure, OperationFailure, ServerSelectionTimeoutError), e:

mongodb_consistent_backup/Common/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from Config import Config # NOQA
1+
from Config import Config, parse_config_bool # NOQA
22
from DB import DB # NOQA
33
from LocalCommand import LocalCommand # NOQA
44
from Lock import Lock # NOQA

mongodb_consistent_backup/Replication/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ def config(parser):
77
parser.add_argument("--replication.min_priority", dest="replication.min_priority", help="Min priority of secondary members for backup (default: 0)", default=0, type=int)
88
parser.add_argument("--replication.max_priority", dest="replication.max_priority", help="Max priority of secondary members for backup (default: 1000)", default=1000, type=int)
99
parser.add_argument("--replication.hidden_only", dest="replication.hidden_only", help="Only use hidden secondary members for backup (default: false)", default=False, action="store_true")
10-
# todo: add tag-specific backup option
11-
# parser.add_argument("-replication.use_tag", dest="replication.use_tag", help="Only use secondary members with tag for backup", type=str)
10+
parser.add_argument("--replication.read_pref_tags", dest="replication.read_pref_tags", default=None, type=str,
11+
help="Only use members that match replication tags in comma-separated key:value format (default: none)")
1212
return parser

0 commit comments

Comments
 (0)