Merge ~peter-sabaini/charm-mongodb:upgrade-functests into charm-mongodb:master

Proposed by Peter Sabaini
Status: Merged
Approved by: Peter Sabaini
Approved revision: d39567042500595199b43817df89ace3c1f393f5
Merged at revision: ed31d71d1f45df6027bdcf8cd7ea760b9eced5d5
Proposed branch: ~peter-sabaini/charm-mongodb:upgrade-functests
Merge into: charm-mongodb:master
Diff against target: 1188 lines (+269/-107)
13 files modified
.gitignore (+2/-1)
Makefile (+10/-6)
actions/backup.py (+4/-4)
dev/null (+0/-74)
hooks/hooks.py (+18/-18)
tests/bundles/bionic-shard.yaml (+27/-0)
tests/bundles/bionic.yaml (+9/-0)
tests/bundles/xenial.yaml (+9/-0)
tests/test_requirements.txt (+2/-0)
tests/tests.yaml (+17/-0)
tests/tests_mongodb.py (+137/-0)
tox.ini (+30/-0)
unit_tests/test_hooks.py (+4/-4)
Reviewer Review Type Date Requested Status
Adam Dyess (community) Approve
Paul Goins Approve
Review via email: mp+382331@code.launchpad.net

Commit message

Functional testing update

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Paul Goins (vultaire) wrote :

I haven't actually tested this myself, but it looks good. It doesn't look like this provides parity with the Amulet suite (correct me if I'm wrong), but it looks like it does port us over to Python 3 and provides a start of a new Zaza-based test suite.

I'm +1 for this, but would like one more pair of eyes given the size of this.

review: Approve
Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Thanks Paul.

On 15.04.20 23:12, Paul Goins wrote:
> Review: Approve
>
> I haven't actually tested this myself, but it looks good. It doesn't look like this provides parity with the Amulet suite (correct me if I'm wrong), but it looks like it does port us over to Python 3 and provides a start of a new Zaza-based test suite.
>
> I'm +1 for this, but would like one more pair of eyes given the size of this.
>
> Diff comments:
>
>> diff --git a/Makefile b/Makefile
>> index c23418b..68db95f 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -31,14 +32,14 @@ lint: .venv
>> .venv/bin/flake8 --exclude hooks/charmhelpers actions $(ACTIONS) hooks tests unit_tests
>> .venv/bin/charm-proof
>>
>> -test: .venv
>> +unit:
>> @echo Starting unit tests...
>> - .venv/bin/nosetests -s --nologcapture --with-coverage $(EXTRA) unit_tests/
>> - .venv/bin/nosetests -s --nologcapture --with-coverage $(EXTRA) actions/
>> + @tox -e unit
>>
>> -functional_test:
>> - @echo Starting amulet tests...
>> - @juju test -v -p AMULET_HTTP_PROXY --timeout 900
>> +functional:
>> + @echo Starting functional tests...
>> + rm -rf .venv
>
> .venv seems tangential to the functional target at this point because of moving to tox. We can remove it, but I'm not sure it's necessary. (Feel free to ignore.)

I've added it as the Zaza deploy uploads the charm incl. the .venv, and balks at the symlink to /usr/bin/python
I will add a comment to that effect

>> + @tox -e functional
>>
>> sync:
>> @mkdir -p bin
>> diff --git a/tox.ini b/tox.ini
>> new file mode 100644
>> index 0000000..013abf0
>> --- /dev/null
>> +++ b/tox.ini
>> @@ -0,0 +1,34 @@
>> +[tox]
>> +skipsdist=True
>> +envlist = unit, functional, lint
>> +skip_missing_interpreters = True
>> +
>> +[testenv]
>> +setenv =
>> + PYTHONPATH = .
>> +passenv =
>> + HOME
>> + JUJU_REPOSITORY
>> + MODEL_SETTINGS
>> +
>> +[testenv:unit]
>> +basepython = python2
>> +commands =
>> + nosetests -s --nologcapture --with-coverage unit_tests/ actions/
>> +deps = -r{toxinidir}/test_requirements.txt
>> +
>> +[testenv:functional]
>> +basepython = python3
>> +commands =
>> + functest-run-suite --keep-model
>> +deps =
>> + git+https://github.com/openstack-charmers/zaza.git#egg=zaza
>> + pymongo
>> +
>> +[testenv:func-smoke]
>> +basepython = python3
>> +commands =
>> + functest-run-suite --keep-model --smoke
>> +deps =
>> + git+https://github.com/openstack-charmers/zaza.git#egg=zaza
>> + pymongo
>
> Since we have this list of dependencies in two different places, I feel like it may be better to put it in a tests/test_requirements.txt, in case it needs to be modified later. Seems this may be easy for someone to update one copy and not the other by mistake.
Fair point, will do
>
>
>

Revision history for this message
Adam Dyess (addyess) wrote :

Wow this is great work. Everything reads so clearly with the zaza tests.

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision ed31d71d1f45df6027bdcf8cd7ea760b9eced5d5

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/.gitignore b/.gitignore
index a936365..e6ff298 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,9 @@
3.pydevproject3.pydevproject
4.coverage4.coverage
5.settings5.settings
6.idea/
6*.pyc7*.pyc
7.venv/8.tox/
8bin/*9bin/*
9scripts/charm-helpers-sync.py10scripts/charm-helpers-sync.py
10exec.d/*11exec.d/*
diff --git a/Makefile b/Makefile
index c23418b..8e20153 100644
--- a/Makefile
+++ b/Makefile
@@ -20,6 +20,7 @@ clean:
20 rm -f .coverage20 rm -f .coverage
21 find . -name '*.pyc' -delete21 find . -name '*.pyc' -delete
22 rm -rf .venv22 rm -rf .venv
23 rm -rf .tox
23 (which dh_clean && dh_clean) || true24 (which dh_clean && dh_clean) || true
2425
25.venv:26.venv:
@@ -31,14 +32,14 @@ lint: .venv
31 .venv/bin/flake8 --exclude hooks/charmhelpers actions $(ACTIONS) hooks tests unit_tests32 .venv/bin/flake8 --exclude hooks/charmhelpers actions $(ACTIONS) hooks tests unit_tests
32 .venv/bin/charm-proof33 .venv/bin/charm-proof
3334
34test: .venv35unit:
35 @echo Starting unit tests...36 @echo Starting unit tests...
36 .venv/bin/nosetests -s --nologcapture --with-coverage $(EXTRA) unit_tests/37 @tox -e unit
37 .venv/bin/nosetests -s --nologcapture --with-coverage $(EXTRA) actions/
3838
39functional_test:39functional:
40 @echo Starting amulet tests...40 @echo Starting functional tests...
41 @juju test -v -p AMULET_HTTP_PROXY --timeout 90041 rm -rf .venv # rm the python2 venv from unittests as it fails the juju deploy
42 @tox -e functional
4243
43sync:44sync:
44 @mkdir -p bin45 @mkdir -p bin
@@ -48,3 +49,6 @@ sync:
48publish: lint unit_test49publish: lint unit_test
49 bzr push lp:charms/mongodb50 bzr push lp:charms/mongodb
50 bzr push lp:charms/trusty/mongodb51 bzr push lp:charms/trusty/mongodb
52
53# The targets below don't depend on a file
54.PHONY: lint test unittest functional publish sync
diff --git a/actions/backup.py b/actions/backup.py
index ac7c4d9..cac42cd 100644
--- a/actions/backup.py
+++ b/actions/backup.py
@@ -35,9 +35,9 @@ def restore():
35def backup_command(cmd, args, dir):35def backup_command(cmd, args, dir):
36 try:36 try:
37 mkdir(dir)37 mkdir(dir)
38 except OSError, e:38 except OSError as e:
39 pass # Ignoring, the directory already exists39 pass # Ignoring, the directory already exists
40 except Exception, e:40 except Exception as e:
41 action_set({"directory creation exception": e})41 action_set({"directory creation exception": e})
42 action_fail(str(e))42 action_fail(str(e))
43 return43 return
@@ -48,10 +48,10 @@ def backup_command(cmd, args, dir):
48 try:48 try:
49 output = execute(command, dir)49 output = execute(command, dir)
50 action_set({"output": output})50 action_set({"output": output})
51 except subprocess.CalledProcessError, e:51 except subprocess.CalledProcessError as e:
52 action_set({"error_code": e.returncode,52 action_set({"error_code": e.returncode,
53 "exception": e, "output": e.output})53 "exception": e, "output": e.output})
54 action_fail(str(e))54 action_fail(str(e))
55 except Exception, e:55 except Exception as e:
56 action_set({"exception": e})56 action_set({"exception": e})
57 action_fail(str(e))57 action_fail(str(e))
diff --git a/hooks/hooks.py b/hooks/hooks.py
index e2bc2b5..a4a3ee5 100755
--- a/hooks/hooks.py
+++ b/hooks/hooks.py
@@ -150,7 +150,7 @@ def port_check(host=None, port=None, protocol='TCP'):
150 s.shutdown(socket.SHUT_RDWR)150 s.shutdown(socket.SHUT_RDWR)
151 juju_log("port_check: %s:%s/%s is open" % (host, port, protocol))151 juju_log("port_check: %s:%s/%s is open" % (host, port, protocol))
152 return(True)152 return(True)
153 except Exception, e:153 except Exception as e:
154 juju_log("port_check: Unable to connect to %s:%s/%s." %154 juju_log("port_check: Unable to connect to %s:%s/%s." %
155 (host, port, protocol))155 (host, port, protocol))
156 juju_log("port_check: Exception: %s" % str(e))156 juju_log("port_check: Exception: %s" % str(e))
@@ -195,7 +195,7 @@ def update_file(filename=None, new_data=None, old_data=None):
195 with open(filename, 'w') as f:195 with open(filename, 'w') as f:
196 f.write(new_data)196 f.write(new_data)
197 retVal = True197 retVal = True
198 except Exception, e:198 except Exception as e:
199 juju_log(str(e))199 juju_log(str(e))
200 retVal = False200 retVal = False
201 finally:201 finally:
@@ -211,7 +211,7 @@ def process_check(pid=None):
211 else:211 else:
212 juju_log("process_check: pid not defined.")212 juju_log("process_check: pid not defined.")
213 retVal = (None, None)213 retVal = (None, None)
214 except Exception, e:214 except Exception as e:
215 juju_log("process_check exception: %s" % str(e))215 juju_log("process_check exception: %s" % str(e))
216 retVal = (None, None)216 retVal = (None, None)
217 finally:217 finally:
@@ -490,7 +490,7 @@ def mongo_client_smart(host='localhost', command=None):
490 '--eval', 'printjson(%s)' % command]490 '--eval', 'printjson(%s)' % command]
491 juju_log("mongo_client_smart executing: %s" % str(cmd_line), level=DEBUG)491 juju_log("mongo_client_smart executing: %s" % str(cmd_line), level=DEBUG)
492492
493 for i in xrange(MONGO_CLIENT_RETRIES):493 for i in range(MONGO_CLIENT_RETRIES):
494 try:494 try:
495 cmd_output = subprocess.check_output(cmd_line)495 cmd_output = subprocess.check_output(cmd_line)
496 juju_log('mongo_client_smart executed, output: %s' %496 juju_log('mongo_client_smart executed, output: %s' %
@@ -614,7 +614,7 @@ def enable_replset(replicaset_name=None):
614614
615 juju_log('enable_replset will return: %s' % str(retVal), level=DEBUG)615 juju_log('enable_replset will return: %s' % str(retVal), level=DEBUG)
616616
617 except Exception, e:617 except Exception as e:
618 juju_log(str(e), level=WARNING)618 juju_log(str(e), level=WARNING)
619 retVal = False619 retVal = False
620 finally:620 finally:
@@ -632,7 +632,7 @@ def remove_replset_from_upstart():
632 mongodb_init_config = re.sub(r' --replSet .\w+', '',632 mongodb_init_config = re.sub(r' --replSet .\w+', '',
633 mongodb_init_config)633 mongodb_init_config)
634 retVal = update_file(default_mongodb_init_config, mongodb_init_config)634 retVal = update_file(default_mongodb_init_config, mongodb_init_config)
635 except Exception, e:635 except Exception as e:
636 juju_log(str(e))636 juju_log(str(e))
637 retVal = False637 retVal = False
638 finally:638 finally:
@@ -643,7 +643,7 @@ def step_down_replset_primary():
643 """Steps down the primary643 """Steps down the primary
644 """644 """
645 retVal = mongo_client('localhost', 'rs.stepDown()')645 retVal = mongo_client('localhost', 'rs.stepDown()')
646 for i in xrange(MONGO_CLIENT_RETRIES):646 for i in range(MONGO_CLIENT_RETRIES):
647 if not am_i_primary():647 if not am_i_primary():
648 juju_log("step_down_replset_primary returns: %s" % retVal,648 juju_log("step_down_replset_primary returns: %s" % retVal,
649 level=DEBUG)649 level=DEBUG)
@@ -665,7 +665,7 @@ def remove_rest_from_upstart():
665 mongodb_init_config = regex_sub([(' --rest ', ' ')],665 mongodb_init_config = regex_sub([(' --rest ', ' ')],
666 mongodb_init_config)666 mongodb_init_config)
667 retVal = update_file(default_mongodb_init_config, mongodb_init_config)667 retVal = update_file(default_mongodb_init_config, mongodb_init_config)
668 except Exception, e:668 except Exception as e:
669 juju_log(str(e))669 juju_log(str(e))
670 retVal = False670 retVal = False
671 finally:671 finally:
@@ -743,7 +743,7 @@ def disable_configsvr(port=None):
743 os.kill(int(pid), signal.SIGTERM)743 os.kill(int(pid), signal.SIGTERM)
744 os.unlink('/var/run/mongodb/configsvr.pid')744 os.unlink('/var/run/mongodb/configsvr.pid')
745 retVal = True745 retVal = True
746 except Exception, e:746 except Exception as e:
747 juju_log('no config server running ...')747 juju_log('no config server running ...')
748 juju_log("Exception: %s" % str(e))748 juju_log("Exception: %s" % str(e))
749 retVal = False749 retVal = False
@@ -835,7 +835,7 @@ def disable_mongos(port=None):
835 os.kill(int(pid), signal.SIGTERM)835 os.kill(int(pid), signal.SIGTERM)
836 os.unlink('/var/run/mongodb/mongos.pid')836 os.unlink('/var/run/mongodb/mongos.pid')
837 retVal = True837 retVal = True
838 except Exception, e:838 except Exception as e:
839 juju_log('no mongo router running ...')839 juju_log('no mongo router running ...')
840 juju_log("Exception: %s" % str(e))840 juju_log("Exception: %s" % str(e))
841 retVal = False841 retVal = False
@@ -948,7 +948,7 @@ def backup_cronjob(disable=False):
948948
949 with open(script_filename, 'w') as output:949 with open(script_filename, 'w') as output:
950 output.writelines(rendered)950 output.writelines(rendered)
951 chmod(script_filename, 0755)951 chmod(script_filename, 0o755)
952952
953 juju_log('Installing cron.d/mongodb')953 juju_log('Installing cron.d/mongodb')
954954
@@ -1085,7 +1085,7 @@ def config_changed():
1085 # update config-server information and port1085 # update config-server information and port
1086 try:1086 try:
1087 (configsvr_pid, configsvr_cmd_line) = configsvr_status()1087 (configsvr_pid, configsvr_cmd_line) = configsvr_status()
1088 except Exception, e:1088 except Exception as e:
1089 configsvr_pid = None1089 configsvr_pid = None
1090 configsvr_cmd_line = None1090 configsvr_cmd_line = None
1091 juju_log("config_changed: configsvr_status failed.")1091 juju_log("config_changed: configsvr_status failed.")
@@ -1101,7 +1101,7 @@ def config_changed():
1101 # update mongos information and port1101 # update mongos information and port
1102 try:1102 try:
1103 (mongos_pid, mongos_cmd_line) = mongos_status()1103 (mongos_pid, mongos_cmd_line) = mongos_status()
1104 except Exception, e:1104 except Exception as e:
1105 mongos_pid = None1105 mongos_pid = None
1106 mongos_cmd_line = None1106 mongos_cmd_line = None
1107 juju_log("config_changed: mongos_status failed.")1107 juju_log("config_changed: mongos_status failed.")
@@ -1137,7 +1137,7 @@ def stop_hook():
1137 retVal = service('stop', 'mongodb')1137 retVal = service('stop', 'mongodb')
1138 os.remove('/var/lib/mongodb/mongod.lock')1138 os.remove('/var/lib/mongodb/mongod.lock')
1139 # FIXME Need to check if this is still needed1139 # FIXME Need to check if this is still needed
1140 except Exception, e:1140 except Exception as e:
1141 juju_log(str(e))1141 juju_log(str(e))
1142 retVal = False1142 retVal = False
1143 finally:1143 finally:
@@ -1227,7 +1227,7 @@ def rs_add(host):
1227 juju_log("Executing: %s" % cmd_line, level=DEBUG)1227 juju_log("Executing: %s" % cmd_line, level=DEBUG)
1228 run(cmd_line)1228 run(cmd_line)
12291229
1230 for i in xrange(MONGO_CLIENT_RETRIES):1230 for i in range(MONGO_CLIENT_RETRIES):
1231 c = MongoClient('localhost')1231 c = MongoClient('localhost')
1232 subprocess.check_output(cmd_line)1232 subprocess.check_output(cmd_line)
1233 r = run_admin_command(c, 'replSetGetStatus')1233 r = run_admin_command(c, 'replSetGetStatus')
@@ -1243,7 +1243,7 @@ def rs_add(host):
12431243
1244def am_i_primary():1244def am_i_primary():
1245 c = MongoClient('localhost')1245 c = MongoClient('localhost')
1246 for i in xrange(10):1246 for i in range(10):
1247 try:1247 try:
1248 r = run_admin_command(c, 'replSetGetStatus')1248 r = run_admin_command(c, 'replSetGetStatus')
1249 pretty_r = pprint.pformat(r)1249 pretty_r = pprint.pformat(r)
@@ -1310,7 +1310,7 @@ def get_mongod_version():
1310 Mainly used for application_set_version in config-changed hook1310 Mainly used for application_set_version in config-changed hook
1311 """1311 """
13121312
1313 c = MongoClient('localhost')1313 c = MongoClient('localhost', serverSelectionTimeoutMS=60000)
1314 return c.server_info()['version']1314 return c.server_info()['version']
13151315
13161316
@@ -1612,7 +1612,7 @@ def run(command, exit_on_error=True):
1612 juju_log(command)1612 juju_log(command)
1613 return subprocess.check_output(1613 return subprocess.check_output(
1614 command, stderr=subprocess.STDOUT, shell=True)1614 command, stderr=subprocess.STDOUT, shell=True)
1615 except subprocess.CalledProcessError, e:1615 except subprocess.CalledProcessError as e:
1616 juju_log("status=%d, output=%s" % (e.returncode, e.output))1616 juju_log("status=%d, output=%s" % (e.returncode, e.output))
1617 if exit_on_error:1617 if exit_on_error:
1618 sys.exit(e.returncode)1618 sys.exit(e.returncode)
diff --git a/tests/00_setup.sh b/tests/00_setup.sh
1619deleted file mode 1007551619deleted file mode 100755
index 4f58709..0000000
--- a/tests/00_setup.sh
+++ /dev/null
@@ -1,9 +0,0 @@
1#!/bin/bash
2
3set -e
4
5sudo apt-get install python-setuptools -y
6sudo add-apt-repository ppa:juju/stable -y
7
8sudo apt-get update
9sudo apt-get install amulet python3 python3-requests python3-pymongo juju-core charm-tools python-mock python-pymongo -y
diff --git a/tests/base_deploy.py b/tests/base_deploy.py
10deleted file mode 1006440deleted file mode 100644
index b136cca..0000000
--- a/tests/base_deploy.py
+++ /dev/null
@@ -1,90 +0,0 @@
1#!/usr/bin/env python3
2
3import amulet
4import requests
5import sys
6import time
7import traceback
8from pymongo import MongoClient
9
10
11class BasicMongo(object):
12 def __init__(self, units, series, deploy_timeout):
13 self.units = units
14 self.series = series
15 self.deploy_timeout = deploy_timeout
16 self.d = amulet.Deployment(series=self.series)
17 self.addy = None
18
19 def deploy(self):
20 try:
21 self.d.setup(self.deploy_timeout)
22 self.d.sentry.wait(self.deploy_timeout)
23 except amulet.helpers.TimeoutError:
24 message = 'The environment did not setup in %d seconds.',
25 self.deploy_timeout
26 amulet.raise_status(amulet.SKIP, msg=message)
27
28 self.sentry_dict = {svc: self.d.sentry[svc]
29 for svc in list(self.d.sentry.unit)}
30
31 def validate_status_interface(self):
32 addy = self.addy
33 fmt = "http://{}:28017"
34 if ":" in addy:
35 fmt = "http://[{}]:28017"
36
37 time_between = 10
38 tries = self.deploy_timeout / time_between
39
40 try:
41 r = requests.get(fmt.format(addy), verify=False)
42 r.raise_for_status()
43 except requests.exception.ConnectionError as ex:
44 sys.stderr.write(
45 'Connection error, sleep and retry... to {}: {}\n'.
46 format(addy, ex))
47 tb_lines = traceback.format_exception(ex.__class__,
48 ex, ex.__traceback__)
49 tb_text = ''.join(tb_lines)
50 sys.stderr.write(tb_text)
51 tries = tries - 1
52 if tries < 0:
53 sys.stderr.write('retry limit caught, failing...\n')
54 time.sleep(time_between)
55
56 def validate_world_connectivity(self):
57 addy = self.addy
58 # ipv6 proper formating
59 if ":" in addy:
60 addy = "[{}]".format(addy)
61
62 client = MongoClient(addy)
63 db = client['test']
64
65 # Can we successfully insert?
66 insert_id = db.amulet.insert({'assert': True})
67 if insert_id is None:
68 amulet.raise_status(amulet.FAIL, msg="Failed to insert test data")
69
70 # Can we delete from a shard using the Mongos hub?
71 result = db.amulet.remove(insert_id)
72 if 'err' in result and result['err'] is not None:
73 amulet.raise_status(amulet.FAIL, msg="Failed to remove test data")
74
75 def validate_running_services(self):
76 for service in self.sentry_dict:
77 grep_command = 'grep RELEASE /etc/lsb-release'
78 release = self.sentry_dict[service].run(grep_command)
79 release = str(release).split('=')[1]
80 if release >= '15.10':
81 status_string = 'active (running)' # systemd
82 else:
83 status_string = 'mongodb start/running' # upstart
84
85 output = self.sentry_dict[service].run('service mongodb status')
86 service_active = str(output).find(status_string)
87 if service_active == -1:
88 message = "Failed to find running MongoDB on host {}".format(
89 service)
90 amulet.raise_status(amulet.SKIP, msg=message)
diff --git a/tests/bundles/bionic-shard.yaml b/tests/bundles/bionic-shard.yaml
91new file mode 1006440new file mode 100644
index 0000000..b7ee354
--- /dev/null
+++ b/tests/bundles/bionic-shard.yaml
@@ -0,0 +1,27 @@
1series: bionic
2description: "mongodb-charm test bundle"
3applications:
4 configsvr:
5 charm: "../../."
6 num_units: 1
7 options:
8 replicaset: configsvr
9 mongodb:
10 charm: "../../."
11 num_units: 1
12 options:
13 replicaset: testset
14 shard1:
15 charm: "../../."
16 num_units: 1
17 options:
18 replicaset: shard1
19 shard2:
20 charm: "../../."
21 num_units: 1
22 options:
23 replicaset: shard2
24relations:
25 - [ "configsvr:configsvr", "mongodb:mongos-cfg" ]
26 - [ "mongodb:mongos", "shard1:database" ]
27 - [ "mongodb:mongos", "shard2:database" ]
diff --git a/tests/bundles/bionic.yaml b/tests/bundles/bionic.yaml
0new file mode 10064428new file mode 100644
index 0000000..ebfb98e
--- /dev/null
+++ b/tests/bundles/bionic.yaml
@@ -0,0 +1,9 @@
1series: bionic
2description: "mongodb-charm test bundle"
3applications:
4 mongodb:
5 charm: "../../."
6 num_units: 3
7 options:
8 replicaset: testset
9 backup_directory: /var/backups
diff --git a/tests/bundles/xenial.yaml b/tests/bundles/xenial.yaml
0new file mode 10064410new file mode 100644
index 0000000..9d29342
--- /dev/null
+++ b/tests/bundles/xenial.yaml
@@ -0,0 +1,9 @@
1series: xenial
2description: "mongodb-charm test bundle"
3applications:
4 mongodb:
5 charm: "../../."
6 num_units: 3
7 options:
8 replicaset: testset
9 backup_directory: /var/backups
diff --git a/tests/deploy_replicaset-trusty b/tests/deploy_replicaset-trusty
0deleted file mode 10075510deleted file mode 100755
index 9ed77a8..0000000
--- a/tests/deploy_replicaset-trusty
+++ /dev/null
@@ -1,6 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_replicaset
4
5t = deploy_replicaset.Replicaset('trusty')
6t.run()
7\ No newline at end of file0\ No newline at end of file
diff --git a/tests/deploy_replicaset-xenial b/tests/deploy_replicaset-xenial
8deleted file mode 1007551deleted file mode 100755
index 4f142dd..0000000
--- a/tests/deploy_replicaset-xenial
+++ /dev/null
@@ -1,6 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_replicaset
4
5t = deploy_replicaset.Replicaset('xenial')
6t.run()
diff --git a/tests/deploy_replicaset.py b/tests/deploy_replicaset.py
7deleted file mode 1006440deleted file mode 100644
index 6fa0290..0000000
--- a/tests/deploy_replicaset.py
+++ /dev/null
@@ -1,150 +0,0 @@
1#!/usr/bin/env python3
2
3import amulet
4import logging
5import re
6import sys
7import time
8import traceback
9from pymongo import MongoClient
10from pymongo.errors import OperationFailure
11from collections import Counter
12
13from base_deploy import BasicMongo
14
15# max amount of time to wait before testing for replicaset status
16wait_for_replicaset = 600
17logger = logging.getLogger(__name__)
18
19
20class Replicaset(BasicMongo):
21 def __init__(self, series):
22 super(Replicaset, self).__init__(units=3,
23 series=series,
24 deploy_timeout=1800)
25
26 def _expect_replicaset_counts(self,
27 primaries_count,
28 secondaries_count,
29 time_between=10):
30 unit_status = []
31 tries = wait_for_replicaset / time_between
32
33 for service in self.sentry_dict:
34 addy = self.sentry_dict[service].info['public-address']
35 if ":" in addy:
36 addy = "[{}]".format(addy)
37 while True:
38 try:
39 client = MongoClient(addy)
40 r = client.admin.command('replSetGetStatus')
41 break
42 except OperationFailure as ex:
43 sys.stderr.write(
44 'OperationFailure, sleep and retry... to {}: {}\n'.
45 format(addy, ex))
46 tb_lines = traceback.format_exception(ex.__class__,
47 ex, ex.__traceback__)
48 tb_text = ''.join(tb_lines)
49 sys.stderr.write(tb_text)
50 tries = tries - 1
51 if tries < 0:
52 sys.stderr.write('retry limit caught, failing...\n')
53 break
54 time.sleep(time_between)
55 unit_status.append(r['myState'])
56 client.close()
57
58 primaries = Counter(unit_status)[1]
59 if primaries != primaries_count:
60 message = "Expected %d PRIMARY unit(s)! Found: %s %s" % (
61 primaries_count,
62 primaries,
63 unit_status)
64 amulet.raise_status(amulet.FAIL, message)
65
66 secondrs = Counter(unit_status)[2]
67 if secondrs != secondaries_count:
68 message = ("Expected %d secondary units! (Found %s) %s" %
69 (secondaries_count, secondrs, unit_status))
70 amulet.raise_status(amulet.FAIL, message)
71
72 def deploy(self):
73 self.d.add('mongodb', charm='mongodb', units=self.units)
74 self.d.expose('mongodb')
75 super(Replicaset, self).deploy()
76 self.wait_for_replicaset = 600
77
78 def validate_status_interface(self):
79 self.addy = self.d.sentry['mongodb'][0].info['public-address']
80 super(Replicaset, self).validate_status_interface()
81
82 def validate_replicaset_setup(self):
83 self.d.sentry.wait(self.deploy_timeout)
84 self._expect_replicaset_counts(1, 2)
85
86 def validate_replicaset_relation_joined(self):
87 self.d.add_unit('mongodb', units=2)
88 self.d.sentry.wait(wait_for_replicaset)
89 self.sentry_dict = {svc: self.d.sentry[svc]
90 for svc in list(self.d.sentry.unit)}
91 self._expect_replicaset_counts(1, 4)
92
93 def validate_world_connectivity(self):
94 # figuring out which unit is primary
95 primary = False
96 while not primary:
97 for unit in self.sentry_dict:
98 unit_address = self.sentry_dict[unit].info['public-address']
99 if ":" in unit_address:
100 unit_address = "[{}]".format(unit_address)
101 c = MongoClient(unit_address)
102 r = c.admin.command('replSetGetStatus')
103 if r['myState'] == 1:
104 # reusing address without possible brackets []
105 primary = self.sentry_dict[unit].info['public-address']
106 break
107 time.sleep(.1)
108
109 self.addy = primary
110 super(Replicaset, self).validate_world_connectivity()
111
112 def validate_running_services(self):
113 super(Replicaset, self).validate_running_services()
114
115 def validate_workload_status(self):
116 primaries = 0
117 secondaries = 0
118 regex = re.compile('^Unit is ready as (PRIMARY|SECONDARY)$')
119 self.d.sentry.wait_for_messages({'mongodb': regex})
120
121 # count how many primaries and secondaries were reported in the
122 # workload status
123 for unit_name, unit in self.d.sentry.get_status()['mongodb'].items():
124 workload_msg = unit['workload-status']['message']
125 matched = re.match(regex, workload_msg)
126
127 if not matched:
128 msg = "'{}' does not match '{}'".format(workload_msg, regex)
129 amulet.raise_status(amulet.FAIL, msg=msg)
130 elif matched.group(1) == 'PRIMARY':
131 primaries += 1
132 elif matched.group(1) == 'SECONDARY':
133 secondaries += 1
134 else:
135 amulet.raise_status(amulet.FAIL,
136 msg='Unknown state: %s' % matched.group(1))
137
138 logger.debug('Secondary units found: %d' % secondaries)
139 if primaries > 1:
140 msg = "Found %d primaries, expected 1" % primaries
141 amulet.raise_status(amulet.FAIL, msg=msg)
142
143 def run(self):
144 self.deploy()
145 self.validate_status_interface()
146 self.validate_running_services()
147 self.validate_replicaset_setup()
148 self.validate_replicaset_relation_joined()
149 self.validate_world_connectivity()
150 self.validate_workload_status()
diff --git a/tests/deploy_shard-trusty b/tests/deploy_shard-trusty
151deleted file mode 1007550deleted file mode 100755
index 200110f..0000000
--- a/tests/deploy_shard-trusty
+++ /dev/null
@@ -1,6 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_shard
4
5t = deploy_shard.ShardNode('trusty')
6t.run()
7\ No newline at end of file0\ No newline at end of file
diff --git a/tests/deploy_shard-xenial b/tests/deploy_shard-xenial
8deleted file mode 1007551deleted file mode 100755
index cb00363..0000000
--- a/tests/deploy_shard-xenial
+++ /dev/null
@@ -1,6 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_shard
4
5t = deploy_shard.ShardNode('xenial')
6t.run()
diff --git a/tests/deploy_shard.py b/tests/deploy_shard.py
7deleted file mode 1006440deleted file mode 100644
index d384ef4..0000000
--- a/tests/deploy_shard.py
+++ /dev/null
@@ -1,80 +0,0 @@
1#!/usr/bin/env python3
2
3import amulet
4
5from base_deploy import BasicMongo
6
7
8class ShardNode(BasicMongo):
9 def __init__(self, series):
10 super(ShardNode, self).__init__(units=1,
11 series=series,
12 deploy_timeout=900)
13
14 def deploy(self):
15 self.d.add('configsvr', charm='mongodb', units=self.units)
16 self.d.add('mongos', charm='mongodb', units=self.units)
17 self.d.add('shard1', charm='mongodb', units=self.units)
18 self.d.add('shard2', charm='mongodb', units=self.units)
19
20 # Setup the config svr
21 self.d.configure('configsvr', {'replicaset': 'configsvr'})
22
23 # define each shardset
24 self.d.configure('shard1', {'replicaset': 'shard1'})
25 self.d.configure('shard2', {'replicaset': 'shard2'})
26
27 self.d.configure('mongos', {})
28
29 # Connect the config servers to mongo shell
30 self.d.relate('configsvr:configsvr', 'mongos:mongos-cfg')
31
32 # connect each shard to the mongo shell
33 self.d.relate('mongos:mongos', 'shard1:database')
34 self.d.relate('mongos:mongos', 'shard2:database')
35 self.d.expose('configsvr')
36 self.d.expose('mongos')
37 super(ShardNode, self).deploy()
38
39 self.sentry_dict = {
40 'config-sentry': self.d.sentry['configsvr'][0],
41 'mongos-sentry': self.d.sentry['mongos'][0],
42 'shard1-sentry': self.d.sentry['shard1'][0],
43 'shard2-sentry': self.d.sentry['shard2'][0]
44 }
45
46 def validate_world_connectivity(self):
47 self.addy = self.d.sentry['mongos'][0].info['public-address']
48 super(ShardNode, self).validate_world_connectivity()
49
50 def validate_running_services(self):
51 super(ShardNode, self).validate_running_services()
52
53 def validate_status_interface(self):
54 self.addy = self.sentry_dict['config-sentry'].info['public-address']
55 super(ShardNode, self).validate_status_interface()
56
57 def validate_manual_connection(self):
58 fmt = "mongo {}"
59 addy = self.d.sentry['mongos'][0].info['public-address']
60 if ":" in addy:
61 fmt = "mongo --ipv6 {}:27017"
62 jujuruncmd = fmt.format(addy)
63 output, code = self.d.sentry['shard1'][0].run(jujuruncmd)
64 if code != 0:
65 msg = ("Manual Connection failed, unit shard1:{} code:{} cmd:{}"
66 .format(output, code, jujuruncmd))
67 amulet.raise_status(amulet.SKIP, msg=msg)
68
69 output, code = self.d.sentry['shard2'][0].run(jujuruncmd)
70 if code != 0:
71 msg = ("Manual Connection failed, unit shard2:{} code:{} cmd:{}"
72 .format(output, code, jujuruncmd))
73 amulet.raise_status(amulet.SKIP, msg=msg)
74
75 def run(self):
76 self.deploy()
77 self.validate_world_connectivity()
78 self.validate_status_interface()
79 self.validate_running_services()
80 self.validate_manual_connection()
diff --git a/tests/deploy_single-trusty b/tests/deploy_single-trusty
81deleted file mode 1007550deleted file mode 100755
index 59a2231..0000000
--- a/tests/deploy_single-trusty
+++ /dev/null
@@ -1,8 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_single
4
5t = deploy_single.SingleNode('trusty')
6t.run()
7
8
diff --git a/tests/deploy_single-xenial b/tests/deploy_single-xenial
9deleted file mode 1007550deleted file mode 100755
index 3f718d8..0000000
--- a/tests/deploy_single-xenial
+++ /dev/null
@@ -1,8 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_single
4
5t = deploy_single.SingleNode('xenial')
6t.run()
7
8
diff --git a/tests/deploy_single.py b/tests/deploy_single.py
9deleted file mode 1006440deleted file mode 100644
index c3181b1..0000000
--- a/tests/deploy_single.py
+++ /dev/null
@@ -1,23 +0,0 @@
1#!/usr/bin/env python3
2
3from base_deploy import BasicMongo
4
5
6class SingleNode(BasicMongo):
7 def __init__(self, series):
8 super(SingleNode, self).__init__(units=1,
9 series=series,
10 deploy_timeout=900)
11
12 def deploy(self):
13 self.d.add('mongodb', charm='mongodb', units=self.units)
14 self.d.expose('mongodb')
15 super(SingleNode, self).deploy()
16
17 def validate_world_connectivity(self):
18 self.addy = self.d.sentry['mongodb'][0].info['public-address']
19 super(SingleNode, self).validate_world_connectivity()
20
21 def run(self):
22 self.deploy()
23 self.validate_world_connectivity()
diff --git a/tests/deploy_with_ceilometer-trusty b/tests/deploy_with_ceilometer-trusty
24deleted file mode 1007550deleted file mode 100755
index b73d870..0000000
--- a/tests/deploy_with_ceilometer-trusty
+++ /dev/null
@@ -1,6 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_with_ceilometer
4
5t = deploy_with_ceilometer.TestCeilometer('trusty')
6t.run()
diff --git a/tests/deploy_with_ceilometer-xenial b/tests/deploy_with_ceilometer-xenial
7deleted file mode 1007550deleted file mode 100755
index 4678457..0000000
--- a/tests/deploy_with_ceilometer-xenial
+++ /dev/null
@@ -1,7 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_with_ceilometer
4
5#Not running this because of: https://launchpad.net/bugs/1656651
6#t = deploy_with_ceilometer.TestCeilometer('xenial')
7#t.run()
diff --git a/tests/deploy_with_ceilometer.py b/tests/deploy_with_ceilometer.py
8deleted file mode 1006440deleted file mode 100644
index 133eee3..0000000
--- a/tests/deploy_with_ceilometer.py
+++ /dev/null
@@ -1,36 +0,0 @@
1#!/usr/bin/env python3
2
3import amulet
4
5from base_deploy import BasicMongo
6
7
8class TestCeilometer(BasicMongo):
9 def __init__(self, series):
10 super(TestCeilometer, self).__init__(units=1, series=series,
11 deploy_timeout=900)
12
13 def deploy(self):
14 self.d.add('mongodb', charm='mongodb', units=self.units)
15 self.d.add('ceilometer', 'cs:{}/ceilometer'.format(self.series))
16 self.d.relate('mongodb:database', 'ceilometer:shared-db')
17 self.d.expose('mongodb')
18 super(TestCeilometer, self).deploy()
19
20 def validate_world_connectivity(self):
21 self.addy = self.d.sentry['mongodb'][0].info['public-address']
22 super(TestCeilometer, self).validate_world_connectivity()
23
24 def validate_mongo_relation(self):
25 unit = self.d.sentry['ceilometer'][0]
26 mongo = self.d.sentry['mongodb'][0].info['public-address']
27 mongo_reladdr = self.d.sentry['mongodb'][0].relation(
28 'database', 'ceilometer:shared-db')
29 cont = unit.file_contents('/etc/ceilometer/ceilometer.conf')
30 if (mongo not in cont and mongo_reladdr.get(
31 'hostname', 'I SURE HOPE NOT') not in cont):
32 amulet.raise_status(amulet.FAIL, "Unable to verify ceilometer cfg")
33
34 def run(self):
35 self.deploy()
36 self.validate_world_connectivity()
diff --git a/tests/deploy_with_storage-trusty b/tests/deploy_with_storage-trusty
37deleted file mode 1007550deleted file mode 100755
index f7e8314..0000000
--- a/tests/deploy_with_storage-trusty
+++ /dev/null
@@ -1,6 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_with_storage
4
5t = deploy_with_storage.WithStorage('trusty')
6t.run()
diff --git a/tests/deploy_with_storage-xenial b/tests/deploy_with_storage-xenial
7deleted file mode 1007550deleted file mode 100755
index 6b0d660..0000000
--- a/tests/deploy_with_storage-xenial
+++ /dev/null
@@ -1,8 +0,0 @@
1#!/usr/bin/env python3
2
3import deploy_with_storage
4
5# We are not testing this against xenial yet, because
6# cs:~chris-gondolin/trusty/storage-5 does not exist for xenial (yet)
7#t = deploy_with_storage.WithStorage('xenial')
8#t.run()
diff --git a/tests/deploy_with_storage.py b/tests/deploy_with_storage.py
9deleted file mode 1006440deleted file mode 100644
index 92a5fe6..0000000
--- a/tests/deploy_with_storage.py
+++ /dev/null
@@ -1,74 +0,0 @@
1#!/usr/bin/env python3
2
3from base_deploy import BasicMongo
4
5import amulet
6from pymongo import MongoClient
7from collections import Counter
8
9
10class WithStorage(BasicMongo):
11 def __init__(self, series):
12 super(WithStorage, self).__init__(units=2,
13 series=series,
14 deploy_timeout=1800)
15
16 def deploy(self):
17 self.d.add('mongodb',
18 charm='mongodb',
19 units=self.units,
20 constraints={'root-disk': '20480M'})
21
22 storage_charm = 'cs:~chris-gondolin/{}/storage-5'.format(self.series)
23 self.d.add('storage', charm=storage_charm, series=self.series)
24 self.d.configure('storage', {'provider': 'local'})
25 super(WithStorage, self).deploy()
26 self.d.expose('mongodb')
27
28 ordered_units = sorted(self.d.sentry['mongodb'],
29 key=lambda u: u.info['unit'])
30 self.sentry_dict = {
31 'mongodb0-sentry': ordered_units[0],
32 'mongodb1-sentry': ordered_units[1]
33 }
34
35 def validate_status(self):
36 self.d.sentry.wait_for_status(self.d.juju_env, ['mongodb'])
37
38 def validate_replicaset_setup(self):
39 self.d.sentry.wait(self.deploy_timeout)
40
41 unit_status = []
42 for service in self.sentry_dict:
43 addy = self.sentry_dict[service].info['public-address']
44 if ":" in addy:
45 addy = "[{}]".format(addy)
46 client = MongoClient(addy)
47 r = client.admin.command('replSetGetStatus')
48 unit_status.append(r['myState'])
49 client.close()
50
51 prims = Counter(unit_status)[1]
52 if prims != 1:
53 message = "Only one PRIMARY unit allowed! Found: %s" % (prims)
54 amulet.raise_status(amulet.FAIL, message)
55
56 secnds = Counter(unit_status)[2]
57 if secnds != 1:
58 message = "Only one SECONDARY unit allowed! (Found %s)" % (secnds)
59 amulet.raise_status(amulet.FAIL, message)
60
61 def run(self):
62 self.deploy()
63 self.validate_status()
64 self.validate_replicaset_setup()
65
66 print("Adding storage relation, and sleeping for 2 min.")
67 try:
68 self.d.relate('mongodb:data', 'storage:data')
69 except OSError as e:
70 print("Ignoring error: {}", e)
71 self.d.sentry.wait(120) # 2 minute
72
73 self.validate_status()
74 self.validate_replicaset_setup()
diff --git a/tests/test_requirements.txt b/tests/test_requirements.txt
75new file mode 1006440new file mode 100644
index 0000000..9fbeeab
--- /dev/null
+++ b/tests/test_requirements.txt
@@ -0,0 +1,2 @@
1git+https://github.com/openstack-charmers/zaza.git#egg=zaza
2pymongo
diff --git a/tests/tests.yaml b/tests/tests.yaml
0new file mode 1006443new file mode 100644
index 0000000..dcbd01c
--- /dev/null
+++ b/tests/tests.yaml
@@ -0,0 +1,17 @@
1charm_name: mongodb-charm
2tests:
3 - model_alias_xenial:
4 - tests.tests_mongodb.BasicMongodbCharmTest
5 - tests.tests_mongodb.ReplicatedMongodbCharmTest
6 - tests.tests_mongodb.XenialMongodbCharmTest
7 - model_alias_bionic:
8 - tests.tests_mongodb.BasicMongodbCharmTest
9 - tests.tests_mongodb.ReplicatedMongodbCharmTest
10 - model_alias_shard:
11 - tests.tests_mongodb.ShardedMongodbCharmTest
12gate_bundles:
13 - model_alias_xenial: xenial
14 - model_alias_bionic: bionic
15 - model_alias_shard: bionic-shard
16smoke_bundles:
17 - model_alias_bionic: bionic
diff --git a/tests/tests_mongodb.py b/tests/tests_mongodb.py
0new file mode 10064418new file mode 100644
index 0000000..dc50d8d
--- /dev/null
+++ b/tests/tests_mongodb.py
@@ -0,0 +1,137 @@
1#!/usr/bin/env python3
2import requests
3import unittest
4
5from pymongo import MongoClient
6from requests.adapters import HTTPAdapter
7from requests.packages.urllib3.util.retry import Retry
8from zaza import model
9from zaza.charm_lifecycle import utils as lifecycle_utils
10
11
12MONGO_STARTUP = 0
13MONGO_PRIMARY = 1
14MONGO_SECONDARY = 2
15MONGO_RECOVERING = 3
16MONGO_FATAL = 4
17MONGO_STARTUP2 = 5
18MONGO_UNKNOWN = 6
19MONGO_ARBITER = 7
20MONGO_DOWN = 8
21MONGO_ROLLBACK = 9
22MONGO_REMOVED = 10
23
24
25def requests_retry_session(
26 retries=3, backoff_factor=2, status_forcelist=(500, 502, 504), session=None
27):
28 """Create a http session with retry"""
29 session = session or requests.Session()
30 retry = Retry(
31 total=retries,
32 read=retries,
33 connect=retries,
34 backoff_factor=backoff_factor,
35 status_forcelist=status_forcelist,
36 )
37 adapter = HTTPAdapter(max_retries=retry)
38 session.mount("http://", adapter)
39 session.mount("https://", adapter)
40 return session
41
42
43class MongodbCharmTestBase(unittest.TestCase):
44 @classmethod
45 def setUpClass(cls):
46 cls.model_name = model.get_juju_model()
47 cls.test_config = lifecycle_utils.get_charm_config()
48 model.block_until_all_units_idle()
49 addr = model.get_lead_unit_ip("mongodb")
50 if ":" in addr: # ipv6 formatting
51 cls.leader_address = "[{}]".format(addr)
52 else:
53 cls.leader_address = addr
54 cls.db_client = MongoClient(cls.leader_address)
55
56 def cat_unit(self, unit, path):
57 unit_res = model.run_on_unit(unit, "sudo cat {}".format(path))
58 return unit_res["Stdout"]
59
60 def web_admin_interface(self, ipaddr, port=28017):
61 url = "http://{}:{}".format(ipaddr, port)
62 resp = requests_retry_session(retries=10).get(url)
63 return resp
64
65
66class BasicMongodbCharmTest(MongodbCharmTestBase):
67 def test_db_insert(self):
68 """Test if we can insert and remove a value"""
69 test_db = self.db_client.test_db
70 insert_id = test_db.testcoll.insert({"assert": True})
71 self.assertTrue(insert_id is not None, "Failed to insert test data")
72 result = test_db.testcoll.remove(insert_id)
73 self.assertTrue("err" not in result, "Failed to remove test data")
74
75 def test_service_running(self):
76 """Test if we have a mongod running on all units"""
77 for i in (0, 1, 2):
78 running_for = model.get_unit_service_start_time(
79 "mongodb/{}".format(i), "mongod", timeout=20
80 )
81 self.assertGreater(running_for, 0)
82
83
84class XenialMongodbCharmTest(MongodbCharmTestBase):
85 def test_status_interface(self):
86 """Check if we can access the web admin port -- xenial only"""
87 resp = self.web_admin_interface(self.leader_address)
88 resp.raise_for_status()
89
90
91class ReplicatedMongodbCharmTest(MongodbCharmTestBase):
92 def get_set_status(self):
93 unit_status = []
94 for addr in model.get_app_ips("mongodb"):
95 unit_client = MongoClient(addr)
96 unit_status.append(unit_client.admin.command("replSetGetStatus"))
97 return unit_status
98
99 def test_replset_numbers(self):
100 """Test if we have 1 primary and 2 secondary mongodbs"""
101 unit_status = self.get_set_status()
102 primaries = [u for u in unit_status if u["myState"] == MONGO_PRIMARY]
103 secondaries = [u for u in unit_status if u["myState"] == MONGO_SECONDARY]
104 self.assertEqual(len(primaries), 1)
105 self.assertEqual(len(secondaries), 2)
106
107 def test_replset_consistent_members(self):
108 """Test if all units have the same view on membership"""
109 unit_status = self.get_set_status()
110 prim_members = [u for u in unit_status if u["myState"] == MONGO_PRIMARY][0][
111 "members"
112 ]
113 secondary_members = [
114 u["members"] for u in unit_status if u["myState"] == MONGO_SECONDARY
115 ]
116
117 def extract(member_dict):
118 # Extract a subset of membership info as a frozenset. Name is ipaddr:port, health a float and stateStr a str
119 return frozenset(
120 v for k, v in member_dict.items() if k in ["name", "health", "stateStr"]
121 )
122
123 ref = set(
124 map(extract, prim_members)
125 ) # Our reference view on membership comes from the primary
126 secondaries = [set(map(extract, sec)) for sec in secondary_members]
127 for sec in secondaries:
128 self.assertEqual(ref, sec)
129
130
131class ShardedMongodbCharmTest(MongodbCharmTestBase):
132 def test_mongos_running(self):
133 """Test if the mongos service is running"""
134 running_for = model.get_unit_service_start_time(
135 "mongodb/0", "mongos", timeout=20
136 )
137 self.assertGreater(running_for, 0)
diff --git a/tox.ini b/tox.ini
0new file mode 100644138new file mode 100644
index 0000000..6e3a650
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,30 @@
1[tox]
2skipsdist=True
3envlist = unit, functional, lint
4skip_missing_interpreters = True
5
6[testenv]
7setenv =
8 PYTHONPATH = .
9passenv =
10 HOME
11 JUJU_REPOSITORY
12 MODEL_SETTINGS
13
14[testenv:unit]
15basepython = python2
16commands =
17 nosetests -s --nologcapture --with-coverage unit_tests/ actions/
18deps = -r{toxinidir}/test_requirements.txt
19
20[testenv:functional]
21basepython = python3
22commands =
23 functest-run-suite --keep-model
24deps = -r{toxinidir}/tests/test_requirements.txt
25
26[testenv:func-smoke]
27basepython = python3
28commands =
29 functest-run-suite --keep-model --smoke
30deps = -r{toxinidir}/tests/test_requirements.txt
diff --git a/unit_tests/test_hooks.py b/unit_tests/test_hooks.py
index b44d11f..c6e3c22 100644
--- a/unit_tests/test_hooks.py
+++ b/unit_tests/test_hooks.py
@@ -187,8 +187,8 @@ class MongoHooksTest(CharmTestCase):
187 @patch('time.sleep')187 @patch('time.sleep')
188 def test_am_i_primary(self, mock_sleep, mock_mongo_client,188 def test_am_i_primary(self, mock_sleep, mock_mongo_client,
189 mock_run_admin_cmd):189 mock_run_admin_cmd):
190 mock_run_admin_cmd.side_effect = [{'myState': x} for x in xrange(5)]190 mock_run_admin_cmd.side_effect = [{'myState': x} for x in range(5)]
191 expected_results = [True if x == 1 else False for x in xrange(5)]191 expected_results = [True if x == 1 else False for x in range(5)]
192192
193 # Check expected return values each time...193 # Check expected return values each time...
194 for exp in expected_results:194 for exp in expected_results:
@@ -203,7 +203,7 @@ class MongoHooksTest(CharmTestCase):
203 mock_run_admin_cmd):203 mock_run_admin_cmd):
204 msg = 'replSetInitiate - should come online shortly'204 msg = 'replSetInitiate - should come online shortly'
205 mock_run_admin_cmd.side_effect = [OperationFailure(msg)205 mock_run_admin_cmd.side_effect = [OperationFailure(msg)
206 for x in xrange(10)]206 for x in range(10)]
207207
208 try:208 try:
209 hooks.am_i_primary()209 hooks.am_i_primary()
@@ -262,7 +262,7 @@ class MongoHooksTest(CharmTestCase):
262 def test_mongo_client_smart_error_cases(self, mock_ck_output, mock_sleep):262 def test_mongo_client_smart_error_cases(self, mock_ck_output, mock_sleep):
263 mock_ck_output.side_effect = [CalledProcessError(1, 'cmd',263 mock_ck_output.side_effect = [CalledProcessError(1, 'cmd',
264 output='fake-error')264 output='fake-error')
265 for x in xrange(11)]265 for x in range(11)]
266 rv = hooks.mongo_client_smart(command='fake-cmd')266 rv = hooks.mongo_client_smart(command='fake-cmd')
267 self.assertFalse(rv)267 self.assertFalse(rv)
268268

Subscribers

People subscribed via source and target branches

to all changes: