Merge pull request #1 from gnuoy/functest-utils
Add helps for charm functional tests
This commit is contained in:
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
Executable
+63
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -1,6 +1,3 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from juju import loop
|
||||
from juju.model import Model
|
||||
|
||||
|
||||
Reference in New Issue
Block a user