aboutsummaryrefslogtreecommitdiffstats
path: root/charms/trusty/cassandra/testing
diff options
context:
space:
mode:
Diffstat (limited to 'charms/trusty/cassandra/testing')
-rw-r--r--charms/trusty/cassandra/testing/__init__.py15
-rw-r--r--charms/trusty/cassandra/testing/amuletfixture.py234
-rw-r--r--charms/trusty/cassandra/testing/mocks.py182
3 files changed, 0 insertions, 431 deletions
diff --git a/charms/trusty/cassandra/testing/__init__.py b/charms/trusty/cassandra/testing/__init__.py
deleted file mode 100644
index b1b7fcd..0000000
--- a/charms/trusty/cassandra/testing/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2015 Canonical Ltd.
-#
-# This file is part of the Cassandra Charm for Juju.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3, as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/charms/trusty/cassandra/testing/amuletfixture.py b/charms/trusty/cassandra/testing/amuletfixture.py
deleted file mode 100644
index 988267f..0000000
--- a/charms/trusty/cassandra/testing/amuletfixture.py
+++ /dev/null
@@ -1,234 +0,0 @@
-# Copyright 2015 Canonical Ltd.
-#
-# This file is part of the Cassandra Charm for Juju.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3, as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from functools import wraps
-import json
-import os
-import shutil
-import subprocess
-import tempfile
-import time
-
-import amulet
-import yaml
-
-
-class AmuletFixture(amulet.Deployment):
- def __init__(self, series, verbose=False):
- if verbose:
- super(AmuletFixture, self).__init__(series=series)
- else:
- # We use a wrapper around juju-deployer so we can fix how it is
- # invoked. In particular, turn off all the noise so we can
- # actually read our test output.
- juju_deployer = os.path.abspath(os.path.join(
- os.path.dirname(__file__), os.pardir, 'lib',
- 'juju-deployer-wrapper.py'))
- super(AmuletFixture, self).__init__(series=series,
- juju_deployer=juju_deployer)
- assert self.series == series
-
- def setUp(self):
- self._temp_dirs = []
-
- self.reset_environment(force=True)
-
- # Repackage our charm to a temporary directory, allowing us
- # to strip our virtualenv symlinks that would otherwise cause
- # juju to abort. We also strip the .bzr directory, working
- # around Bug #1394078.
- self.repackage_charm()
-
- # Fix amulet.Deployment so it doesn't depend on environment
- # variables or the current working directory, but rather the
- # environment we have introspected.
- with open(os.path.join(self.charm_dir, 'metadata.yaml'), 'r') as s:
- self.charm_name = yaml.safe_load(s)['name']
- self.charm_cache.test_charm = None
- self.charm_cache.fetch(self.charm_name, self.charm_dir,
- series=self.series)
-
- # Explicitly reset $JUJU_REPOSITORY to ensure amulet and
- # juju-deployer does not mess with the real one, per Bug #1393792
- self.org_repo = os.environ.get('JUJU_REPOSITORY', None)
- temp_repo = tempfile.mkdtemp(suffix='.repo')
- self._temp_dirs.append(temp_repo)
- os.environ['JUJU_REPOSITORY'] = temp_repo
- os.makedirs(os.path.join(temp_repo, self.series), mode=0o700)
-
- def tearDown(self, reset_environment=True):
- if reset_environment:
- self.reset_environment()
- if self.org_repo is None:
- del os.environ['JUJU_REPOSITORY']
- else:
- os.environ['JUJU_REPOSITORY'] = self.org_repo
-
- def deploy(self, timeout=None):
- '''Deploying or updating the configured system.
-
- Invokes amulet.Deployer.setup with a nicer name and standard
- timeout handling.
- '''
- if timeout is None:
- timeout = int(os.environ.get('AMULET_TIMEOUT', 900))
-
- # juju-deployer is buried under here, and has race conditions.
- # Sleep a bit before invoking it, so its cached view of the
- # environment matches reality.
- time.sleep(15)
-
- # If setUp fails, tearDown is never called leaving the
- # environment setup. This is useful for debugging.
- self.setup(timeout=timeout)
- self.wait(timeout=timeout)
-
- def __del__(self):
- for temp_dir in self._temp_dirs:
- if os.path.exists(temp_dir):
- shutil.rmtree(temp_dir, ignore_errors=True)
-
- def get_status(self):
- try:
- raw = subprocess.check_output(['juju', 'status', '--format=json'],
- universal_newlines=True)
- except subprocess.CalledProcessError as x:
- print(x.output)
- raise
- if raw:
- return json.loads(raw)
- return None
-
- def wait(self, timeout=None):
- '''Wait until the environment has reached a stable state.'''
- if timeout is None:
- timeout = int(os.environ.get('AMULET_TIMEOUT', 900))
- cmd = ['timeout', str(timeout), 'juju', 'wait', '-q']
- try:
- subprocess.check_output(cmd, universal_newlines=True)
- except subprocess.CalledProcessError as x:
- print(x.output)
- raise
-
- def reset_environment(self, force=False):
- if force:
- status = self.get_status()
- machines = [m for m in status.get('machines', {}).keys()
- if m != '0']
- if machines:
- subprocess.call(['juju', 'destroy-machine',
- '--force'] + machines,
- stdout=subprocess.DEVNULL,
- stderr=subprocess.DEVNULL)
- fails = dict()
- while True:
- status = self.get_status()
- service_items = status.get('services', {}).items()
- if not service_items:
- break
- for service_name, service in service_items:
- if service.get('life', '') not in ('dying', 'dead'):
- subprocess.call(['juju', 'destroy-service', service_name],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- for unit_name, unit in service.get('units', {}).items():
- if unit.get('agent-state', None) == 'error':
- if force:
- # If any units have failed hooks, unstick them.
- # This should no longer happen now we are
- # using the 'destroy-machine --force' command
- # earlier.
- try:
- subprocess.check_output(
- ['juju', 'resolved', unit_name],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError:
- # A previous 'resolved' call make cause a
- # subsequent one to fail if it is still
- # being processed. However, we need to keep
- # retrying because after a successful
- # resolution a subsequent hook may cause an
- # error state.
- pass
- else:
- fails[unit_name] = unit
- time.sleep(1)
-
- harvest_machines = []
- for machine, state in status.get('machines', {}).items():
- if machine != "0" and state.get('life') not in ('dying', 'dead'):
- harvest_machines.append(machine)
-
- if harvest_machines:
- cmd = ['juju', 'remove-machine', '--force'] + harvest_machines
- subprocess.check_output(cmd, stderr=subprocess.STDOUT)
-
- if fails:
- raise Exception("Teardown failed", fails)
-
- def repackage_charm(self):
- """Mirror the charm into a staging area.
-
- We do this to work around issues with Amulet, juju-deployer
- and juju. In particular:
- - symlinks in the Python virtual env pointing outside of the
- charm directory.
- - odd bzr interactions, such as tests being run on the committed
- version of the charm, rather than the working tree.
-
- Returns the test charm directory.
- """
- # Find the charm_dir we are testing
- src_charm_dir = os.path.dirname(__file__)
- while True:
- if os.path.exists(os.path.join(src_charm_dir,
- 'metadata.yaml')):
- break
- assert src_charm_dir != os.sep, 'metadata.yaml not found'
- src_charm_dir = os.path.abspath(os.path.join(src_charm_dir,
- os.pardir))
-
- with open(os.path.join(src_charm_dir, 'metadata.yaml'), 'r') as s:
- self.charm_name = yaml.safe_load(s)['name']
-
- repack_root = tempfile.mkdtemp(suffix='.charm')
- self._temp_dirs.append(repack_root)
- # juju-deployer now requires the series in the path when
- # deploying from an absolute path.
- repack_root = os.path.join(repack_root, self.series)
- os.makedirs(repack_root, mode=0o700)
-
- self.charm_dir = os.path.join(repack_root, self.charm_name)
-
- # Ignore .bzr to work around weird bzr interactions with
- # juju-deployer, per Bug #1394078, and ignore .venv
- # due to a) it containing symlinks juju will reject and b) to avoid
- # infinite recursion.
- shutil.copytree(src_charm_dir, self.charm_dir, symlinks=True,
- ignore=shutil.ignore_patterns('.venv?', '.bzr'))
-
-
-# Bug #1417097 means we need to monkey patch Amulet for now.
-real_juju = amulet.helpers.juju
-
-
-@wraps(real_juju)
-def patched_juju(args, env=None):
- args = [str(a) for a in args]
- return real_juju(args, env)
-
-amulet.helpers.juju = patched_juju
-amulet.deployer.juju = patched_juju
diff --git a/charms/trusty/cassandra/testing/mocks.py b/charms/trusty/cassandra/testing/mocks.py
deleted file mode 100644
index 7d03f23..0000000
--- a/charms/trusty/cassandra/testing/mocks.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Copyright 2015 Canonical Ltd.
-#
-# This file is part of the Cassandra Charm for Juju.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3, as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-'''
-charm-helpers mocks.
-'''
-import os.path
-import shutil
-import tempfile
-from unittest.mock import patch
-
-import yaml
-
-from charmhelpers import fetch
-from charmhelpers.core import hookenv
-
-
-CHARM_DIR = os.path.abspath(os.path.join(
- os.path.dirname(__file__), os.pardir))
-
-
-def mock_charmhelpers(test_case):
- hookenv.cache.clear() # Clear the hookenv memorisation.
-
- mocks = []
-
- # Mock environment
- charm_dir = tempfile.TemporaryDirectory()
- test_case.addCleanup(charm_dir.cleanup)
- mock_env = patch.dict(os.environ, dict(CHARM_DIR=charm_dir.name))
- mock_env.start()
- test_case.addCleanup(mock_env.stop)
- shutil.copyfile(os.path.join(CHARM_DIR, 'metadata.yaml'),
- os.path.join(charm_dir.name, 'metadata.yaml'))
-
- # Mock config.
- # Set items:
- # hookenv.config()['foo'] = 'bar'
- # Reset 'previous' state:
- # hookenv.config().save();
- # hookenv.config().load_previous()
- config = hookenv.Config()
- tmp = tempfile.NamedTemporaryFile(suffix='.config')
- config.CONFIG_FILE_NAME = tmp.name
- test_case.addCleanup(tmp.close)
- with open(os.path.join(CHARM_DIR, 'config.yaml'), 'rb') as f:
- defaults = yaml.safe_load(f)['options']
- for k, v in defaults.items():
- opt_type = v.get('type', 'string')
- opt_val = v.get('default', None)
- if opt_val is None:
- config[k] = None
- elif opt_type == 'string':
- config[k] = str(opt_val)
- elif opt_type == 'int':
- config[k] = int(opt_val)
- elif opt_type == 'boolean':
- config[k] = bool(opt_val)
-
- def mock_config(scope=None):
- if scope is None:
- return config
- return config.get(scope, None)
- mocks.append(patch('charmhelpers.core.hookenv.config',
- side_effect=mock_config, autospec=True))
-
- # Magic mocks.
- methods = [
- 'charmhelpers.core.hookenv.log',
- 'charmhelpers.core.hookenv.hook_name',
- 'charmhelpers.core.hookenv.related_units',
- 'charmhelpers.core.hookenv.relation_get',
- 'charmhelpers.core.hookenv.relation_set',
- 'charmhelpers.core.hookenv.relation_ids',
- 'charmhelpers.core.hookenv.relation_type',
- 'charmhelpers.core.hookenv.service_name',
- 'charmhelpers.core.hookenv.local_unit',
- 'charmhelpers.core.hookenv.unit_private_ip',
- 'charmhelpers.core.hookenv.unit_public_ip',
- 'charmhelpers.core.host.log',
- 'charmhelpers.fetch.filter_installed_packages',
- 'os.chown', 'os.fchown',
- ]
- for m in methods:
- mocks.append(patch(m, autospec=True))
-
- for mock in mocks:
- mock.start()
- test_case.addCleanup(mock.stop)
-
- hookenv.local_unit.return_value = 'service/1'
-
- def mock_unit_private_ip():
- return '10.20.0.{}'.format(hookenv.local_unit().split('/')[-1])
- hookenv.unit_private_ip.side_effect = mock_unit_private_ip
-
- def mock_unit_public_ip():
- return '10.30.0.{}'.format(hookenv.local_unit().split('/')[-1])
- hookenv.unit_public_ip.side_effect = mock_unit_public_ip
-
- def mock_service_name():
- return hookenv.local_unit().split('/')[0]
- hookenv.service_name.side_effect = mock_service_name
-
- hookenv.relation_ids.side_effect = (
- lambda x: ['{}:1'.format(x)] if x else [])
- hookenv.related_units.return_value = ('service/2', 'service/3')
-
- relinfos = dict()
-
- def mock_relation_set(relation_id=None, relation_settings=None, **kwargs):
- if relation_id is None:
- relation_id = hookenv.relation_id()
- unit = hookenv.local_unit()
- relinfo = mock_relation_get(unit=unit, rid=relation_id)
- if relation_settings is not None:
- relinfo.update(relation_settings)
- relinfo.update(kwargs)
- return None
- hookenv.relation_set.side_effect = mock_relation_set
-
- def mock_relation_get(attribute=None, unit=None, rid=None):
- if rid is None:
- rid = hookenv.relation_id()
- if unit is None:
- unit = hookenv.remove_unit()
- service, unit_num = unit.split('/')
- unit_num = int(unit_num)
- relinfos.setdefault(rid, {})
- relinfos[rid].setdefault(
- unit, {'private-address': '10.20.0.{}'.format(unit_num)})
- relinfo = relinfos[rid][unit]
- if attribute is None or attribute == '-':
- return relinfo
- return relinfo.get(attribute)
- hookenv.relation_get.side_effect = mock_relation_get
-
- def mock_chown(target, uid, gid):
- assert uid == 0
- assert gid == 0
- assert os.path.exists(target)
- os.chown.side_effect = mock_chown
-
- def mock_fchown(fd, uid, gid):
- assert uid == 0
- assert gid == 0
- os.fchown.side_effect = mock_fchown
-
- fetch.filter_installed_packages.side_effect = lambda pkgs: list(pkgs)
-
- def mock_relation_for_unit(unit=None, rid=None):
- if unit is None:
- unit = hookenv.remote_unit()
- service, unit_num = unit.split('/')
- unit_num = int(unit_num)
- return {'private-address': '10.20.0.{}'.format(unit_num)}
- hookenv.relation_for_unit.side_effect = mock_relation_for_unit
-
- def mock_chown(target, uid, gid):
- assert uid == 0
- assert gid == 0
- assert os.path.exists(target)
- os.chown.side_effect = mock_chown
-
- def mock_fchown(fd, uid, gid):
- assert uid == 0
- assert gid == 0
- os.fchown.side_effect = mock_fchown
-
- fetch.filter_installed_packages.side_effect = lambda pkgs: list(pkgs)