diff options
| author | Peter Sabaini <peter.sabaini@canonical.com> | 2020-04-16 16:28:38 +0000 |
|---|---|---|
| committer | Canonical IS Mergebot <canonical-is-mergebot@canonical.com> | 2020-04-16 16:28:38 +0000 |
| commit | ed31d71d1f45df6027bdcf8cd7ea760b9eced5d5 (patch) | |
| tree | f1821553fac41b5d16366bed34817a0587775411 | |
| parent | 67a8a1bf09526bccf7bff692b2dd418c1a27fe65 (diff) | |
| parent | d39567042500595199b43817df89ace3c1f393f5 (diff) | |
Functional testing update
Reviewed-on: https://code.launchpad.net/~peter-sabaini/charm-mongodb/+git/charm-mongodb/+merge/382331 Reviewed-by: Adam Dyess <adam.dyess@canonical.com> Reviewed-by: Paul Goins <paul.goins@canonical.com>
29 files changed, 269 insertions, 562 deletions
@@ -3,8 +3,9 @@ .pydevproject .coverage .settings +.idea/ *.pyc -.venv/ +.tox/ bin/* scripts/charm-helpers-sync.py exec.d/* @@ -20,6 +20,7 @@ clean: rm -f .coverage find . -name '*.pyc' -delete rm -rf .venv + rm -rf .tox (which dh_clean && dh_clean) || true .venv: @@ -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 # rm the python2 venv from unittests as it fails the juju deploy + @tox -e functional sync: @mkdir -p bin @@ -48,3 +49,6 @@ sync: publish: lint unit_test bzr push lp:charms/mongodb bzr push lp:charms/trusty/mongodb + +# The targets below don't depend on a file +.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(): def backup_command(cmd, args, dir): try: mkdir(dir) - except OSError, e: + except OSError as e: pass # Ignoring, the directory already exists - except Exception, e: + except Exception as e: action_set({"directory creation exception": e}) action_fail(str(e)) return @@ -48,10 +48,10 @@ def backup_command(cmd, args, dir): try: output = execute(command, dir) action_set({"output": output}) - except subprocess.CalledProcessError, e: + except subprocess.CalledProcessError as e: action_set({"error_code": e.returncode, "exception": e, "output": e.output}) action_fail(str(e)) - except Exception, e: + except Exception as e: action_set({"exception": e}) 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'): s.shutdown(socket.SHUT_RDWR) juju_log("port_check: %s:%s/%s is open" % (host, port, protocol)) return(True) - except Exception, e: + except Exception as e: juju_log("port_check: Unable to connect to %s:%s/%s." % (host, port, protocol)) juju_log("port_check: Exception: %s" % str(e)) @@ -195,7 +195,7 @@ def update_file(filename=None, new_data=None, old_data=None): with open(filename, 'w') as f: f.write(new_data) retVal = True - except Exception, e: + except Exception as e: juju_log(str(e)) retVal = False finally: @@ -211,7 +211,7 @@ def process_check(pid=None): else: juju_log("process_check: pid not defined.") retVal = (None, None) - except Exception, e: + except Exception as e: juju_log("process_check exception: %s" % str(e)) retVal = (None, None) finally: @@ -490,7 +490,7 @@ def mongo_client_smart(host='localhost', command=None): '--eval', 'printjson(%s)' % command] juju_log("mongo_client_smart executing: %s" % str(cmd_line), level=DEBUG) - for i in xrange(MONGO_CLIENT_RETRIES): + for i in range(MONGO_CLIENT_RETRIES): try: cmd_output = subprocess.check_output(cmd_line) juju_log('mongo_client_smart executed, output: %s' % @@ -614,7 +614,7 @@ def enable_replset(replicaset_name=None): juju_log('enable_replset will return: %s' % str(retVal), level=DEBUG) - except Exception, e: + except Exception as e: juju_log(str(e), level=WARNING) retVal = False finally: @@ -632,7 +632,7 @@ def remove_replset_from_upstart(): mongodb_init_config = re.sub(r' --replSet .\w+', '', mongodb_init_config) retVal = update_file(default_mongodb_init_config, mongodb_init_config) - except Exception, e: + except Exception as e: juju_log(str(e)) retVal = False finally: @@ -643,7 +643,7 @@ def step_down_replset_primary(): """Steps down the primary """ retVal = mongo_client('localhost', 'rs.stepDown()') - for i in xrange(MONGO_CLIENT_RETRIES): + for i in range(MONGO_CLIENT_RETRIES): if not am_i_primary(): juju_log("step_down_replset_primary returns: %s" % retVal, level=DEBUG) @@ -665,7 +665,7 @@ def remove_rest_from_upstart(): mongodb_init_config = regex_sub([(' --rest ', ' ')], mongodb_init_config) retVal = update_file(default_mongodb_init_config, mongodb_init_config) - except Exception, e: + except Exception as e: juju_log(str(e)) retVal = False finally: @@ -743,7 +743,7 @@ def disable_configsvr(port=None): os.kill(int(pid), signal.SIGTERM) os.unlink('/var/run/mongodb/configsvr.pid') retVal = True - except Exception, e: + except Exception as e: juju_log('no config server running ...') juju_log("Exception: %s" % str(e)) retVal = False @@ -835,7 +835,7 @@ def disable_mongos(port=None): os.kill(int(pid), signal.SIGTERM) os.unlink('/var/run/mongodb/mongos.pid') retVal = True - except Exception, e: + except Exception as e: juju_log('no mongo router running ...') juju_log("Exception: %s" % str(e)) retVal = False @@ -948,7 +948,7 @@ def backup_cronjob(disable=False): with open(script_filename, 'w') as output: output.writelines(rendered) - chmod(script_filename, 0755) + chmod(script_filename, 0o755) juju_log('Installing cron.d/mongodb') @@ -1085,7 +1085,7 @@ def config_changed(): # update config-server information and port try: (configsvr_pid, configsvr_cmd_line) = configsvr_status() - except Exception, e: + except Exception as e: configsvr_pid = None configsvr_cmd_line = None juju_log("config_changed: configsvr_status failed.") @@ -1101,7 +1101,7 @@ def config_changed(): # update mongos information and port try: (mongos_pid, mongos_cmd_line) = mongos_status() - except Exception, e: + except Exception as e: mongos_pid = None mongos_cmd_line = None juju_log("config_changed: mongos_status failed.") @@ -1137,7 +1137,7 @@ def stop_hook(): retVal = service('stop', 'mongodb') os.remove('/var/lib/mongodb/mongod.lock') # FIXME Need to check if this is still needed - except Exception, e: + except Exception as e: juju_log(str(e)) retVal = False finally: @@ -1227,7 +1227,7 @@ def rs_add(host): juju_log("Executing: %s" % cmd_line, level=DEBUG) run(cmd_line) - for i in xrange(MONGO_CLIENT_RETRIES): + for i in range(MONGO_CLIENT_RETRIES): c = MongoClient('localhost') subprocess.check_output(cmd_line) r = run_admin_command(c, 'replSetGetStatus') @@ -1243,7 +1243,7 @@ def rs_add(host): def am_i_primary(): c = MongoClient('localhost') - for i in xrange(10): + for i in range(10): try: r = run_admin_command(c, 'replSetGetStatus') pretty_r = pprint.pformat(r) @@ -1310,7 +1310,7 @@ def get_mongod_version(): Mainly used for application_set_version in config-changed hook """ - c = MongoClient('localhost') + c = MongoClient('localhost', serverSelectionTimeoutMS=60000) return c.server_info()['version'] @@ -1612,7 +1612,7 @@ def run(command, exit_on_error=True): juju_log(command) return subprocess.check_output( command, stderr=subprocess.STDOUT, shell=True) - except subprocess.CalledProcessError, e: + except subprocess.CalledProcessError as e: juju_log("status=%d, output=%s" % (e.returncode, e.output)) if exit_on_error: sys.exit(e.returncode) diff --git a/tests/00_setup.sh b/tests/00_setup.sh deleted file mode 100755 index 4f58709..0000000 --- a/tests/00_setup.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -e - -sudo apt-get install python-setuptools -y -sudo add-apt-repository ppa:juju/stable -y - -sudo apt-get update -sudo 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 deleted file mode 100644 index b136cca..0000000 --- a/tests/base_deploy.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 - -import amulet -import requests -import sys -import time -import traceback -from pymongo import MongoClient - - -class BasicMongo(object): - def __init__(self, units, series, deploy_timeout): - self.units = units - self.series = series - self.deploy_timeout = deploy_timeout - self.d = amulet.Deployment(series=self.series) - self.addy = None - - def deploy(self): - try: - self.d.setup(self.deploy_timeout) - self.d.sentry.wait(self.deploy_timeout) - except amulet.helpers.TimeoutError: - message = 'The environment did not setup in %d seconds.', - self.deploy_timeout - amulet.raise_status(amulet.SKIP, msg=message) - - self.sentry_dict = {svc: self.d.sentry[svc] - for svc in list(self.d.sentry.unit)} - - def validate_status_interface(self): - addy = self.addy - fmt = "http://{}:28017" - if ":" in addy: - fmt = "http://[{}]:28017" - - time_between = 10 - tries = self.deploy_timeout / time_between - - try: - r = requests.get(fmt.format(addy), verify=False) - r.raise_for_status() - except requests.exception.ConnectionError as ex: - sys.stderr.write( - 'Connection error, sleep and retry... to {}: {}\n'. - format(addy, ex)) - tb_lines = traceback.format_exception(ex.__class__, - ex, ex.__traceback__) - tb_text = ''.join(tb_lines) - sys.stderr.write(tb_text) - tries = tries - 1 - if tries < 0: - sys.stderr.write('retry limit caught, failing...\n') - time.sleep(time_between) - - def validate_world_connectivity(self): - addy = self.addy - # ipv6 proper formating - if ":" in addy: - addy = "[{}]".format(addy) - - client = MongoClient(addy) - db = client['test'] - - # Can we successfully insert? - insert_id = db.amulet.insert({'assert': True}) - if insert_id is None: - amulet.raise_status(amulet.FAIL, msg="Failed to insert test data") - - # Can we delete from a shard using the Mongos hub? - result = db.amulet.remove(insert_id) - if 'err' in result and result['err'] is not None: - amulet.raise_status(amulet.FAIL, msg="Failed to remove test data") - - def validate_running_services(self): - for service in self.sentry_dict: - grep_command = 'grep RELEASE /etc/lsb-release' - release = self.sentry_dict[service].run(grep_command) - release = str(release).split('=')[1] - if release >= '15.10': - status_string = 'active (running)' # systemd - else: - status_string = 'mongodb start/running' # upstart - - output = self.sentry_dict[service].run('service mongodb status') - service_active = str(output).find(status_string) - if service_active == -1: - message = "Failed to find running MongoDB on host {}".format( - service) - amulet.raise_status(amulet.SKIP, msg=message) diff --git a/tests/bundles/bionic-shard.yaml b/tests/bundles/bionic-shard.yaml new file mode 100644 index 0000000..b7ee354 --- /dev/null +++ b/tests/bundles/bionic-shard.yaml @@ -0,0 +1,27 @@ +series: bionic +description: "mongodb-charm test bundle" +applications: + configsvr: + charm: "../../." + num_units: 1 + options: + replicaset: configsvr + mongodb: + charm: "../../." + num_units: 1 + options: + replicaset: testset + shard1: + charm: "../../." + num_units: 1 + options: + replicaset: shard1 + shard2: + charm: "../../." + num_units: 1 + options: + replicaset: shard2 +relations: + - [ "configsvr:configsvr", "mongodb:mongos-cfg" ] + - [ "mongodb:mongos", "shard1:database" ] + - [ "mongodb:mongos", "shard2:database" ] diff --git a/tests/bundles/bionic.yaml b/tests/bundles/bionic.yaml new file mode 100644 index 0000000..ebfb98e --- /dev/null +++ b/tests/bundles/bionic.yaml @@ -0,0 +1,9 @@ +series: bionic +description: "mongodb-charm test bundle" +applications: + mongodb: + charm: "../../." + num_units: 3 + options: + replicaset: testset + backup_directory: /var/backups diff --git a/tests/bundles/xenial.yaml b/tests/bundles/xenial.yaml new file mode 100644 index 0000000..9d29342 --- /dev/null +++ b/tests/bundles/xenial.yaml @@ -0,0 +1,9 @@ +series: xenial +description: "mongodb-charm test bundle" +applications: + mongodb: + charm: "../../." + num_units: 3 + options: + replicaset: testset + backup_directory: /var/backups diff --git a/tests/deploy_replicaset-trusty b/tests/deploy_replicaset-trusty deleted file mode 100755 index 9ed77a8..0000000 --- a/tests/deploy_replicaset-trusty +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_replicaset - -t = deploy_replicaset.Replicaset('trusty') -t.run() \ No newline at end of file diff --git a/tests/deploy_replicaset-xenial b/tests/deploy_replicaset-xenial deleted file mode 100755 index 4f142dd..0000000 --- a/tests/deploy_replicaset-xenial +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_replicaset - -t = deploy_replicaset.Replicaset('xenial') -t.run() diff --git a/tests/deploy_replicaset.py b/tests/deploy_replicaset.py deleted file mode 100644 index 6fa0290..0000000 --- a/tests/deploy_replicaset.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 - -import amulet -import logging -import re -import sys -import time -import traceback -from pymongo import MongoClient -from pymongo.errors import OperationFailure -from collections import Counter - -from base_deploy import BasicMongo - -# max amount of time to wait before testing for replicaset status -wait_for_replicaset = 600 -logger = logging.getLogger(__name__) - - -class Replicaset(BasicMongo): - def __init__(self, series): - super(Replicaset, self).__init__(units=3, - series=series, - deploy_timeout=1800) - - def _expect_replicaset_counts(self, - primaries_count, - secondaries_count, - time_between=10): - unit_status = [] - tries = wait_for_replicaset / time_between - - for service in self.sentry_dict: - addy = self.sentry_dict[service].info['public-address'] - if ":" in addy: - addy = "[{}]".format(addy) - while True: - try: - client = MongoClient(addy) - r = client.admin.command('replSetGetStatus') - break - except OperationFailure as ex: - sys.stderr.write( - 'OperationFailure, sleep and retry... to {}: {}\n'. - format(addy, ex)) - tb_lines = traceback.format_exception(ex.__class__, - ex, ex.__traceback__) - tb_text = ''.join(tb_lines) - sys.stderr.write(tb_text) - tries = tries - 1 - if tries < 0: - sys.stderr.write('retry limit caught, failing...\n') - break - time.sleep(time_between) - unit_status.append(r['myState']) - client.close() - - primaries = Counter(unit_status)[1] - if primaries != primaries_count: - message = "Expected %d PRIMARY unit(s)! Found: %s %s" % ( - primaries_count, - primaries, - unit_status) - amulet.raise_status(amulet.FAIL, message) - - secondrs = Counter(unit_status)[2] - if secondrs != secondaries_count: - message = ("Expected %d secondary units! (Found %s) %s" % - (secondaries_count, secondrs, unit_status)) - amulet.raise_status(amulet.FAIL, message) - - def deploy(self): - self.d.add('mongodb', charm='mongodb', units=self.units) - self.d.expose('mongodb') - super(Replicaset, self).deploy() - self.wait_for_replicaset = 600 - - def validate_status_interface(self): - self.addy = self.d.sentry['mongodb'][0].info['public-address'] - super(Replicaset, self).validate_status_interface() - - def validate_replicaset_setup(self): - self.d.sentry.wait(self.deploy_timeout) - self._expect_replicaset_counts(1, 2) - - def validate_replicaset_relation_joined(self): - self.d.add_unit('mongodb', units=2) - self.d.sentry.wait(wait_for_replicaset) - self.sentry_dict = {svc: self.d.sentry[svc] - for svc in list(self.d.sentry.unit)} - self._expect_replicaset_counts(1, 4) - - def validate_world_connectivity(self): - # figuring out which unit is primary - primary = False - while not primary: - for unit in self.sentry_dict: - unit_address = self.sentry_dict[unit].info['public-address'] - if ":" in unit_address: - unit_address = "[{}]".format(unit_address) - c = MongoClient(unit_address) - r = c.admin.command('replSetGetStatus') - if r['myState'] == 1: - # reusing address without possible brackets [] - primary = self.sentry_dict[unit].info['public-address'] - break - time.sleep(.1) - - self.addy = primary - super(Replicaset, self).validate_world_connectivity() - - def validate_running_services(self): - super(Replicaset, self).validate_running_services() - - def validate_workload_status(self): - primaries = 0 - secondaries = 0 - regex = re.compile('^Unit is ready as (PRIMARY|SECONDARY)$') - self.d.sentry.wait_for_messages({'mongodb': regex}) - - # count how many primaries and secondaries were reported in the - # workload status - for unit_name, unit in self.d.sentry.get_status()['mongodb'].items(): - workload_msg = unit['workload-status']['message'] - matched = re.match(regex, workload_msg) - - if not matched: - msg = "'{}' does not match '{}'".format(workload_msg, regex) - amulet.raise_status(amulet.FAIL, msg=msg) - elif matched.group(1) == 'PRIMARY': - primaries += 1 - elif matched.group(1) == 'SECONDARY': - secondaries += 1 - else: - amulet.raise_status(amulet.FAIL, - msg='Unknown state: %s' % matched.group(1)) - - logger.debug('Secondary units found: %d' % secondaries) - if primaries > 1: - msg = "Found %d primaries, expected 1" % primaries - amulet.raise_status(amulet.FAIL, msg=msg) - - def run(self): - self.deploy() - self.validate_status_interface() - self.validate_running_services() - self.validate_replicaset_setup() - self.validate_replicaset_relation_joined() - self.validate_world_connectivity() - self.validate_workload_status() diff --git a/tests/deploy_shard-trusty b/tests/deploy_shard-trusty deleted file mode 100755 index 200110f..0000000 --- a/tests/deploy_shard-trusty +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_shard - -t = deploy_shard.ShardNode('trusty') -t.run() \ No newline at end of file diff --git a/tests/deploy_shard-xenial b/tests/deploy_shard-xenial deleted file mode 100755 index cb00363..0000000 --- a/tests/deploy_shard-xenial +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_shard - -t = deploy_shard.ShardNode('xenial') -t.run() diff --git a/tests/deploy_shard.py b/tests/deploy_shard.py deleted file mode 100644 index d384ef4..0000000 --- a/tests/deploy_shard.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 - -import amulet - -from base_deploy import BasicMongo - - -class ShardNode(BasicMongo): - def __init__(self, series): - super(ShardNode, self).__init__(units=1, - series=series, - deploy_timeout=900) - - def deploy(self): - self.d.add('configsvr', charm='mongodb', units=self.units) - self.d.add('mongos', charm='mongodb', units=self.units) - self.d.add('shard1', charm='mongodb', units=self.units) - self.d.add('shard2', charm='mongodb', units=self.units) - - # Setup the config svr - self.d.configure('configsvr', {'replicaset': 'configsvr'}) - - # define each shardset - self.d.configure('shard1', {'replicaset': 'shard1'}) - self.d.configure('shard2', {'replicaset': 'shard2'}) - - self.d.configure('mongos', {}) - - # Connect the config servers to mongo shell - self.d.relate('configsvr:configsvr', 'mongos:mongos-cfg') - - # connect each shard to the mongo shell - self.d.relate('mongos:mongos', 'shard1:database') - self.d.relate('mongos:mongos', 'shard2:database') - self.d.expose('configsvr') - self.d.expose('mongos') - super(ShardNode, self).deploy() - - self.sentry_dict = { - 'config-sentry': self.d.sentry['configsvr'][0], - 'mongos-sentry': self.d.sentry['mongos'][0], - 'shard1-sentry': self.d.sentry['shard1'][0], - 'shard2-sentry': self.d.sentry['shard2'][0] - } - - def validate_world_connectivity(self): - self.addy = self.d.sentry['mongos'][0].info['public-address'] - super(ShardNode, self).validate_world_connectivity() - - def validate_running_services(self): - super(ShardNode, self).validate_running_services() - - def validate_status_interface(self): - self.addy = self.sentry_dict['config-sentry'].info['public-address'] - super(ShardNode, self).validate_status_interface() - - def validate_manual_connection(self): - fmt = "mongo {}" - addy = self.d.sentry['mongos'][0].info['public-address'] - if ":" in addy: - fmt = "mongo --ipv6 {}:27017" - jujuruncmd = fmt.format(addy) - output, code = self.d.sentry['shard1'][0].run(jujuruncmd) - if code != 0: - msg = ("Manual Connection failed, unit shard1:{} code:{} cmd:{}" - .format(output, code, jujuruncmd)) - amulet.raise_status(amulet.SKIP, msg=msg) - - output, code = self.d.sentry['shard2'][0].run(jujuruncmd) - if code != 0: - msg = ("Manual Connection failed, unit shard2:{} code:{} cmd:{}" - .format(output, code, jujuruncmd)) - amulet.raise_status(amulet.SKIP, msg=msg) - - def run(self): - self.deploy() - self.validate_world_connectivity() - self.validate_status_interface() - self.validate_running_services() - self.validate_manual_connection() diff --git a/tests/deploy_single-trusty b/tests/deploy_single-trusty deleted file mode 100755 index 59a2231..0000000 --- a/tests/deploy_single-trusty +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_single - -t = deploy_single.SingleNode('trusty') -t.run() - - diff --git a/tests/deploy_single-xenial b/tests/deploy_single-xenial deleted file mode 100755 index 3f718d8..0000000 --- a/tests/deploy_single-xenial +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_single - -t = deploy_single.SingleNode('xenial') -t.run() - - diff --git a/tests/deploy_single.py b/tests/deploy_single.py deleted file mode 100644 index c3181b1..0000000 --- a/tests/deploy_single.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -from base_deploy import BasicMongo - - -class SingleNode(BasicMongo): - def __init__(self, series): - super(SingleNode, self).__init__(units=1, - series=series, - deploy_timeout=900) - - def deploy(self): - self.d.add('mongodb', charm='mongodb', units=self.units) - self.d.expose('mongodb') - super(SingleNode, self).deploy() - - def validate_world_connectivity(self): - self.addy = self.d.sentry['mongodb'][0].info['public-address'] - super(SingleNode, self).validate_world_connectivity() - - def run(self): - self.deploy() - self.validate_world_connectivity() diff --git a/tests/deploy_with_ceilometer-trusty b/tests/deploy_with_ceilometer-trusty deleted file mode 100755 index b73d870..0000000 --- a/tests/deploy_with_ceilometer-trusty +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_with_ceilometer - -t = deploy_with_ceilometer.TestCeilometer('trusty') -t.run() diff --git a/tests/deploy_with_ceilometer-xenial b/tests/deploy_with_ceilometer-xenial deleted file mode 100755 index 4678457..0000000 --- a/tests/deploy_with_ceilometer-xenial +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_with_ceilometer - -#Not running this because of: https://launchpad.net/bugs/1656651 -#t = deploy_with_ceilometer.TestCeilometer('xenial') -#t.run() diff --git a/tests/deploy_with_ceilometer.py b/tests/deploy_with_ceilometer.py deleted file mode 100644 index 133eee3..0000000 --- a/tests/deploy_with_ceilometer.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -import amulet - -from base_deploy import BasicMongo - - -class TestCeilometer(BasicMongo): - def __init__(self, series): - super(TestCeilometer, self).__init__(units=1, series=series, - deploy_timeout=900) - - def deploy(self): - self.d.add('mongodb', charm='mongodb', units=self.units) - self.d.add('ceilometer', 'cs:{}/ceilometer'.format(self.series)) - self.d.relate('mongodb:database', 'ceilometer:shared-db') - self.d.expose('mongodb') - super(TestCeilometer, self).deploy() - - def validate_world_connectivity(self): - self.addy = self.d.sentry['mongodb'][0].info['public-address'] - super(TestCeilometer, self).validate_world_connectivity() - - def validate_mongo_relation(self): - unit = self.d.sentry['ceilometer'][0] - mongo = self.d.sentry['mongodb'][0].info['public-address'] - mongo_reladdr = self.d.sentry['mongodb'][0].relation( - 'database', 'ceilometer:shared-db') - cont = unit.file_contents('/etc/ceilometer/ceilometer.conf') - if (mongo not in cont and mongo_reladdr.get( - 'hostname', 'I SURE HOPE NOT') not in cont): - amulet.raise_status(amulet.FAIL, "Unable to verify ceilometer cfg") - - def run(self): - self.deploy() - self.validate_world_connectivity() diff --git a/tests/deploy_with_storage-trusty b/tests/deploy_with_storage-trusty deleted file mode 100755 index f7e8314..0000000 --- a/tests/deploy_with_storage-trusty +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_with_storage - -t = deploy_with_storage.WithStorage('trusty') -t.run() diff --git a/tests/deploy_with_storage-xenial b/tests/deploy_with_storage-xenial deleted file mode 100755 index 6b0d660..0000000 --- a/tests/deploy_with_storage-xenial +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 - -import deploy_with_storage - -# We are not testing this against xenial yet, because -# cs:~chris-gondolin/trusty/storage-5 does not exist for xenial (yet) -#t = deploy_with_storage.WithStorage('xenial') -#t.run() diff --git a/tests/deploy_with_storage.py b/tests/deploy_with_storage.py deleted file mode 100644 index 92a5fe6..0000000 --- a/tests/deploy_with_storage.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 - -from base_deploy import BasicMongo - -import amulet -from pymongo import MongoClient -from collections import Counter - - -class WithStorage(BasicMongo): - def __init__(self, series): - super(WithStorage, self).__init__(units=2, - series=series, - deploy_timeout=1800) - - def deploy(self): - self.d.add('mongodb', - charm='mongodb', - units=self.units, - constraints={'root-disk': '20480M'}) - - storage_charm = 'cs:~chris-gondolin/{}/storage-5'.format(self.series) - self.d.add('storage', charm=storage_charm, series=self.series) - self.d.configure('storage', {'provider': 'local'}) - super(WithStorage, self).deploy() - self.d.expose('mongodb') - - ordered_units = sorted(self.d.sentry['mongodb'], - key=lambda u: u.info['unit']) - self.sentry_dict = { - 'mongodb0-sentry': ordered_units[0], - 'mongodb1-sentry': ordered_units[1] - } - - def validate_status(self): - self.d.sentry.wait_for_status(self.d.juju_env, ['mongodb']) - - def validate_replicaset_setup(self): - self.d.sentry.wait(self.deploy_timeout) - - unit_status = [] - for service in self.sentry_dict: - addy = self.sentry_dict[service].info['public-address'] - if ":" in addy: - addy = "[{}]".format(addy) - client = MongoClient(addy) - r = client.admin.command('replSetGetStatus') - unit_status.append(r['myState']) - client.close() - - prims = Counter(unit_status)[1] - if prims != 1: - message = "Only one PRIMARY unit allowed! Found: %s" % (prims) - amulet.raise_status(amulet.FAIL, message) - - secnds = Counter(unit_status)[2] - if secnds != 1: - message = "Only one SECONDARY unit allowed! (Found %s)" % (secnds) - amulet.raise_status(amulet.FAIL, message) - - def run(self): - self.deploy() - self.validate_status() - self.validate_replicaset_setup() - - print("Adding storage relation, and sleeping for 2 min.") - try: - self.d.relate('mongodb:data', 'storage:data') - except OSError as e: - print("Ignoring error: {}", e) - self.d.sentry.wait(120) # 2 minute - - self.validate_status() - self.validate_replicaset_setup() diff --git a/tests/test_requirements.txt b/tests/test_requirements.txt new file mode 100644 index 0000000..9fbeeab --- /dev/null +++ b/tests/test_requirements.txt @@ -0,0 +1,2 @@ +git+https://github.com/openstack-charmers/zaza.git#egg=zaza +pymongo diff --git a/tests/tests.yaml b/tests/tests.yaml new file mode 100644 index 0000000..dcbd01c --- /dev/null +++ b/tests/tests.yaml @@ -0,0 +1,17 @@ +charm_name: mongodb-charm +tests: + - model_alias_xenial: + - tests.tests_mongodb.BasicMongodbCharmTest + - tests.tests_mongodb.ReplicatedMongodbCharmTest + - tests.tests_mongodb.XenialMongodbCharmTest + - model_alias_bionic: + - tests.tests_mongodb.BasicMongodbCharmTest + - tests.tests_mongodb.ReplicatedMongodbCharmTest + - model_alias_shard: + - tests.tests_mongodb.ShardedMongodbCharmTest +gate_bundles: + - model_alias_xenial: xenial + - model_alias_bionic: bionic + - model_alias_shard: bionic-shard +smoke_bundles: + - model_alias_bionic: bionic diff --git a/tests/tests_mongodb.py b/tests/tests_mongodb.py new file mode 100644 index 0000000..dc50d8d --- /dev/null +++ b/tests/tests_mongodb.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import requests +import unittest + +from pymongo import MongoClient +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry +from zaza import model +from zaza.charm_lifecycle import utils as lifecycle_utils + + +MONGO_STARTUP = 0 +MONGO_PRIMARY = 1 +MONGO_SECONDARY = 2 +MONGO_RECOVERING = 3 +MONGO_FATAL = 4 +MONGO_STARTUP2 = 5 +MONGO_UNKNOWN = 6 +MONGO_ARBITER = 7 +MONGO_DOWN = 8 +MONGO_ROLLBACK = 9 +MONGO_REMOVED = 10 + + +def requests_retry_session( + retries=3, backoff_factor=2, status_forcelist=(500, 502, 504), session=None +): + """Create a http session with retry""" + session = session or requests.Session() + retry = Retry( + total=retries, + read=retries, + connect=retries, + backoff_factor=backoff_factor, + status_forcelist=status_forcelist, + ) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + return session + + +class MongodbCharmTestBase(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.model_name = model.get_juju_model() + cls.test_config = lifecycle_utils.get_charm_config() + model.block_until_all_units_idle() + addr = model.get_lead_unit_ip("mongodb") + if ":" in addr: # ipv6 formatting + cls.leader_address = "[{}]".format(addr) + else: + cls.leader_address = addr + cls.db_client = MongoClient(cls.leader_address) + + def cat_unit(self, unit, path): + unit_res = model.run_on_unit(unit, "sudo cat {}".format(path)) + return unit_res["Stdout"] + + def web_admin_interface(self, ipaddr, port=28017): + url = "http://{}:{}".format(ipaddr, port) + resp = requests_retry_session(retries=10).get(url) + return resp + + +class BasicMongodbCharmTest(MongodbCharmTestBase): + def test_db_insert(self): + """Test if we can insert and remove a value""" + test_db = self.db_client.test_db + insert_id = test_db.testcoll.insert({"assert": True}) + self.assertTrue(insert_id is not None, "Failed to insert test data") + result = test_db.testcoll.remove(insert_id) + self.assertTrue("err" not in result, "Failed to remove test data") + + def test_service_running(self): + """Test if we have a mongod running on all units""" + for i in (0, 1, 2): + running_for = model.get_unit_service_start_time( + "mongodb/{}".format(i), "mongod", timeout=20 + ) + self.assertGreater(running_for, 0) + + +class XenialMongodbCharmTest(MongodbCharmTestBase): + def test_status_interface(self): + """Check if we can access the web admin port -- xenial only""" + resp = self.web_admin_interface(self.leader_address) + resp.raise_for_status() + + +class ReplicatedMongodbCharmTest(MongodbCharmTestBase): + def get_set_status(self): + unit_status = [] + for addr in model.get_app_ips("mongodb"): + unit_client = MongoClient(addr) + unit_status.append(unit_client.admin.command("replSetGetStatus")) + return unit_status + + def test_replset_numbers(self): + """Test if we have 1 primary and 2 secondary mongodbs""" + unit_status = self.get_set_status() + primaries = [u for u in unit_status if u["myState"] == MONGO_PRIMARY] + secondaries = [u for u in unit_status if u["myState"] == MONGO_SECONDARY] + self.assertEqual(len(primaries), 1) + self.assertEqual(len(secondaries), 2) + + def test_replset_consistent_members(self): + """Test if all units have the same view on membership""" + unit_status = self.get_set_status() + prim_members = [u for u in unit_status if u["myState"] == MONGO_PRIMARY][0][ + "members" + ] + secondary_members = [ + u["members"] for u in unit_status if u["myState"] == MONGO_SECONDARY + ] + + def extract(member_dict): + # Extract a subset of membership info as a frozenset. Name is ipaddr:port, health a float and stateStr a str + return frozenset( + v for k, v in member_dict.items() if k in ["name", "health", "stateStr"] + ) + + ref = set( + map(extract, prim_members) + ) # Our reference view on membership comes from the primary + secondaries = [set(map(extract, sec)) for sec in secondary_members] + for sec in secondaries: + self.assertEqual(ref, sec) + + +class ShardedMongodbCharmTest(MongodbCharmTestBase): + def test_mongos_running(self): + """Test if the mongos service is running""" + running_for = model.get_unit_service_start_time( + "mongodb/0", "mongos", timeout=20 + ) + self.assertGreater(running_for, 0) @@ -0,0 +1,30 @@ +[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 = -r{toxinidir}/tests/test_requirements.txt + +[testenv:func-smoke] +basepython = python3 +commands = + functest-run-suite --keep-model --smoke +deps = -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): @patch('time.sleep') def test_am_i_primary(self, mock_sleep, mock_mongo_client, mock_run_admin_cmd): - mock_run_admin_cmd.side_effect = [{'myState': x} for x in xrange(5)] - expected_results = [True if x == 1 else False for x in xrange(5)] + mock_run_admin_cmd.side_effect = [{'myState': x} for x in range(5)] + expected_results = [True if x == 1 else False for x in range(5)] # Check expected return values each time... for exp in expected_results: @@ -203,7 +203,7 @@ class MongoHooksTest(CharmTestCase): mock_run_admin_cmd): msg = 'replSetInitiate - should come online shortly' mock_run_admin_cmd.side_effect = [OperationFailure(msg) - for x in xrange(10)] + for x in range(10)] try: hooks.am_i_primary() @@ -262,7 +262,7 @@ class MongoHooksTest(CharmTestCase): def test_mongo_client_smart_error_cases(self, mock_ck_output, mock_sleep): mock_ck_output.side_effect = [CalledProcessError(1, 'cmd', output='fake-error') - for x in xrange(11)] + for x in range(11)] rv = hooks.mongo_client_smart(command='fake-cmd') self.assertFalse(rv) |
