Merge pull request #1 from gnuoy/functest-utils

Add helps for charm functional tests
This commit is contained in:
David Ames
2018-03-27 07:48:03 -07:00
committed by GitHub
23 changed files with 858 additions and 7 deletions
+11 -2
View File
@@ -8,7 +8,10 @@ from setuptools.command.test import test as TestCommand
version = "0.0.1.dev1"
install_require = [
'juju'
'hvac',
'juju',
'juju-wait',
'PyYAML',
]
tests_require = [
@@ -72,6 +75,12 @@ setup(
],
entry_points={
'console_scripts': [
'functest-run-suite = zaza.charm_lifecycle.func_test_runner:main',
'functest-deploy = zaza.charm_lifecycle.deploy:main',
'functest-configure = zaza.charm_lifecycle.configure:main',
'functest-destroy = zaza.charm_lifecycle.destroy:main',
'functest-prepare = zaza.charm_lifecycle.prepare:main',
'functest-test = zaza.charm_lifecycle.test:main',
'current-apps = zaza.model:main',
'tempest-config = zaza.tempest_config:main',
]
@@ -88,4 +97,4 @@ setup(
'testing': tests_require,
},
tests_require=tests_require,
)
)
+4 -2
View File
@@ -1,3 +1,5 @@
flake8>=2.2.4,<=2.4.1
juju_wait
PyYAML
flake8>=2.2.4,<=3.5.0
mock>=1.2
nose>=1.3.7
nose>=1.3.7
@@ -0,0 +1,27 @@
import mock
import zaza.charm_lifecycle.configure as lc_configure
import unit_tests.utils as ut_utils
class TestCharmLifecycleConfigure(ut_utils.BaseTestCase):
def test_run_configure_list(self):
self.patch_object(lc_configure.utils, 'get_class')
self.get_class.side_effect = lambda x: x
mock1 = mock.MagicMock()
mock2 = mock.MagicMock()
lc_configure.run_configure_list([mock1, mock2])
self.assertTrue(mock1.called)
self.assertTrue(mock2.called)
def test_configure(self):
self.patch_object(lc_configure, 'run_configure_list')
mock1 = mock.MagicMock()
mock2 = mock.MagicMock()
lc_configure.configure([mock1, mock2])
self.run_configure_list.assert_called_once_with([mock1, mock2])
def test_parser(self):
args = lc_configure.parse_args(['-c', 'my.func1', 'my.func2'])
self.assertEqual(args.configfuncs, ['my.func1', 'my.func2'])
@@ -0,0 +1,40 @@
import zaza.charm_lifecycle.deploy as lc_deploy
import unit_tests.utils as ut_utils
class TestCharmLifecycleDeploy(ut_utils.BaseTestCase):
def test_deploy_bundle(self):
self.patch_object(lc_deploy.subprocess, 'check_call')
lc_deploy.deploy_bundle('bun.yaml', 'newmodel')
self.check_call.assert_called_once_with(
['juju', 'deploy', '-m', 'newmodel', 'bun.yaml'])
def test_deploy(self):
self.patch_object(lc_deploy, 'deploy_bundle')
self.patch_object(lc_deploy.juju_wait, 'wait')
lc_deploy.deploy('bun.yaml', 'newmodel')
self.deploy_bundle.assert_called_once_with('bun.yaml', 'newmodel')
self.wait.assert_called_once_with()
def test_deploy_nowait(self):
self.patch_object(lc_deploy, 'deploy_bundle')
self.patch_object(lc_deploy.juju_wait, 'wait')
lc_deploy.deploy('bun.yaml', 'newmodel', wait=False)
self.deploy_bundle.assert_called_once_with('bun.yaml', 'newmodel')
self.assertFalse(self.wait.called)
def test_parser(self):
args = lc_deploy.parse_args([
'-m', 'mymodel',
'-b', 'bun.yaml'])
self.assertEqual(args.model, 'mymodel')
self.assertEqual(args.bundle, 'bun.yaml')
self.assertTrue(args.wait)
def test_parser_nowait(self):
args = lc_deploy.parse_args([
'-m', 'mymodel',
'-b', 'bun.yaml',
'--no-wait'])
self.assertFalse(args.wait)
@@ -0,0 +1,20 @@
import zaza.charm_lifecycle.destroy as lc_destroy
import unit_tests.utils as ut_utils
class TestCharmLifecycleDestroy(ut_utils.BaseTestCase):
def test_destroy_model(self):
self.patch_object(lc_destroy.subprocess, 'check_call')
lc_destroy.destroy_model('doomed')
self.check_call.assert_called_once_with(
['juju', 'destroy-model', '--yes', 'doomed'])
def test_destroy(self):
self.patch_object(lc_destroy, 'destroy_model')
lc_destroy.destroy('doomed')
self.destroy_model.assert_called_once_with('doomed')
def test_parser(self):
args = lc_destroy.parse_args(['-m', 'doomed'])
self.assertEqual(args.model_name, 'doomed')
@@ -0,0 +1,55 @@
import mock
import zaza.charm_lifecycle.func_test_runner as lc_func_test_runner
import unit_tests.utils as ut_utils
class TestCharmLifecycleFuncTestRunner(ut_utils.BaseTestCase):
def test_func_test_runner(self):
self.patch_object(lc_func_test_runner.utils, 'get_charm_config')
self.patch_object(lc_func_test_runner, 'generate_model_name')
self.patch_object(lc_func_test_runner.prepare, 'prepare')
self.patch_object(lc_func_test_runner.deploy, 'deploy')
self.patch_object(lc_func_test_runner.configure, 'configure')
self.patch_object(lc_func_test_runner.test, 'test')
self.patch_object(lc_func_test_runner.destroy, 'destroy')
self.generate_model_name.return_value = 'newmodel'
self.get_charm_config.return_value = {
'charm_name': 'mycharm',
'gate_bundles': ['bundle1', 'bundle2'],
'configure': [
'zaza.charm_tests.mycharm.setup.basic_setup'
'zaza.charm_tests.othercharm.setup.setup'],
'tests': [
'zaza.charm_tests.mycharm.tests.SmokeTest',
'zaza.charm_tests.mycharm.tests.ComplexTest']}
lc_func_test_runner.func_test_runner()
prepare_calls = [
mock.call('newmodel'),
mock.call('newmodel')]
deploy_calls = [
mock.call('./tests/bundles/bundle1.yaml', 'newmodel'),
mock.call('./tests/bundles/bundle2.yaml', 'newmodel')]
configure_calls = [
mock.call([
'zaza.charm_tests.mycharm.setup.basic_setup'
'zaza.charm_tests.othercharm.setup.setup']),
mock.call([
'zaza.charm_tests.mycharm.setup.basic_setup'
'zaza.charm_tests.othercharm.setup.setup'])]
test_calls = [
mock.call([
'zaza.charm_tests.mycharm.tests.SmokeTest',
'zaza.charm_tests.mycharm.tests.ComplexTest']),
mock.call([
'zaza.charm_tests.mycharm.tests.SmokeTest',
'zaza.charm_tests.mycharm.tests.ComplexTest'])]
destroy_calls = [
mock.call('newmodel'),
mock.call('newmodel')]
self.prepare.assert_has_calls(prepare_calls)
self.deploy.assert_has_calls(deploy_calls)
self.configure.assert_has_calls(configure_calls)
self.test.assert_has_calls(test_calls)
self.destroy.assert_has_calls(destroy_calls)
@@ -0,0 +1,20 @@
import zaza.charm_lifecycle.prepare as lc_prepare
import unit_tests.utils as ut_utils
class TestCharmLifecyclePrepare(ut_utils.BaseTestCase):
def test_add_model(self):
self.patch_object(lc_prepare.subprocess, 'check_call')
lc_prepare.add_model('newmodel')
self.check_call.assert_called_once_with(
['juju', 'add-model', 'newmodel'])
def test_prepare(self):
self.patch_object(lc_prepare, 'add_model')
lc_prepare.add_model('newmodel')
self.add_model.assert_called_once_with('newmodel')
def test_parser(self):
args = lc_prepare.parse_args(['-m', 'newmodel'])
self.assertEqual(args.model_name, 'newmodel')
@@ -0,0 +1,36 @@
import mock
import zaza.charm_lifecycle.test as lc_test
import unit_tests.utils as ut_utils
class TestCharmLifecycleTest(ut_utils.BaseTestCase):
def test_run_test_list(self):
loader_mock = mock.MagicMock()
runner_mock = mock.MagicMock()
self.patch_object(lc_test.unittest, 'TestLoader')
self.patch_object(lc_test.unittest, 'TextTestRunner')
self.TestLoader.return_value = loader_mock
self.TextTestRunner.return_value = runner_mock
self.patch_object(lc_test.utils, 'get_class')
self.get_class.side_effect = lambda x: x
test_class1_mock = mock.MagicMock()
test_class2_mock = mock.MagicMock()
lc_test.run_test_list([test_class1_mock, test_class2_mock])
loader_calls = [
mock.call(test_class1_mock),
mock.call(test_class2_mock)]
loader_mock.loadTestsFromTestCase.assert_has_calls(loader_calls)
def test_test(self):
self.patch_object(lc_test, 'run_test_list')
lc_test.run_test_list(['test_class1', 'test_class2'])
self.run_test_list.assert_called_once_with(
['test_class1', 'test_class2'])
def test_parser(self):
args = lc_test.parse_args(['-t', 'my.test_class1', 'my.test_class2'])
self.assertEqual(
args.tests,
['my.test_class1', 'my.test_class2'])
@@ -0,0 +1,24 @@
import os
import tempfile
import yaml
import zaza.charm_lifecycle.utils as lc_utils
import unit_tests.utils as ut_utils
class TestCharmLifecycleUtils(ut_utils.BaseTestCase):
def test_get_charm_config(self):
f = tempfile.NamedTemporaryFile(delete=False, mode='w')
f.write(yaml.dump({'test_config': 'someconfig'}))
f.close()
charm_config = lc_utils.get_charm_config(yaml_file=f.name)
os.unlink(f.name)
self.assertEqual(charm_config, {'test_config': 'someconfig'})
def test_get_class(self):
self.assertEqual(
type(lc_utils.get_class('unit_tests.'
'test_zaza_charm_lifecycle_utils.'
'TestCharmLifecycleUtils')()),
type(self))
+82
View File
@@ -0,0 +1,82 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Note that the unit_tests/__init__.py also mocks out two charmhelpers imports
# that have side effects that try to apt install modules:
# sys.modules['charmhelpers.contrib.openstack.utils'] = mock.MagicMock()
# sys.modules['charmhelpers.contrib.network.ip'] = mock.MagicMock()
import contextlib
import io
import mock
import unittest
@contextlib.contextmanager
def patch_open():
'''Patch open() to allow mocking both open() itself and the file that is
yielded.
Yields the mock for "open" and "file", respectively.'''
mock_open = mock.MagicMock(spec=open)
mock_file = mock.MagicMock(spec=io.FileIO)
@contextlib.contextmanager
def stub_open(*args, **kwargs):
mock_open(*args, **kwargs)
yield mock_file
with mock.patch('builtins.open', stub_open):
yield mock_open, mock_file
class BaseTestCase(unittest.TestCase):
def setUp(self):
self._patches = {}
self._patches_start = {}
def tearDown(self):
for k, v in self._patches.items():
v.stop()
setattr(self, k, None)
self._patches = None
self._patches_start = None
def patch_object(self, obj, attr, return_value=None, name=None, new=None,
**kwargs):
if name is None:
name = attr
if new is not None:
mocked = mock.patch.object(obj, attr, new=new, **kwargs)
else:
mocked = mock.patch.object(obj, attr, **kwargs)
self._patches[name] = mocked
started = mocked.start()
if new is None:
started.return_value = return_value
self._patches_start[name] = started
setattr(self, name, started)
def patch(self, item, return_value=None, name=None, new=None, **kwargs):
if name is None:
raise RuntimeError("Must pass 'name' to .patch()")
if new is not None:
mocked = mock.patch(item, new=new, **kwargs)
else:
mocked = mock.patch(item, **kwargs)
self._patches[name] = mocked
started = mocked.start()
if new is None:
started.return_value = return_value
self._patches_start[name] = started
setattr(self, name, started)
+201
View File
@@ -0,0 +1,201 @@
# Enabling Charm Functional Tests with Zaza
The end-to-end tests of a charm are divided into distinct phases. Each phase
can be run in isolation and tests shared between charms.
# Running a suite of deployments and tests
**functest-run-suite** will read the charms tests.yaml and execute the
deployments and tests outlined there. However, each phase can be run
independently.
## Charm Test Phases
Charms should ship with bundles that deploy the charm with different
application versions, topologies or config options. functest-run-suite will
run through each phase listed below in order for each bundle that is to be
tested.
### 1) Prepare
Prepare the environment ready for a deployment. At a minimum create a model
to run the deployment in.
To run manually:
```
$ functest-prepare --help
usage: functest-prepare [-h] -m MODEL_NAME
optional arguments:
-h, --help show this help message and exit
-m MODEL_NAME, --model-name MODEL_NAME
Name of new model
```
### 2) Deploy
Deploy the target bundle and wait for it to complete. **functest-run-suite**
will look at the list of bundles in the tests.yaml in the charm to determine
the bundle.
To run manually:
```
$ functest-deploy --help
usage: functest-deploy [-h] -m MODEL -b BUNDLE [--no-wait]
optional arguments:
-h, --help show this help message and exit
-m MODEL, --model MODEL
Model to deploy to
-b BUNDLE, --bundle BUNDLE
Bundle name (excluding file ext)
--no-wait Do not wait for deployment to settle
```
### 3) Configure
Post-deployment configuration, for example create network, tenant, image, etc.
Any necessary post-deploy actions go here. **functest-run-suite** will look
for a list of functions that should be run in tests.yaml and execute each
in turn.
To run manually:
```
functest-configure --help
usage: functest-configure [-h] [-c CONFIGFUNCS [CONFIGFUNCS ...]]
optional arguments:
-h, --help show this help message and exit
-c CONFIGFUNCS [CONFIGFUNCS ...], --configfuncs CONFIGFUNCS [CONFIGFUNCS ...]
Space sperated list of config functions
```
### 4) Test
Run tests. These maybe tests in zaza or a wrapper around another testing
framework like rally or tempest. **functest-run-suite** will look for a list
of test classes that should be run in tests.yaml and execute each in turn.
To run manually:
```
usage: functest-test [-h] [-t TESTS [TESTS ...]]
optional arguments:
-h, --help show this help message and exit
-t TESTS [TESTS ...], --tests TESTS [TESTS ...]
Space sperated list of test classes
```
### 5) Collect
Collect artifacts useful for debugging any failures or useful for trend
analysis like deprecation warning or deployment time.
### 6) Destroy
Destroy the model.
```
functest-destroy --help
usage: functest-destroy [-h] -m MODEL_NAME
optional arguments:
-h, --help show this help message and exit
-m MODEL_NAME, --model-name MODEL_NAME
Name of model to remove
```
# Enabling zaza tests in a charm
* Add zaza in the charms test-requirements.txt
* tox.ini should include a target like:
```
[testenv:func35]
basepython = python3
commands =
functest-run-suite
```
* Bundles which are to be used for the tests:
```
tests/bundles/base-xenial.yaml
tests/bundles/base-xenial-ha.yaml
tests/bundles/base-bionic.yaml
```
* A tests/tests.yaml file that describes the bundles to be run and
the tests
```
charm_name: vault
tests:
- zaza.charm_tests.vault.VaultTest
configure:
- zaza.charm_tests.vault.setup.basic_setup
gate_bundles:
- base-xenial
- base-bionic
dev_bundles:
- base-xenial-ha
```
# Adding tests to zaza
The setup and tests for a charm should live in zaza, this enables the code to
be shared between multiple charms. To add support for a new charm create a
directory, named after the charm, inside **zaza/charm_tests**. Within the new
directory define the tests in **tests.py** and any setup code in **setup.py**
This code can then be referenced in the charms **tests.yaml**
eg to add support for a new congress charm create a new directory in zaza
```
mkdir zaza/charm_tests/congress
```
Add setup code into setup.py
```
$ cat zaza/charm_tests/congress/setup.py
def basic_setup():
congress_client(run_special_setup)
```
Add test code into tests.py
```
class CongressTest(unittest.TestCase):
def test_policy_create(self):
policy = congress.create_policy()
self.assertTrue(policy)
```
These now need to be refenced in the congress charms tests.yaml. Additional
setup is needed to run a useful congress tests, so congress' tests.yaml might
look like:
```
charm_name: congress
configure:
- zaza.charm_tests.nova.setup.flavor_setup
- zaza.charm_tests.nova.setup.image_setup
- zaza.charm_tests.neutron.setup.create_tenant_networks
- zaza.charm_tests.neutron.setup.create_ext_networks
- zaza.charm_tests.congress.setup.basic_setup
tests:
- zaza.charm_tests.keystone.KeystoneBasicTest
- zaza.charm_tests.congress.CongressTest
gate_bundles:
- base-xenial
- base-bionic
dev_bundles:
- base-xenial-ha
```
View File
View File
+49
View File
@@ -0,0 +1,49 @@
import argparse
import logging
import sys
import zaza.charm_lifecycle.utils as utils
def run_configure_list(functions):
"""Run the configure scripts as defined in the list of test classes in
series.
:param functions: List of configure functions functions
:type tests: ['zaza.charms_tests.svc.setup', ...]
"""
for func in functions:
utils.get_class(func)()
def configure(functions):
"""Run all post-deployment configuration steps
:param functions: List of configure functions functions
:type tests: ['zaza.charms_tests.svc.setup', ...]"""
run_configure_list(functions)
def parse_args(args):
"""Parse command line arguments
:param args: List of configure functions functions
:type list: [str1, str2,...] List of command line arguments
:returns: Parsed arguments
:rtype: Namespace
"""
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--configfuncs', nargs='+',
help='Space sperated list of config functions',
required=False)
return parser.parse_args(args)
def main():
"""Run the configuration defined by the command line args or if none were
provided read the configuration functions from the charms tests.yaml
config file"""
logging.basicConfig(level=logging.INFO)
args = parse_args(sys.argv[1:])
funcs = args.configfuncs or utils.get_charm_config()['configure']
configure(funcs)
+63
View File
@@ -0,0 +1,63 @@
import argparse
import logging
import subprocess
import sys
import juju_wait
def deploy_bundle(bundle, model):
"""Deploy the given bundle file in the specified model
:param bundle: Path to bundle file
:type bundle: str
:param model: Name of model to deploy bundle in
:type model: str
"""
logging.info("Deploying bundle {}".format(bundle))
subprocess.check_call(['juju', 'deploy', '-m', model, bundle])
def deploy(bundle, model, wait=True):
"""Run all steps to complete deployment
:param bundle: Path to bundle file
:type bundle: str
:param model: Name of model to deploy bundle in
:type model: str
:param wait: Whether to wait until deployment completes
:type model: bool
"""
deploy_bundle(bundle, model)
if wait:
logging.info("Waiting for environment to settle")
juju_wait.wait()
def parse_args(args):
"""Parse command line arguments
:param args: List of configure functions functions
:type list: [str1, str2,...] List of command line arguments
:returns: Parsed arguments
:rtype: Namespace
"""
parser = argparse.ArgumentParser()
parser.add_argument('-m', '--model',
help='Model to deploy to',
required=True)
parser.add_argument('-b', '--bundle',
help='Bundle name (excluding file ext)',
required=True)
parser.add_argument('--no-wait', dest='wait',
help='Do not wait for deployment to settle',
action='store_false')
parser.set_defaults(wait=True)
return parser.parse_args(args)
def main():
"""Deploy bundle"""
logging.basicConfig(level=logging.INFO)
args = parse_args(sys.argv[1:])
deploy_bundle(args.bundle, args.model, wait=args.wait)
+44
View File
@@ -0,0 +1,44 @@
import argparse
import logging
import subprocess
import sys
def destroy_model(model_name):
"""Remove a model with the given name
:param model: Name of model to remove
:type bundle: str
"""
logging.info("Remove model {}".format(model_name))
subprocess.check_call(['juju', 'destroy-model', '--yes', model_name])
def destroy(model_name):
"""Run all steps to cleaup after a test run
:param model: Name of model to remove
:type bundle: str
"""
destroy_model(model_name)
def parse_args(args):
"""Parse command line arguments
:param args: List of configure functions functions
:type list: [str1, str2,...] List of command line arguments
:returns: Parsed arguments
:rtype: Namespace
"""
parser = argparse.ArgumentParser()
parser.add_argument('-m', '--model-name', help='Name of model to remove',
required=True)
return parser.parse_args(args)
def main():
"""Cleanup after test run"""
logging.basicConfig(level=logging.INFO)
args = parse_args(sys.argv[1:])
destroy(args.model_name)
+38
View File
@@ -0,0 +1,38 @@
import datetime
import os
import zaza.charm_lifecycle.configure as configure
import zaza.charm_lifecycle.destroy as destroy
import zaza.charm_lifecycle.utils as utils
import zaza.charm_lifecycle.prepare as prepare
import zaza.charm_lifecycle.deploy as deploy
import zaza.charm_lifecycle.test as test
def generate_model_name(charm_name, bundle_name):
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
return '{}{}{}'.format(charm_name, bundle_name, timestamp)
def func_test_runner():
"""Deploy the bundles and run the tests as defined by the charms tests.yaml
"""
test_config = utils.get_charm_config()
for t in test_config['gate_bundles']:
model_name = generate_model_name(test_config['charm_name'], t)
# Prepare
prepare.prepare(model_name)
# Deploy
deploy.deploy(
os.path.join(utils.BUNDLE_DIR, '{}.yaml'.format(t)),
model_name)
# Configure
configure.configure(test_config['configure'])
# Test
test.test(test_config['tests'])
# Destroy
destroy.destroy(model_name)
def main():
func_test_runner()
+44
View File
@@ -0,0 +1,44 @@
import argparse
import logging
import subprocess
import sys
def add_model(model_name):
"""Add a model with the given name
:param model: Name of model to add
:type bundle: str
"""
logging.info("Adding model {}".format(model_name))
subprocess.check_call(['juju', 'add-model', model_name])
def prepare(model_name):
"""Run all steps to prepare the environment before a functional test run
:param model: Name of model to add
:type bundle: str
"""
add_model(model_name)
def parse_args(args):
"""Parse command line arguments
:param args: List of configure functions functions
:type list: [str1, str2,...] List of command line arguments
:returns: Parsed arguments
:rtype: Namespace
"""
parser = argparse.ArgumentParser()
parser.add_argument('-m', '--model-name', help='Name of model to remove',
required=True)
return parser.parse_args(args)
def main():
"""Add a new model"""
logging.basicConfig(level=logging.INFO)
args = parse_args(sys.argv[1:])
prepare(args.model_name)
+49
View File
@@ -0,0 +1,49 @@
import argparse
import logging
import unittest
import sys
import zaza.charm_lifecycle.utils as utils
def run_test_list(tests):
"""Run the tests as defined in the list of test classes in series.
:param tests: List of test class strings
:type tests: ['zaza.charms_tests.svc.TestSVCClass1', ...]
:raises: AssertionError if test run fails
"""
for _testcase in tests:
testcase = utils.get_class(_testcase)
suite = unittest.TestLoader().loadTestsFromTestCase(testcase)
test_result = unittest.TextTestRunner(verbosity=2).run(suite)
assert test_result.wasSuccessful(), "Test run failed"
def test(tests):
"""Run all steps to execute tests against the model"""
run_test_list(tests)
def parse_args(args):
"""Parse command line arguments
:param args: List of configure functions functions
:type list: [str1, str2,...] List of command line arguments
:returns: Parsed arguments
:rtype: Namespace
"""
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--tests', nargs='+',
help='Space sperated list of test classes',
required=False)
return parser.parse_args(args)
def main():
"""Run the tests defined by the command line args or if none were provided
read the tests from the charms tests.yaml config file"""
logging.basicConfig(level=logging.INFO)
args = parse_args(sys.argv[1:])
tests = args.tests or utils.get_charm_config()['tests']
test(tests)
+36
View File
@@ -0,0 +1,36 @@
import importlib
import yaml
BUNDLE_DIR = "./tests/bundles/"
DEFAULT_TEST_CONFIG = "./tests/tests.yaml"
def get_charm_config(yaml_file=None):
"""Read the yaml test config file and return the resulting config
:param yaml_file: File to be read
:type yaml_file: str
:returns: Config dictionary
:rtype: dict
"""
if not yaml_file:
yaml_file = DEFAULT_TEST_CONFIG
with open(yaml_file, 'r') as stream:
return yaml.load(stream)
def get_class(class_str):
"""Get the class represented by the given string
For example, get_class('zaza.charms_tests.svc.TestSVCClass1')
returns zaza.charms_tests.svc.TestSVCClass1
:param class_str: Class to be returned
:type class_str: str
:returns: Test class
:rtype: class
"""
module_name = '.'.join(class_str.split('.')[:-1])
class_name = class_str.split('.')[-1]
module = importlib.import_module(module_name)
return getattr(module, class_name)
View File
+15
View File
@@ -0,0 +1,15 @@
import logging
import zaza.model
def skipIfNotHA(service_name):
def _skipIfNotHA_inner_1(f):
def _skipIfNotHA_inner_2(*args, **kwargs):
if len(zaza.model.get_app_ips(service_name)) > 1:
return f(*args, **kwargs)
else:
logging.warn("Skipping HA test for non-ha service {}".format(
service_name))
return _skipIfNotHA_inner_2
return _skipIfNotHA_inner_1
-3
View File
@@ -1,6 +1,3 @@
import logging
import sys
from juju import loop
from juju.model import Model