Skip to content

Commit c419a3c

Browse files
authored
Merge pull request #247 from UnderGreen/sergei/python3
Support for Python 3 on controller node
2 parents 36f22ae + a4167ff commit c419a3c

File tree

6 files changed

+110
-64
lines changed

6 files changed

+110
-64
lines changed

library/mongodb_replication.py

Lines changed: 90 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
# You should have received a copy of the GNU General Public License
1818
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
1919

20+
from __future__ import (absolute_import, division, print_function)
21+
__metaclass__ = type
22+
2023
DOCUMENTATION = '''
2124
---
2225
module: mongodb_replication
@@ -156,39 +159,67 @@
156159
type: string
157160
sample: "replica"
158161
'''
159-
import ConfigParser
162+
163+
import os
160164
import ssl as ssl_lib
161165
import time
166+
import traceback
162167
from datetime import datetime as dtdatetime
163168
from distutils.version import LooseVersion
169+
164170
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
170172
from pymongo import version as PyMongoVersion
171173
from pymongo import MongoClient
172174
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
174181
else:
175182
pymongo_found = True
176183

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+
177188
# =========================================
178189
# MongoDB module specific support methods.
179190
#
191+
192+
180193
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)')
188220

189221

190222
def check_members(state, module, client, host_name, host_port, host_type):
191-
admin_db = client['admin']
192223
local_db = client['local']
193224

194225
if local_db.system.replset.count() > 1:
@@ -214,6 +245,7 @@ def check_members(state, module, client, host_name, host_port, host_type):
214245
if "{0}:{1}".format(host_name, host_port) not in member['host'] and member['arbiterOnly']:
215246
module.exit_json(changed=False, host_name=host_name, host_port=host_port, host_type=host_type)
216247

248+
217249
def add_host(module, client, host_name, host_port, host_type, timeout=180, **kwargs):
218250
start_time = dtdatetime.now()
219251
while True:
@@ -229,8 +261,8 @@ def add_host(module, client, host_name, host_port, host_type, timeout=180, **kwa
229261
module.fail_json(msg='no config object retrievable from local.system.replset')
230262

231263
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)}
234266
if host_type == 'arbiter':
235267
new_host['arbiterOnly'] = True
236268

@@ -254,14 +286,14 @@ def add_host(module, client, host_name, host_port, host_type, timeout=180, **kwa
254286
return
255287
except (OperationFailure, AutoReconnect) as e:
256288
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())
258290
time.sleep(5)
259291

292+
260293
def remove_host(module, client, host_name, timeout=180):
261294
start_time = dtdatetime.now()
262295
while True:
263296
try:
264-
admin_db = client['admin']
265297
local_db = client['local']
266298

267299
if local_db.system.replset.count() > 1:
@@ -283,30 +315,32 @@ def remove_host(module, client, host_name, timeout=180):
283315
module.fail_json(msg=fail_msg)
284316
except (OperationFailure, AutoReconnect) as e:
285317
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())
287319
time.sleep(5)
288320

321+
289322
def load_mongocnf():
290-
config = ConfigParser.RawConfigParser()
323+
config = configparser.RawConfigParser()
291324
mongocnf = os.path.expanduser('~/.mongodb.cnf')
292325

293326
try:
294327
config.readfp(open(mongocnf))
295328
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')
298331
)
299-
except (ConfigParser.NoOptionError, IOError):
332+
except (configparser.NoOptionError, IOError):
300333
return False
301334

302335
return creds
303336

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):
305339
start_time = dtdatetime.now()
306340
while True:
307341
try:
308342
client = MongoClient(**connection_params)
309-
authenticate(client, connection_params["username"], connection_params["password"])
343+
authenticate(module, client, connection_params["username"], connection_params["password"])
310344

311345
status = client.admin.command('replSetGetStatus', check=False)
312346
if status['ok'] == 1 and status['myState'] == 1:
@@ -322,7 +356,8 @@ def wait_for_ok_and_master(module, connection_params, timeout = 180):
322356

323357
time.sleep(1)
324358

325-
def authenticate(client, login_user, login_password):
359+
360+
def authenticate(module, client, login_user, login_password):
326361
if login_user is None and login_password is None:
327362
mongocnf_creds = load_mongocnf()
328363
if mongocnf_creds is not False:
@@ -338,9 +373,10 @@ def authenticate(client, login_user, login_password):
338373
# Module execution.
339374
#
340375

376+
341377
def main():
342378
module = AnsibleModule(
343-
argument_spec = dict(
379+
argument_spec=dict(
344380
login_user=dict(default=None),
345381
login_password=dict(default=None, no_log=True),
346382
login_host=dict(default='localhost'),
@@ -349,20 +385,20 @@ def main():
349385
replica_set=dict(default=None),
350386
host_name=dict(default='localhost'),
351387
host_port=dict(default='27017'),
352-
host_type=dict(default='replica', choices=['replica','arbiter']),
388+
host_type=dict(default='replica', choices=['replica', 'arbiter']),
353389
ssl=dict(default=False, type='bool'),
354390
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'),
360396
state=dict(default='present', choices=['absent', 'present']),
361397
)
362398
)
363399

364400
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'))
366402

367403
login_user = module.params['login_user']
368404
login_password = module.params['login_password']
@@ -398,7 +434,7 @@ def main():
398434
connection_params["ssl_cert_reqs"] = getattr(ssl_lib, module.params['ssl_cert_reqs'])
399435

400436
client = MongoClient(**connection_params)
401-
authenticate(client, login_user, login_password)
437+
authenticate(module, client, login_user, login_password)
402438
client['admin'].command('replSetGetStatus')
403439

404440
except ServerSelectionTimeoutError:
@@ -417,24 +453,25 @@ def main():
417453
connection_params["ssl_cert_reqs"] = getattr(ssl_lib, module.params['ssl_cert_reqs'])
418454

419455
client = MongoClient(**connection_params)
420-
authenticate(client, login_user, login_password)
456+
authenticate(module, client, login_user, login_password)
421457
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]}
425462
client['admin'].command('replSetInitiate', config)
426463
client.close()
427464
wait_for_ok_and_master(module, connection_params)
428465
replica_set_created = True
429466
module.exit_json(changed=True, host_name=host_name, host_port=host_port, host_type=host_type)
430467
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())
432469
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())
434471

435472
# reconnect again
436473
client = MongoClient(**connection_params)
437-
authenticate(client, login_user, login_password)
474+
authenticate(module, client, login_user, login_password)
438475
check_compatibility(module, client)
439476
check_members(state, module, client, host_name, host_port, host_type)
440477

@@ -445,22 +482,22 @@ def main():
445482
try:
446483
if not replica_set_created:
447484
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'])
453490
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())
455492

456493
elif state == 'absent':
457494
try:
458495
remove_host(module, client, host_name)
459496
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())
461498

462499
module.exit_json(changed=True, host_name=host_name, host_port=host_port, host_type=host_type)
463500

464-
# import module snippets
465-
from ansible.module_utils.basic import *
466-
main()
501+
502+
if __name__ == '__main__':
503+
main()

molecule/cluster/molecule.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ dependency:
33
name: galaxy
44
driver:
55
name: docker
6+
lint: |
7+
set -e
8+
yamllint .
9+
ansible-lint .
10+
flake8 library --ignore=E501,E402
611
platforms:
712
- name: mongo1
813
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest"
@@ -45,13 +50,16 @@ provisioner:
4550
inventory:
4651
host_vars:
4752
mongo1:
53+
ansible_python_interpreter: auto_silent
4854
mongodb_master: true
4955
mongodb_replication_params:
5056
- host_name: "{{ hostvars[inventory_hostname].ansible_default_ipv4.address }}"
5157
mongo2:
58+
ansible_python_interpreter: auto_silent
5259
mongodb_replication_params:
5360
- host_name: "{{ hostvars[inventory_hostname].ansible_default_ipv4.address }}"
5461
mongo3:
62+
ansible_python_interpreter: auto_silent
5563
mongodb_net_port: 30000
5664
mongodb_storage_journal_enabled: "{{ mongodb_major_version is version('4.0', '>=') }}"
5765
mongodb_storage_smallfiles: true

molecule/default/molecule.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ lint: |
77
set -e
88
yamllint .
99
ansible-lint .
10-
#flake8
10+
flake8 library --ignore=E501,E402
1111
platforms:
1212
- name: mongo1
1313
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest"

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ molecule >= 3.2, < 3.3
33
molecule-docker >= 0.2, < 0.3
44
ansible-lint >=5.0, < 5.1
55
yamllint >= 1.26, < 1.27
6+
flake8 >= 3.9, < 3.10

tasks/auth_initialization.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
password: "{{ item.password }}"
2121
update_password: "{{ mongodb_user_update_password }}"
2222
roles: "{{ item.roles }}"
23-
login_port: "{{ mongodb_net_port }}"
23+
login_port: "{{ mongodb_net_port | int }}"
2424
with_items:
2525
- {
2626
name: "{{ mongodb_user_admin_name }}",
2727
password: "{{ mongodb_user_admin_password }}",
2828
roles: "userAdminAnyDatabase"
2929
}
30-
no_log: true
30+
no_log: false
3131

3232
- name: create administrative user "{{ mongodb_root_admin_name }}"
3333
mongodb_user:
@@ -36,14 +36,14 @@
3636
password: "{{ item.password }}"
3737
update_password: "{{ mongodb_user_update_password }}"
3838
roles: "{{ item.roles }}"
39-
login_port: "{{ mongodb_net_port }}"
39+
login_port: "{{ mongodb_net_port | int }}"
4040
with_items:
4141
- {
4242
name: "{{ mongodb_root_admin_name }}",
4343
password: "{{ mongodb_root_admin_password }}",
4444
roles: "root"
4545
}
46-
no_log: true
46+
no_log: false
4747

4848
- name: create backup user "{{ mongodb_root_backup_name }}"
4949
mongodb_user:
@@ -52,14 +52,14 @@
5252
password: "{{ item.password }}"
5353
update_password: "{{ mongodb_user_update_password }}"
5454
roles: "{{ item.roles }}"
55-
login_port: "{{ mongodb_net_port }}"
55+
login_port: "{{ mongodb_net_port | int }}"
5656
with_items:
5757
- {
5858
name: "{{ mongodb_root_backup_name }}",
5959
password: "{{ mongodb_root_backup_password }}",
6060
roles: "backup,clusterMonitor"
6161
}
62-
no_log: true
62+
no_log: false
6363

6464
- name: Move back mongod.conf
6565
template:

0 commit comments

Comments
 (0)