17
17
# You should have received a copy of the GNU General Public License
18
18
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
19
19
20
+ from __future__ import (absolute_import , division , print_function )
21
+ __metaclass__ = type
22
+
20
23
DOCUMENTATION = '''
21
24
---
22
25
module: mongodb_replication
156
159
type: string
157
160
sample: "replica"
158
161
'''
159
- import ConfigParser
162
+
163
+ import os
160
164
import ssl as ssl_lib
161
165
import time
166
+ import traceback
162
167
from datetime import datetime as dtdatetime
163
168
from distutils .version import LooseVersion
169
+
164
170
try :
165
- from pymongo .errors import ConnectionFailure
166
- from pymongo .errors import OperationFailure
167
- from pymongo .errors import ConfigurationError
168
- from pymongo .errors import AutoReconnect
169
- from pymongo .errors import ServerSelectionTimeoutError
171
+ from pymongo .errors import ConnectionFailure , OperationFailure , AutoReconnect , ServerSelectionTimeoutError
170
172
from pymongo import version as PyMongoVersion
171
173
from pymongo import MongoClient
172
174
except ImportError :
173
- pymongo_found = False
175
+ try : # for older PyMongo 2.2
176
+ from pymongo import Connection as MongoClient
177
+ except ImportError :
178
+ pymongo_found = False
179
+ else :
180
+ pymongo_found = True
174
181
else :
175
182
pymongo_found = True
176
183
184
+ from ansible .module_utils .basic import AnsibleModule , missing_required_lib
185
+ from ansible .module_utils .six .moves import configparser
186
+ from ansible .module_utils ._text import to_native
187
+
177
188
# =========================================
178
189
# MongoDB module specific support methods.
179
190
#
191
+
192
+
180
193
def check_compatibility (module , client ):
181
- srv_info = client .server_info ()
182
- if LooseVersion (PyMongoVersion ) <= LooseVersion ('3.2' ):
183
- module .fail_json (msg = 'Note: you must use pymongo 3.2+' )
184
- if LooseVersion (srv_info ['version' ]) >= LooseVersion ('3.4' ) and LooseVersion (PyMongoVersion ) <= LooseVersion ('3.4' ):
185
- module .fail_json (msg = 'Note: you must use pymongo 3.4+ with MongoDB 3.4.x' )
186
- if LooseVersion (srv_info ['version' ]) >= LooseVersion ('3.6' ) and LooseVersion (PyMongoVersion ) <= LooseVersion ('3.6' ):
187
- module .fail_json (msg = 'Note: you must use pymongo 3.6+ with MongoDB 3.6.x' )
194
+ """Check the compatibility between the driver and the database.
195
+ See: https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#python-driver-compatibility
196
+ Args:
197
+ module: Ansible module.
198
+ client (cursor): Mongodb cursor on admin database.
199
+ """
200
+ loose_srv_version = LooseVersion (client .server_info ()['version' ])
201
+ loose_driver_version = LooseVersion (PyMongoVersion )
202
+
203
+ if loose_srv_version >= LooseVersion ('4.0' ) and loose_driver_version < LooseVersion ('3.7' ):
204
+ module .fail_json (msg = ' (Note: you must use pymongo 3.7+ with MongoDB >= 4.0)' )
205
+
206
+ elif loose_srv_version >= LooseVersion ('3.6' ) and loose_driver_version < LooseVersion ('3.6' ):
207
+ module .fail_json (msg = ' (Note: you must use pymongo 3.6+ with MongoDB >= 3.6)' )
208
+
209
+ elif loose_srv_version >= LooseVersion ('3.2' ) and loose_driver_version < LooseVersion ('3.2' ):
210
+ module .fail_json (msg = ' (Note: you must use pymongo 3.2+ with MongoDB >= 3.2)' )
211
+
212
+ elif loose_srv_version >= LooseVersion ('3.0' ) and loose_driver_version <= LooseVersion ('2.8' ):
213
+ module .fail_json (msg = ' (Note: you must use pymongo 2.8+ with MongoDB 3.0)' )
214
+
215
+ elif loose_srv_version >= LooseVersion ('2.6' ) and loose_driver_version <= LooseVersion ('2.7' ):
216
+ module .fail_json (msg = ' (Note: you must use pymongo 2.7+ with MongoDB 2.6)' )
217
+
218
+ elif LooseVersion (PyMongoVersion ) <= LooseVersion ('2.5' ):
219
+ module .fail_json (msg = ' (Note: you must be on mongodb 2.4+ and pymongo 2.5+ to use the roles param)' )
188
220
189
221
190
222
def check_members (state , module , client , host_name , host_port , host_type ):
191
- admin_db = client ['admin' ]
192
223
local_db = client ['local' ]
193
224
194
225
if local_db .system .replset .count () > 1 :
@@ -214,6 +245,7 @@ def check_members(state, module, client, host_name, host_port, host_type):
214
245
if "{0}:{1}" .format (host_name , host_port ) not in member ['host' ] and member ['arbiterOnly' ]:
215
246
module .exit_json (changed = False , host_name = host_name , host_port = host_port , host_type = host_type )
216
247
248
+
217
249
def add_host (module , client , host_name , host_port , host_type , timeout = 180 , ** kwargs ):
218
250
start_time = dtdatetime .now ()
219
251
while True :
@@ -229,8 +261,8 @@ def add_host(module, client, host_name, host_port, host_type, timeout=180, **kwa
229
261
module .fail_json (msg = 'no config object retrievable from local.system.replset' )
230
262
231
263
cfg ['version' ] += 1
232
- max_id = max (cfg ['members' ], key = lambda x :x ['_id' ])
233
- new_host = { '_id' : max_id ['_id' ] + 1 , 'host' : "{0}:{1}" .format (host_name , host_port ) }
264
+ max_id = max (cfg ['members' ], key = lambda x : x ['_id' ])
265
+ new_host = {'_id' : max_id ['_id' ] + 1 , 'host' : "{0}:{1}" .format (host_name , host_port )}
234
266
if host_type == 'arbiter' :
235
267
new_host ['arbiterOnly' ] = True
236
268
@@ -254,14 +286,14 @@ def add_host(module, client, host_name, host_port, host_type, timeout=180, **kwa
254
286
return
255
287
except (OperationFailure , AutoReconnect ) as e :
256
288
if (dtdatetime .now () - start_time ).seconds > timeout :
257
- module .fail_json (msg = 'reached timeout while waiting for rs.reconfig(): %s' % str ( e ))
289
+ module .fail_json (msg = 'reached timeout while waiting for rs.reconfig(): %s' % to_native ( e ), exception = traceback . format_exc ( ))
258
290
time .sleep (5 )
259
291
292
+
260
293
def remove_host (module , client , host_name , timeout = 180 ):
261
294
start_time = dtdatetime .now ()
262
295
while True :
263
296
try :
264
- admin_db = client ['admin' ]
265
297
local_db = client ['local' ]
266
298
267
299
if local_db .system .replset .count () > 1 :
@@ -283,30 +315,32 @@ def remove_host(module, client, host_name, timeout=180):
283
315
module .fail_json (msg = fail_msg )
284
316
except (OperationFailure , AutoReconnect ) as e :
285
317
if (dtdatetime .now () - start_time ).seconds > timeout :
286
- module .fail_json (msg = 'reached timeout while waiting for rs.reconfig(): %s' % str ( e ))
318
+ module .fail_json (msg = 'reached timeout while waiting for rs.reconfig(): %s' % to_native ( e ), exception = traceback . format_exc ( ))
287
319
time .sleep (5 )
288
320
321
+
289
322
def load_mongocnf ():
290
- config = ConfigParser .RawConfigParser ()
323
+ config = configparser .RawConfigParser ()
291
324
mongocnf = os .path .expanduser ('~/.mongodb.cnf' )
292
325
293
326
try :
294
327
config .readfp (open (mongocnf ))
295
328
creds = dict (
296
- user = config .get ('client' , 'user' ),
297
- password = config .get ('client' , 'pass' )
329
+ user = config .get ('client' , 'user' ),
330
+ password = config .get ('client' , 'pass' )
298
331
)
299
- except (ConfigParser .NoOptionError , IOError ):
332
+ except (configparser .NoOptionError , IOError ):
300
333
return False
301
334
302
335
return creds
303
336
304
- def wait_for_ok_and_master (module , connection_params , timeout = 180 ):
337
+
338
+ def wait_for_ok_and_master (module , connection_params , timeout = 180 ):
305
339
start_time = dtdatetime .now ()
306
340
while True :
307
341
try :
308
342
client = MongoClient (** connection_params )
309
- authenticate (client , connection_params ["username" ], connection_params ["password" ])
343
+ authenticate (module , client , connection_params ["username" ], connection_params ["password" ])
310
344
311
345
status = client .admin .command ('replSetGetStatus' , check = False )
312
346
if status ['ok' ] == 1 and status ['myState' ] == 1 :
@@ -322,7 +356,8 @@ def wait_for_ok_and_master(module, connection_params, timeout = 180):
322
356
323
357
time .sleep (1 )
324
358
325
- def authenticate (client , login_user , login_password ):
359
+
360
+ def authenticate (module , client , login_user , login_password ):
326
361
if login_user is None and login_password is None :
327
362
mongocnf_creds = load_mongocnf ()
328
363
if mongocnf_creds is not False :
@@ -338,9 +373,10 @@ def authenticate(client, login_user, login_password):
338
373
# Module execution.
339
374
#
340
375
376
+
341
377
def main ():
342
378
module = AnsibleModule (
343
- argument_spec = dict (
379
+ argument_spec = dict (
344
380
login_user = dict (default = None ),
345
381
login_password = dict (default = None , no_log = True ),
346
382
login_host = dict (default = 'localhost' ),
@@ -349,20 +385,20 @@ def main():
349
385
replica_set = dict (default = None ),
350
386
host_name = dict (default = 'localhost' ),
351
387
host_port = dict (default = '27017' ),
352
- host_type = dict (default = 'replica' , choices = ['replica' ,'arbiter' ]),
388
+ host_type = dict (default = 'replica' , choices = ['replica' , 'arbiter' ]),
353
389
ssl = dict (default = False , type = 'bool' ),
354
390
ssl_cert_reqs = dict (default = 'CERT_REQUIRED' , choices = ['CERT_NONE' , 'CERT_OPTIONAL' , 'CERT_REQUIRED' ]),
355
- build_indexes = dict (type = 'bool' , default = 'yes' ),
356
- hidden = dict (type = 'bool' , default = 'no' ),
357
- priority = dict (default = '1.0' ),
358
- slave_delay = dict (type = 'int' , default = '0' ),
359
- votes = dict (type = 'int' , default = '1' ),
391
+ build_indexes = dict (type = 'bool' , default = 'yes' ),
392
+ hidden = dict (type = 'bool' , default = 'no' ),
393
+ priority = dict (default = '1.0' ),
394
+ slave_delay = dict (type = 'int' , default = '0' ),
395
+ votes = dict (type = 'int' , default = '1' ),
360
396
state = dict (default = 'present' , choices = ['absent' , 'present' ]),
361
397
)
362
398
)
363
399
364
400
if not pymongo_found :
365
- module .fail_json (msg = 'the python pymongo (>= 3.2) module is required' )
401
+ module .fail_json (msg = missing_required_lib ( ' pymongo' ) )
366
402
367
403
login_user = module .params ['login_user' ]
368
404
login_password = module .params ['login_password' ]
@@ -398,7 +434,7 @@ def main():
398
434
connection_params ["ssl_cert_reqs" ] = getattr (ssl_lib , module .params ['ssl_cert_reqs' ])
399
435
400
436
client = MongoClient (** connection_params )
401
- authenticate (client , login_user , login_password )
437
+ authenticate (module , client , login_user , login_password )
402
438
client ['admin' ].command ('replSetGetStatus' )
403
439
404
440
except ServerSelectionTimeoutError :
@@ -417,24 +453,25 @@ def main():
417
453
connection_params ["ssl_cert_reqs" ] = getattr (ssl_lib , module .params ['ssl_cert_reqs' ])
418
454
419
455
client = MongoClient (** connection_params )
420
- authenticate (client , login_user , login_password )
456
+ authenticate (module , client , login_user , login_password )
421
457
if state == 'present' :
422
- new_host = { '_id' : 0 , 'host' : "{0}:{1}" .format (host_name , host_port ) }
423
- if priority != 1.0 : new_host ['priority' ] = priority
424
- config = { '_id' : "{0}" .format (replica_set ), 'members' : [new_host ] }
458
+ new_host = {'_id' : 0 , 'host' : "{0}:{1}" .format (host_name , host_port )}
459
+ if priority != 1.0 :
460
+ new_host ['priority' ] = priority
461
+ config = {'_id' : "{0}" .format (replica_set ), 'members' : [new_host ]}
425
462
client ['admin' ].command ('replSetInitiate' , config )
426
463
client .close ()
427
464
wait_for_ok_and_master (module , connection_params )
428
465
replica_set_created = True
429
466
module .exit_json (changed = True , host_name = host_name , host_port = host_port , host_type = host_type )
430
467
except OperationFailure as e :
431
- module .fail_json (msg = 'Unable to initiate replica set: %s' % str ( e ))
468
+ module .fail_json (msg = 'Unable to initiate replica set: %s' % to_native ( e ), exception = traceback . format_exc ( ))
432
469
except ConnectionFailure as e :
433
- module .fail_json (msg = 'unable to connect to database: %s' % str ( e ))
470
+ module .fail_json (msg = 'unable to connect to database: %s' % to_native ( e ), exception = traceback . format_exc ( ))
434
471
435
472
# reconnect again
436
473
client = MongoClient (** connection_params )
437
- authenticate (client , login_user , login_password )
474
+ authenticate (module , client , login_user , login_password )
438
475
check_compatibility (module , client )
439
476
check_members (state , module , client , host_name , host_port , host_type )
440
477
@@ -445,22 +482,22 @@ def main():
445
482
try :
446
483
if not replica_set_created :
447
484
add_host (module , client , host_name , host_port , host_type ,
448
- build_indexes = module .params ['build_indexes' ],
449
- hidden = module .params ['hidden' ],
450
- priority = float (module .params ['priority' ]),
451
- slave_delay = module .params ['slave_delay' ],
452
- votes = module .params ['votes' ])
485
+ build_indexes = module .params ['build_indexes' ],
486
+ hidden = module .params ['hidden' ],
487
+ priority = float (module .params ['priority' ]),
488
+ slave_delay = module .params ['slave_delay' ],
489
+ votes = module .params ['votes' ])
453
490
except OperationFailure as e :
454
- module .fail_json (msg = 'Unable to add new member to replica set: %s' % str ( e ))
491
+ module .fail_json (msg = 'Unable to add new member to replica set: %s' % to_native ( e ), exception = traceback . format_exc ( ))
455
492
456
493
elif state == 'absent' :
457
494
try :
458
495
remove_host (module , client , host_name )
459
496
except OperationFailure as e :
460
- module .fail_json (msg = 'Unable to remove member of replica set: %s' % str ( e ))
497
+ module .fail_json (msg = 'Unable to remove member of replica set: %s' % to_native ( e ), exception = traceback . format_exc ( ))
461
498
462
499
module .exit_json (changed = True , host_name = host_name , host_port = host_port , host_type = host_type )
463
500
464
- # import module snippets
465
- from ansible . module_utils . basic import *
466
- main ()
501
+
502
+ if __name__ == '__main__' :
503
+ main ()
0 commit comments