modify base zaza poackage into zaza.openstack package

This commit is contained in:
Chris MacNaughton
2019-05-08 13:38:33 +02:00
parent ebf9aa085e
commit 0c2f36fb4e
29 changed files with 21 additions and 4482 deletions

2
.gitignore vendored
View File

@@ -3,7 +3,7 @@
build/
dist/
.local
zaza.egg-info/
zaza.openstack.egg-info/
.coverage
.vscode/
# Sphinx

View File

@@ -6,23 +6,5 @@ python:
install: pip install tox-travis
env:
- ENV=pep8
- ENV=py3
- ENV=func-travis
comment: |
install dependencies in script phase saving time on simpler test environments
sudo back to ourself to activate lxd group membership executable search path
script:
- if [ $ENV = 'func-travis' ]; then
sudo apt remove -y --purge lxd lxd-client;
sudo snap install lxd;
sudo snap install juju --classic;
sudo sh -c 'echo PATH=/snap/bin:$PATH >> /etc/environment';
sudo lxd waitready;
sudo lxd init --auto;
sudo usermod -a -G lxd travis;
sudo su travis -c 'juju bootstrap --debug --no-gui localhost';
fi
- tox -c tox.ini -e $ENV
- if [ $ENV = 'func-travis' ]; then
sudo su travis -c 'juju status -m $(juju models --format yaml|grep "^- name:.*zaza"|cut -f2 -d/)';
fi
- tox -c tox.ini -e $ENV

View File

@@ -1,3 +1,4 @@
aiounittest
async_generator
juju
@@ -35,3 +36,4 @@ paramiko
# Documentation requirements
sphinx
sphinxcontrib-asyncio
git+https://github.com/openstack-charmers/zaza#egg=zaza

View File

@@ -1,12 +1,12 @@
[metadata]
name = zaza
summary = A Python3-only functional test framework for OpenStack Charms
version = 0.0.2.dev1
name = zaza.openstack
summary = Zaza tests for the OpenStack Charms project
version = 0.0.1.dev1
description-file =
README.rst
author = OpenStack Charmers
author-email = openstack-charmers@lists.ubuntu.com
url = https://github.com/openstack-charmers/zaza
url = https://github.com/openstack-charmers/zaza-openstack-tests
classifier =
Development Status :: 2 - Pre-Alpha
Intended Audience :: Developers
@@ -26,18 +26,18 @@ all_files = 1
upload-dir = doc/build/html
[compile_catalog]
directory = zaza/locale
domain = zaza
directory = zaza_openstack_tests/locale
domain = zaza_openstack_tests
[update_catalog]
domain = zaza
output_dir = zaza/locale
input_file = zaza/locale/zaza.pot
domain = zaza_openstack_tests
output_dir = zaza_openstack_tests/locale
input_file = zaza_openstack_tests/locale/zaza.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = zaza/locale/zaza.pot
output_file = zaza_openstack_tests/locale/zaza.pot
[nosetests]
nologcapture=1
nologcapture=1

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module used to setup the zaza framework."""
"""Module used to setup the zaza framework tests."""
from __future__ import print_function
@@ -41,6 +41,7 @@ install_require = [
'python-octaviaclient',
'python-cinderclient',
'python-swiftclient',
'zaza@git+https://github.com/openstack-charmers/zaza.git#egg=zaza',
]
tests_require = [
@@ -92,19 +93,6 @@ if sys.argv[-1] == 'tag':
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',
'remove-placement = zaza.openstack.utilities.bundle:main',
]
},
license='Apache-2.0: http://www.apache.org/licenses/LICENSE-2.0',
packages=find_packages(exclude=["unit_tests"]),
zip_safe=False,
@@ -114,4 +102,4 @@ setup(
'testing': tests_require,
},
tests_require=tests_require,
)
)

29
tox.ini
View File

@@ -1,5 +1,5 @@
[tox]
envlist = pep8,py3
envlist = pep8, py3
skipsdist = True
[testenv]
@@ -7,6 +7,7 @@ setenv = VIRTUAL_ENV={envdir}
PYTHONHASHSEED=0
install_command =
pip install {opts} {packages}
commands = nosetests --with-coverage --cover-package=zaza {posargs} {toxinidir}/unit_tests
[testenv:py3]
@@ -33,28 +34,4 @@ basepython = python3
changedir = doc/source
deps =
-r{toxinidir}/requirements.txt
commands = sphinx-build -W -b html -d {toxinidir}/doc/build/doctrees . {toxinidir}/doc/build/html
[testenv:func]
basepython = python3
deps = -r{toxinidir}/requirements.txt
commands =
{envdir}/bin/python3 setup.py install
functest-run-suite --keep-model
[testenv:func-travis]
basepython = python3
deps = -r{toxinidir}/requirements.txt
# sudo back to ourself to activate lxd group membership executable search path
whitelist_externals = sudo
passenv = USER
commands =
{envdir}/bin/python3 setup.py install
sudo su {env:USER} -c 'source {envdir}/bin/activate && functest-run-suite --keep-model'
[testenv:remove-placement]
basepython = python3
deps = -r{toxinidir}/requirements.txt
commands =
{envdir}/bin/python3 setup.py install
remove-placement {posargs}
commands = sphinx-build -W -b html -d {toxinidir}/doc/build/doctrees . {toxinidir}/doc/build/html

View File

@@ -1,51 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
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('modelname', [mock1, mock2])
self.run_configure_list.assert_called_once_with([mock1, mock2])
def test_parser(self):
args = lc_configure.parse_args(
['-m', 'modelname', '-c', 'my.func1', 'my.func2'])
self.assertEqual(args.configfuncs, ['my.func1', 'my.func2'])
self.assertEqual(args.model_name, 'modelname')
def test_parser_logging(self):
# Using defaults
args = lc_configure.parse_args(['-m', 'model'])
self.assertEqual(args.loglevel, 'INFO')
# Using args
args = lc_configure.parse_args(['-m', 'model', '--log', 'DEBUG'])
self.assertEqual(args.loglevel, 'DEBUG')

View File

@@ -1,280 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
import jinja2
import mock
import zaza.charm_lifecycle.deploy as lc_deploy
import unit_tests.utils as ut_utils
class TestCharmLifecycleDeploy(ut_utils.BaseTestCase):
def test_is_valid_env_key(self):
self.assertTrue(lc_deploy.is_valid_env_key('OS_VIP04'))
self.assertTrue(lc_deploy.is_valid_env_key('FIP_RANGE'))
self.assertTrue(lc_deploy.is_valid_env_key('GATEWAY'))
self.assertTrue(lc_deploy.is_valid_env_key('NAME_SERVER'))
self.assertTrue(lc_deploy.is_valid_env_key('NET_ID'))
self.assertTrue(lc_deploy.is_valid_env_key('VIP_RANGE'))
self.assertTrue(lc_deploy.is_valid_env_key('AMULET_OS_VIP'))
self.assertFalse(lc_deploy.is_valid_env_key('ZAZA_TEMPLATE_VIP00'))
self.assertFalse(lc_deploy.is_valid_env_key('PATH'))
def test_get_template_context_from_env(self):
self.patch_object(lc_deploy.os, 'environ')
self.environ.items.return_value = [
('AMULET_OS_VIP', '10.10.0.2'),
('OS_VIP04', '10.10.0.2'),
('ZAZA_TEMPLATE_VIP00', '20.3.4.5'),
('PATH', 'aa')]
self.assertEqual(
lc_deploy.get_template_context_from_env(),
{'OS_VIP04': '10.10.0.2',
'AMULET_OS_VIP': '10.10.0.2'}
)
def test_get_charm_config_context(self):
self.patch_object(lc_deploy.utils, 'get_charm_config')
self.get_charm_config.return_value = {
'charm_name': 'mycharm'}
self.assertEqual(
lc_deploy.get_charm_config_context(),
{'charm_location': '../../../mycharm', 'charm_name': 'mycharm'})
def test_get_template_overlay_context(self):
self.patch_object(lc_deploy, 'get_template_context_from_env')
self.patch_object(lc_deploy, 'get_charm_config_context')
self.get_template_context_from_env.return_value = {
'OS_VIP04': '10.10.0.2'}
self.get_charm_config_context.return_value = {
'charm_location': '../../../mycharm',
'charm_name': 'mycharm'}
self.assertEqual(
lc_deploy.get_template_overlay_context(),
{
'OS_VIP04': '10.10.0.2',
'charm_location': '../../../mycharm',
'charm_name': 'mycharm'})
def test_get_overlay_template_dir(self):
self.assertEqual(
lc_deploy.get_overlay_template_dir(),
'tests/bundles/overlays')
def test_get_jinja2_env(self):
self.patch_object(lc_deploy, 'get_overlay_template_dir')
self.get_overlay_template_dir.return_value = 'mytemplatedir'
self.patch_object(lc_deploy.jinja2, 'Environment')
self.patch_object(lc_deploy.jinja2, 'FileSystemLoader')
jinja_env_mock = mock.MagicMock()
self.Environment.return_value = jinja_env_mock
self.assertEqual(
lc_deploy.get_jinja2_env(),
jinja_env_mock)
self.FileSystemLoader.assert_called_once_with('mytemplatedir')
def test_get_template_name(self):
self.assertEqual(
lc_deploy.get_template_name('mybundles/mybundle.yaml'),
'mybundle.yaml.j2')
def test_get_template(self):
self.patch_object(lc_deploy, 'get_jinja2_env')
jinja_env_mock = mock.MagicMock()
self.get_jinja2_env.return_value = jinja_env_mock
jinja_env_mock.get_template.return_value = 'mytemplate'
self.assertEqual(
lc_deploy.get_template('mybundle.yaml'),
'mytemplate')
def test_get_template_missing_template(self):
self.patch_object(lc_deploy, 'get_jinja2_env')
jinja_env_mock = mock.MagicMock()
self.get_jinja2_env.return_value = jinja_env_mock
jinja_env_mock.get_template.side_effect = \
jinja2.exceptions.TemplateNotFound(name='bob')
self.assertIsNone(lc_deploy.get_template('mybundle.yaml'))
def test_render_template(self):
self.patch_object(lc_deploy, 'get_template_overlay_context')
template_mock = mock.MagicMock()
template_mock.render.return_value = 'Template contents'
m = mock.mock_open()
with mock.patch('zaza.charm_lifecycle.deploy.open', m, create=True):
lc_deploy.render_template(template_mock, '/tmp/mybundle.yaml')
m.assert_called_once_with('/tmp/mybundle.yaml', 'w')
handle = m()
handle.write.assert_called_once_with('Template contents')
def test_render_overlay(self):
self.patch_object(lc_deploy, 'render_template')
template_mock = mock.MagicMock()
self.patch_object(lc_deploy, 'get_template')
self.get_template.return_value = template_mock
lc_deploy.render_overlay('my_overlay.yaml', '/tmp/special-dir')
self.render_template.assert_called_once_with(
template_mock,
'/tmp/special-dir/my_overlay.yaml')
def test_template_missing_required_variables(self):
self.patch_object(lc_deploy, 'get_template_overlay_context')
self.get_template_overlay_context.return_value = {}
self.patch_object(lc_deploy.sys, 'exit')
self.patch_object(lc_deploy.logging, 'error')
jinja2_env = lc_deploy.get_jinja2_env()
template = jinja2_env.from_string('{{required_variable}}')
m = mock.mock_open()
with mock.patch('zaza.charm_lifecycle.deploy.open', m, create=True):
lc_deploy.render_template(template, '/tmp/mybundle.yaml')
m.assert_called_once_with('/tmp/mybundle.yaml', 'w')
self.error.assert_called_once_with(
"Template error. You may be missing"
" a mandatory environment variable : "
"'required_variable' is undefined")
self.exit.assert_called_once_with(1)
def test_render_overlay_no_template(self):
self.patch_object(lc_deploy, 'get_template')
self.get_template.return_value = None
self.assertIsNone(lc_deploy.render_overlay('mybundle.yaml', '/tmp/'))
def test_render_local_overlay(self):
self.patch_object(lc_deploy.utils, 'get_charm_config')
self.get_charm_config.return_value = {
'charm_name': 'mycharm'}
self.patch_object(lc_deploy.jinja2, 'Environment')
self.patch_object(lc_deploy, 'get_template', return_value='atemplate')
self.patch_object(lc_deploy, 'render_template')
self.assertEqual(
lc_deploy.render_local_overlay('/target'),
'/target/local-charm-overlay.yaml')
self.assertFalse(self.Environment.called)
self.render_template.assert_called_once_with(
'atemplate',
'/target/local-charm-overlay.yaml')
def test_render_local_overlay_default(self):
jenv_mock = mock.MagicMock()
jenv_mock.from_string.return_value = 'atemplate'
self.patch_object(lc_deploy.utils, 'get_charm_config')
self.get_charm_config.return_value = {
'charm_name': 'mycharm'}
self.patch_object(lc_deploy.jinja2, 'Environment',
return_value=jenv_mock)
self.patch_object(lc_deploy, 'get_template', return_value=None)
self.patch_object(lc_deploy, 'render_template')
self.assertEqual(
lc_deploy.render_local_overlay('/target'),
'/target/local-charm-overlay.yaml')
jenv_mock.from_string.assert_called_once_with(mock.ANY)
self.render_template.assert_called_once_with(
'atemplate',
'/target/local-charm-overlay.yaml')
def test_render_overlays(self):
RESP = {
'mybundles/mybundle.yaml': '/tmp/mybundle.yaml'}
self.patch_object(lc_deploy, 'render_local_overlay')
self.render_local_overlay.return_value = '/tmp/local-overlay.yaml'
self.patch_object(lc_deploy, 'render_overlay')
self.render_overlay.side_effect = lambda x, y: RESP[x]
self.assertEqual(
lc_deploy.render_overlays('mybundles/mybundle.yaml', '/tmp'),
['/tmp/local-overlay.yaml', '/tmp/mybundle.yaml'])
def test_render_overlays_missing(self):
RESP = {'mybundles/mybundle.yaml': None}
self.patch_object(lc_deploy, 'render_overlay')
self.patch_object(lc_deploy, 'render_local_overlay')
self.render_local_overlay.return_value = '/tmp/local.yaml'
self.render_overlay.side_effect = lambda x, y: RESP[x]
self.assertEqual(
lc_deploy.render_overlays('mybundles/mybundle.yaml', '/tmp'),
['/tmp/local.yaml'])
def test_deploy_bundle(self):
self.patch_object(lc_deploy.utils, 'get_charm_config')
self.get_charm_config.return_value = {}
self.patch_object(lc_deploy, 'render_overlays')
self.patch_object(lc_deploy.subprocess, 'check_call')
self.render_overlays.return_value = []
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.zaza.model, 'wait_for_application_states')
self.patch_object(lc_deploy.utils, 'get_charm_config')
self.get_charm_config.return_value = {}
self.patch_object(lc_deploy, 'deploy_bundle')
lc_deploy.deploy('bun.yaml', 'newmodel')
self.deploy_bundle.assert_called_once_with('bun.yaml', 'newmodel')
self.wait_for_application_states.assert_called_once_with(
'newmodel',
{})
def test_deploy_bespoke_states(self):
self.patch_object(lc_deploy.zaza.model, 'wait_for_application_states')
self.patch_object(lc_deploy.utils, 'get_charm_config')
self.get_charm_config.return_value = {
'target_deploy_status': {
'vault': {
'workload-status': 'blocked',
'workload-status-message': 'Vault needs to be inited'}}}
self.patch_object(lc_deploy, 'deploy_bundle')
lc_deploy.deploy('bun.yaml', 'newmodel')
self.deploy_bundle.assert_called_once_with('bun.yaml', 'newmodel')
self.wait_for_application_states.assert_called_once_with(
'newmodel',
{'vault': {
'workload-status': 'blocked',
'workload-status-message': 'Vault needs to be inited'}})
def test_deploy_nowait(self):
self.patch_object(lc_deploy.zaza.model, 'wait_for_application_states')
self.patch_object(lc_deploy, 'deploy_bundle')
lc_deploy.deploy('bun.yaml', 'newmodel', wait=False)
self.deploy_bundle.assert_called_once_with('bun.yaml', 'newmodel')
self.assertFalse(self.wait_for_application_states.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)
def test_parser_logging(self):
args = lc_deploy.parse_args([
'-m', 'mymodel',
'-b', 'bun.yaml'
])
# Using defaults
self.assertEqual(args.loglevel, 'INFO')
# Specify the parameter
args = lc_deploy.parse_args([
'-m', 'mymodel',
'-b', 'bun.yaml',
'--log', 'DEBUG'
])
self.assertEqual(args.loglevel, 'DEBUG')

View File

@@ -1,36 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
import zaza.charm_lifecycle.destroy as lc_destroy
import unit_tests.utils as ut_utils
class TestCharmLifecycleDestroy(ut_utils.BaseTestCase):
def test_destroy(self):
self.patch_object(lc_destroy.zaza.controller, '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')
def test_parser_logging(self):
# Using defaults
args = lc_destroy.parse_args(['-m', 'doomed'])
self.assertEqual(args.loglevel, 'INFO')
# Using args
args = lc_destroy.parse_args(['-m', 'doomed', '--log', 'DEBUG'])
self.assertEqual(args.loglevel, 'DEBUG')

View File

@@ -1,250 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
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_parser(self):
# Test defaults
args = lc_func_test_runner.parse_args([])
self.assertFalse(args.keep_model)
self.assertFalse(args.smoke)
self.assertFalse(args.dev)
self.assertIsNone(args.bundle)
# Test flags
args = lc_func_test_runner.parse_args(['--keep-model'])
self.assertTrue(args.keep_model)
args = lc_func_test_runner.parse_args(['--smoke'])
self.assertTrue(args.smoke)
args = lc_func_test_runner.parse_args(['--dev'])
self.assertTrue(args.dev)
args = lc_func_test_runner.parse_args(['--bundle', 'mybundle'])
self.assertEqual(args.bundle, 'mybundle')
args = lc_func_test_runner.parse_args(['--log', 'DEBUG'])
self.assertEqual(args.loglevel, 'DEBUG')
def test_func_test_runner(self):
self.patch_object(lc_func_test_runner.utils, 'get_charm_config')
self.patch_object(lc_func_test_runner.utils, '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'],
'smoke_bundles': ['bundle2'],
'dev_bundles': ['bundle3', 'bundle4'],
'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('newmodel', [
'zaza.charm_tests.mycharm.setup.basic_setup'
'zaza.charm_tests.othercharm.setup.setup']),
mock.call('newmodel', [
'zaza.charm_tests.mycharm.setup.basic_setup'
'zaza.charm_tests.othercharm.setup.setup'])]
test_calls = [
mock.call('newmodel', [
'zaza.charm_tests.mycharm.tests.SmokeTest',
'zaza.charm_tests.mycharm.tests.ComplexTest']),
mock.call('newmodel', [
'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)
def test_func_test_runner_smoke(self):
self.patch_object(lc_func_test_runner.utils, 'get_charm_config')
self.patch_object(lc_func_test_runner.utils, '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'],
'smoke_bundles': ['bundle2'],
'dev_bundles': ['bundle3', 'bundle4'],
'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(smoke=True)
deploy_calls = [
mock.call('./tests/bundles/bundle2.yaml', 'newmodel')]
self.deploy.assert_has_calls(deploy_calls)
def test_func_test_runner_dev(self):
self.patch_object(lc_func_test_runner.utils, 'get_charm_config')
self.patch_object(lc_func_test_runner.utils, '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'],
'smoke_bundles': ['bundle2'],
'dev_bundles': ['bundle3', 'bundle4'],
'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(dev=True)
deploy_calls = [
mock.call('./tests/bundles/bundle3.yaml', 'newmodel'),
mock.call('./tests/bundles/bundle4.yaml', 'newmodel')]
self.deploy.assert_has_calls(deploy_calls)
def test_func_test_runner_specify_bundle(self):
self.patch_object(lc_func_test_runner.utils, 'get_charm_config')
self.patch_object(lc_func_test_runner.utils, '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'],
'smoke_bundles': ['bundle2'],
'dev_bundles': ['bundle3', 'bundle4'],
'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(bundle='maveric-filebeat')
deploy_calls = [
mock.call('./tests/bundles/maveric-filebeat.yaml', 'newmodel')]
self.deploy.assert_has_calls(deploy_calls)
def test_main_loglevel(self):
self.patch_object(lc_func_test_runner, 'parse_args')
self.patch_object(lc_func_test_runner, 'logging')
self.patch_object(lc_func_test_runner, 'func_test_runner')
self.patch_object(lc_func_test_runner, 'asyncio')
_args = mock.Mock()
_args.loglevel = 'DeBuG'
_args.dev = False
_args.smoke = False
self.parse_args.return_value = _args
self.logging.DEBUG = 10
lc_func_test_runner.main()
self.logging.basicConfig.assert_called_with(level=10)
def test_main_loglevel_invalid(self):
self.patch_object(lc_func_test_runner, 'parse_args')
self.patch_object(lc_func_test_runner, 'logging')
self.patch_object(lc_func_test_runner, 'func_test_runner')
self.patch_object(lc_func_test_runner, 'asyncio')
_args = mock.Mock()
_args.loglevel = 'invalid'
self.parse_args.return_value = _args
with self.assertRaises(ValueError) as context:
lc_func_test_runner.main()
self.assertEqual(
'Invalid log level: "invalid"',
str(context.exception))
self.assertFalse(self.logging.basicConfig.called)
def test_main_smoke_dev_ambiguous(self):
self.patch_object(lc_func_test_runner, 'parse_args')
self.patch_object(lc_func_test_runner, 'logging')
self.patch_object(lc_func_test_runner, 'func_test_runner')
self.patch_object(lc_func_test_runner, 'asyncio')
_args = mock.Mock()
_args.loglevel = 'DEBUG'
_args.dev = True
_args.smoke = True
self.parse_args.return_value = _args
self.logging.DEBUG = 10
with self.assertRaises(ValueError) as context:
lc_func_test_runner.main()
self.assertEqual(
'Ambiguous arguments: --smoke and --dev cannot be used together',
str(context.exception))
def test_main_bundle_dev_ambiguous(self):
self.patch_object(lc_func_test_runner, 'parse_args')
self.patch_object(lc_func_test_runner, 'logging')
self.patch_object(lc_func_test_runner, 'func_test_runner')
self.patch_object(lc_func_test_runner, 'asyncio')
_args = mock.Mock()
_args.loglevel = 'DEBUG'
_args.dev = True
_args.smoke = False
_args.bundle = 'foo.yaml'
self.parse_args.return_value = _args
self.logging.DEBUG = 10
with self.assertRaises(ValueError) as context:
lc_func_test_runner.main()
self.assertEqual(
('Ambiguous arguments: --bundle and --dev '
'cannot be used together'),
str(context.exception))
def test_main_bundle_smoke_ambiguous(self):
self.patch_object(lc_func_test_runner, 'parse_args')
self.patch_object(lc_func_test_runner, 'logging')
self.patch_object(lc_func_test_runner, 'func_test_runner')
self.patch_object(lc_func_test_runner, 'asyncio')
_args = mock.Mock()
_args.loglevel = 'DEBUG'
_args.dev = False
_args.smoke = True
_args.bundle = 'foo.yaml'
self.parse_args.return_value = _args
self.logging.DEBUG = 10
with self.assertRaises(ValueError) as context:
lc_func_test_runner.main()
self.assertEqual(
('Ambiguous arguments: --bundle and --smoke '
'cannot be used together'),
str(context.exception))

View File

@@ -1,103 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
import copy
import mock
import zaza.charm_lifecycle.prepare as lc_prepare
import unit_tests.utils as ut_utils
class TestCharmLifecyclePrepare(ut_utils.BaseTestCase):
MODEL_CONFIG_DEFAULTS = lc_prepare.MODEL_DEFAULTS
def base_parse_option_list_string(self, env, expect):
with mock.patch.dict(lc_prepare.os.environ, env):
self.assertEqual(lc_prepare.get_model_settings(), expect)
def test_parse_option_list_string_empty_config(self):
self.assertEqual(
lc_prepare.parse_option_list_string(option_list=""),
{})
def test_parse_option_list_string_single_value(self):
self.assertEqual(
lc_prepare.parse_option_list_string(
option_list='image-stream=released'),
{'image-stream': 'released'})
def test_parse_option_list_string_multiple_values(self):
self.assertEqual(
lc_prepare.parse_option_list_string(
option_list='image-stream=released;no-proxy=jujucharms.com'),
{
'image-stream': 'released',
'no-proxy': 'jujucharms.com'})
def test_parse_option_list_string_whitespace(self):
self.assertEqual(
lc_prepare.parse_option_list_string(
option_list=' test-mode= false ; image-stream= released'),
{
'test-mode': 'false',
'image-stream': 'released'})
def test_get_model_settings_no_config(self):
self.base_parse_option_list_string({}, self.MODEL_CONFIG_DEFAULTS)
def test_get_model_settings_multiple_values_override(self):
expect_config = copy.deepcopy(self.MODEL_CONFIG_DEFAULTS)
expect_config.update({'test-mode': 'false'})
self.base_parse_option_list_string(
{'MODEL_SETTINGS': 'test-mode=false'},
expect_config)
def test_prepare(self):
self.patch_object(lc_prepare.zaza.controller, 'add_model')
self.patch_object(lc_prepare, 'get_model_settings')
self.patch_object(lc_prepare, 'get_model_constraints')
self.patch_object(lc_prepare.zaza.model, 'set_model_constraints')
self.get_model_settings.return_value = lc_prepare.MODEL_DEFAULTS
self.get_model_constraints.return_value = {'image-stream': 'released'}
lc_prepare.prepare('newmodel')
self.add_model.assert_called_once_with(
'newmodel',
config={
'default-series': 'xenial',
'image-stream': 'daily',
'test-mode': 'true',
'transmit-vendor-metrics': 'false',
'enable-os-upgrade': 'false',
'automatically-retry-hooks': 'false',
'use-default-secgroup': 'true'})
self.set_model_constraints.assert_called_once_with(
constraints={'image-stream': 'released'},
model_name='newmodel')
def test_parser(self):
args = lc_prepare.parse_args([])
self.assertTrue(args.model_name.startswith('zaza-'))
def test_parser_model(self):
args = lc_prepare.parse_args(['-m', 'newmodel'])
self.assertEqual(args.model_name, 'newmodel')
def test_parser_logging(self):
# Using defaults
args = lc_prepare.parse_args(['-m', 'model'])
self.assertEqual(args.loglevel, 'INFO')
# Using args
args = lc_prepare.parse_args(['-m', 'model', '--log', 'DEBUG'])
self.assertEqual(args.loglevel, 'DEBUG')

View File

@@ -1,60 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
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(
['-m', 'modelname', '-t', 'my.test_class1', 'my.test_class2'])
self.assertEqual(
args.tests,
['my.test_class1', 'my.test_class2'])
self.assertEqual(args.model_name, 'modelname')
def test_parser_logging(self):
# Using defaults
args = lc_test.parse_args(['-m', 'model'])
self.assertEqual(args.loglevel, 'INFO')
# Using args
args = lc_test.parse_args(['-m', 'model', '--log', 'DEBUG'])
self.assertEqual(args.loglevel, 'DEBUG')

View File

@@ -1,52 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
import mock
import zaza.charm_lifecycle.utils as lc_utils
import unit_tests.utils as ut_utils
class TestCharmLifecycleUtils(ut_utils.BaseTestCase):
def test_generate_model_name(self):
self.patch_object(lc_utils.uuid, "uuid4")
self.uuid4.return_value = "longer-than-12characters"
self.assertEqual(lc_utils.generate_model_name(),
"zaza-12characters")
def test_get_charm_config(self):
self.patch("builtins.open",
new_callable=mock.mock_open(),
name="_open")
self.patch_object(lc_utils, 'yaml')
_yaml = "testconfig: someconfig"
_yaml_dict = {'test_config': 'someconfig'}
self.yaml.safe_load.return_value = _yaml_dict
_filename = "filename"
_fileobj = mock.MagicMock()
_fileobj.__enter__.return_value = _yaml
self._open.return_value = _fileobj
self.assertEqual(lc_utils.get_charm_config(yaml_file=_filename),
_yaml_dict)
self._open.assert_called_once_with(_filename, "r")
self.yaml.safe_load.assert_called_once_with(_yaml)
def test_get_class(self):
self.assertEqual(
type(lc_utils.get_class('unit_tests.'
'test_zaza_charm_lifecycle_utils.'
'TestCharmLifecycleUtils')()),
type(self))

View File

@@ -1,109 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
import mock
import unit_tests.utils as ut_utils
import zaza.controller as controller
class TestController(ut_utils.BaseTestCase):
def setUp(self):
super(TestController, self).setUp()
async def _disconnect():
return
async def _connect():
return
async def _list_models():
return self.models
async def _add_model(model_name, config=None):
return self.model1
async def _destroy_model(model_name):
return
async def _get_cloud():
return self.cloud
# Cloud
self.cloud = "FakeCloud"
# Model
self.Model_mock = mock.MagicMock()
self.Model_mock.connect.side_effect = _connect
self.Model_mock.disconnect.side_effect = _disconnect
self.Model_mock.disconnect.side_effect = _disconnect
self.model1 = self.Model_mock
self.model2 = mock.MagicMock()
self.model1.info.name = "model1"
self.model2.info.name = "model2"
self.models = [self.model1.info.name, self.model2.info.name]
# Controller
self.Controller_mock = mock.MagicMock()
self.Controller_mock.connect.side_effect = _connect
self.Controller_mock.disconnect.side_effect = _disconnect
self.Controller_mock.add_model.side_effect = _add_model
self.Controller_mock.destroy_model.side_effect = _destroy_model
self.Controller_mock.list_models.side_effect = _list_models
self.Controller_mock.get_cloud.side_effect = _get_cloud
self.controller_name = "testcontroller"
self.Controller_mock.info.name = self.controller_name
self.patch_object(controller, 'Controller')
self.Controller.return_value = self.Controller_mock
def test_add_model(self):
self.patch_object(controller, 'go_list_models')
controller.add_model(self.model1.info.name)
self.Controller_mock.add_model.assert_called_once_with(
self.model1.info.name,
config=None)
def test_add_model_config(self):
self.patch_object(controller, 'go_list_models')
controller.add_model(self.model1.info.name,
{'run-faster': 'true'})
self.Controller_mock.add_model.assert_called_once_with(
self.model1.info.name,
config={'run-faster': 'true'})
self.go_list_models.assert_called_once()
def test_destroy_model(self):
controller.destroy_model(self.model1.info.name)
self.Controller_mock.destroy_model.assert_called_once_with(
self.model1.info.name)
def test_get_cloud(self):
self.assertEqual(
controller.get_cloud(),
self.cloud)
self.Controller_mock.get_cloud.assert_called_once()
def test_list_models(self):
self.assertEqual(
controller.list_models(),
self.models)
self.Controller_mock.list_models.assert_called_once()
def test_go_list_models(self):
self.patch_object(controller, 'subprocess')
controller.go_list_models()
self.subprocess.check_call.assert_called_once_with([
"juju", "models"])

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Modules for running lifecycle phases."""

View File

@@ -1,15 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Run collect phase."""

View File

@@ -1,82 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Run configuration phase."""
import asyncio
import argparse
import logging
import sys
import zaza.model
import zaza.charm_lifecycle.utils as utils
def run_configure_list(functions):
"""Run the configure scripts.
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(model_name, functions):
"""Run all post-deployment configuration steps.
:param functions: List of configure functions functions
:type tests: ['zaza.charms_tests.svc.setup', ...]
"""
zaza.model.set_juju_model(model_name)
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 separated list of config functions',
required=False)
parser.add_argument('-m', '--model-name', help='Name of model to remove',
required=True)
parser.add_argument('--log', dest='loglevel',
help='Loglevel [DEBUG|INFO|WARN|ERROR|CRITICAL]')
parser.set_defaults(loglevel='INFO')
return parser.parse_args(args)
def main():
"""Run the configuration defined by the command line args.
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
"""
args = parse_args(sys.argv[1:])
level = getattr(logging, args.loglevel.upper(), None)
if not isinstance(level, int):
raise ValueError('Invalid log level: "{}"'.format(args.loglevel))
logging.basicConfig(level=level)
funcs = args.configfuncs or utils.get_charm_config()['configure']
configure(args.model_name, funcs)
asyncio.get_event_loop().close()

View File

@@ -1,304 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Run deploy phase."""
import argparse
import jinja2
import logging
import os
import subprocess
import sys
import tempfile
import zaza.model
import zaza.charm_lifecycle.utils as utils
DEFAULT_OVERLAY_TEMPLATE_DIR = 'tests/bundles/overlays'
VALID_ENVIRONMENT_KEY_PREFIXES = [
'FIP_RANGE',
'GATEWAY',
'NAME_SERVER',
'NET_ID',
'OS_',
'VIP_RANGE',
'AMULET_',
'MOJO_',
]
LOCAL_OVERLAY_TEMPLATE = """
applications:
{{ charm_name }}:
charm: {{ charm_location }}
"""
LOCAL_OVERLAY_TEMPLATE_NAME = 'local-charm-overlay.yaml'
def is_valid_env_key(key):
"""Check if key is a valid environment variable name for use with template.
:param key: List of configure functions functions
:type key: str
:returns: Whether key is a valid environment variable name
:rtype: bool
"""
valid = False
for _k in VALID_ENVIRONMENT_KEY_PREFIXES:
if key.startswith(_k):
valid = True
break
return valid
def get_template_context_from_env():
"""Return environment vars from the current env for template rendering.
:returns: Environment variable key values for use with template rendering
:rtype: dict
"""
return {k: v for k, v in os.environ.items() if is_valid_env_key(k)}
def get_charm_config_context():
"""Return settings from charm config file.
:returns: Context for template rendering
:rtype: dict
"""
test_config = utils.get_charm_config()
ctxt = {
'charm_name': test_config['charm_name'],
'charm_location': '../../../{}'.format(test_config['charm_name'])}
return ctxt
def get_template_overlay_context():
"""Combine contexts which can be used for overlay template rendering.
:returns: Context for template rendering
:rtype: dict
"""
context = {}
contexts = [
get_template_context_from_env(),
]
try:
contexts.append(get_charm_config_context())
except KeyError:
pass
for c in contexts:
context.update(c)
return context
def get_overlay_template_dir():
"""Return the directory to look for overlay template files in.
:returns: Overlay template file dir
:rtype: str
"""
return DEFAULT_OVERLAY_TEMPLATE_DIR
def get_jinja2_env():
"""Return a jinja2 environment that can be used to render templates from.
:returns: Jinja2 template loader
:rtype: jinja2.Environment
"""
template_dir = get_overlay_template_dir()
return jinja2.Environment(
loader=jinja2.FileSystemLoader(template_dir),
undefined=jinja2.StrictUndefined
)
def get_template_name(target_file):
"""Return the template name for target_file.
Return the expected name of the template used to generate the
target_file
:param target_file: File to be rendered
:type target_file: str
:returns: Name of template used to render target_file
:rtype: str
"""
return '{}.j2'.format(os.path.basename(target_file))
def get_template(target_file):
"""Return the jinja2 template for the given file.
:returns: Template object used to generate target_file
:rtype: jinja2.Template
"""
jinja2_env = get_jinja2_env()
try:
template = jinja2_env.get_template(get_template_name(target_file))
except jinja2.exceptions.TemplateNotFound:
template = None
return template
def render_template(template, target_file):
"""Render the template to the file supplied.
:param template: Template to be rendered
:type template: jinja2.Template
:param target_file: File name for rendered template
:type target_file: str
"""
try:
with open(target_file, "w") as fh:
fh.write(
template.render(get_template_overlay_context()))
except jinja2.exceptions.UndefinedError as e:
logging.error("Template error. You may be missing"
" a mandatory environment variable : {}".format(e))
sys.exit(1)
logging.info("Rendered template '{}' to file '{}'".format(template,
target_file))
def render_overlay(overlay_name, target_dir):
"""Render the overlay template in the directory supplied.
:param overlay_name: Name of overlay to be rendered
:type overlay_name: str
:param target_dir: Directory to render overlay in
:type overlay_name: str
:returns: Path to rendered overlay
:rtype: str
"""
template = get_template(overlay_name)
if not template:
return
rendered_template_file = os.path.join(
target_dir,
os.path.basename(overlay_name))
render_template(template, rendered_template_file)
return rendered_template_file
def render_local_overlay(target_dir):
"""Render the local overlay template in the directory supplied.
:param target_dir: Directory to render overlay in
:type overlay_name: str
:returns: Path to rendered overlay
:rtype: str
"""
template = get_template(LOCAL_OVERLAY_TEMPLATE_NAME)
if not template:
template = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
LOCAL_OVERLAY_TEMPLATE)
rendered_template_file = os.path.join(
target_dir,
os.path.basename(LOCAL_OVERLAY_TEMPLATE_NAME))
if utils.get_charm_config().get('charm_name', None):
render_template(template, rendered_template_file)
return rendered_template_file
def render_overlays(bundle, target_dir):
"""Render the overlays for the given bundle in the directory provided.
:param bundle: Name of bundle being deployed
:type bundle: str
:param target_dir: Directory to render overlay in
:type overlay_name: str
:returns: List of rendered overlays
:rtype: [str, str,...]
"""
overlays = []
local_overlay = render_local_overlay(target_dir)
if local_overlay:
overlays.append(local_overlay)
rendered_bundle_overlay = render_overlay(bundle, target_dir)
if rendered_bundle_overlay:
overlays.append(rendered_bundle_overlay)
return overlays
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 '{}' on to '{}' model"
.format(bundle, model))
cmd = ['juju', 'deploy', '-m', model, bundle]
with tempfile.TemporaryDirectory() as tmpdirname:
for overlay in render_overlays(bundle, tmpdirname):
logging.info("Deploying overlay '{}' on to '{}' model"
.format(overlay, model))
cmd.extend(['--overlay', overlay])
subprocess.check_call(cmd)
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:
test_config = utils.get_charm_config()
logging.info("Waiting for environment to settle")
zaza.model.set_juju_model(model)
zaza.model.wait_for_application_states(
model,
test_config.get('target_deploy_status', {}))
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.add_argument('--log', dest='loglevel',
help='Loglevel [DEBUG|INFO|WARN|ERROR|CRITICAL]')
parser.set_defaults(wait=True, loglevel='INFO')
return parser.parse_args(args)
def main():
"""Deploy bundle."""
args = parse_args(sys.argv[1:])
level = getattr(logging, args.loglevel.upper(), None)
if not isinstance(level, int):
raise ValueError('Invalid log level: "{}"'.format(args.loglevel))
logging.basicConfig(level=level)
deploy(args.bundle, args.model, wait=args.wait)

View File

@@ -1,56 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Run destroy phase."""
import argparse
import logging
import sys
import zaza.controller
def destroy(model_name):
"""Run all steps to cleaup after a test run.
:param model: Name of model to remove
:type bundle: str
"""
zaza.controller.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)
parser.add_argument('--log', dest='loglevel',
help='Loglevel [DEBUG|INFO|WARN|ERROR|CRITICAL]')
parser.set_defaults(loglevel='INFO')
return parser.parse_args(args)
def main():
"""Cleanup after test run."""
args = parse_args(sys.argv[1:])
level = getattr(logging, args.loglevel.upper(), None)
if not isinstance(level, int):
raise ValueError('Invalid log level: "{}"'.format(args.loglevel))
logging.basicConfig(level=level)
destroy(args.model_name)

View File

@@ -1,131 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Run full test lifecycle."""
import argparse
import asyncio
import logging
import os
import sys
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 func_test_runner(keep_model=False, smoke=False, dev=False, bundle=None):
"""Deploy the bundles and run the tests as defined by the charms tests.yaml.
:param keep_model: Whether to destroy model at end of run
:type keep_model: boolean
:param smoke: Whether to just run smoke test.
:param dev: Whether to just run dev test.
:type smoke: boolean
:type dev: boolean
"""
test_config = utils.get_charm_config()
if bundle:
bundles = [bundle]
else:
if smoke:
bundle_key = 'smoke_bundles'
elif dev:
bundle_key = 'dev_bundles'
else:
bundle_key = 'gate_bundles'
bundles = test_config[bundle_key]
last_test = bundles[-1]
for t in bundles:
model_name = utils.generate_model_name()
# Prepare
prepare.prepare(model_name)
# Deploy
deploy.deploy(
os.path.join(utils.BUNDLE_DIR, '{}.yaml'.format(t)),
model_name)
if 'configure' in test_config:
# Configure
configure.configure(model_name, test_config['configure'])
# Test
test.test(model_name, test_config['tests'])
# Destroy
# Keep the model from the last run if keep_model is true, this is to
# maintian compat with osci and should change when the zaza collect
# functions take over from osci for artifact collection.
if keep_model and t == last_test:
pass
else:
destroy.destroy(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('--keep-model', dest='keep_model',
help='Keep model at the end of the run',
action='store_true')
parser.add_argument('--smoke', dest='smoke',
help='Just run smoke test(s)',
action='store_true')
parser.add_argument('--dev', dest='dev',
help='Just run dev test(s)',
action='store_true')
parser.add_argument('-b', '--bundle', dest='bundle',
help='Override the bundle to be run',
required=False)
parser.add_argument('--log', dest='loglevel',
help='Loglevel [DEBUG|INFO|WARN|ERROR|CRITICAL]')
parser.set_defaults(keep_model=False,
smoke=False,
dev=False,
loglevel='INFO')
return parser.parse_args(args)
def main():
"""Execute full test run."""
args = parse_args(sys.argv[1:])
level = getattr(logging, args.loglevel.upper(), None)
if not isinstance(level, int):
raise ValueError('Invalid log level: "{}"'.format(args.loglevel))
logging.basicConfig(level=level)
if args.dev and args.smoke:
raise ValueError('Ambiguous arguments: --smoke and '
'--dev cannot be used together')
if args.dev and args.bundle:
raise ValueError('Ambiguous arguments: --bundle and '
'--dev cannot be used together')
if args.smoke and args.bundle:
raise ValueError('Ambiguous arguments: --bundle and '
'--smoke cannot be used together')
func_test_runner(
keep_model=args.keep_model,
smoke=args.smoke,
dev=args.dev,
bundle=args.bundle)
asyncio.get_event_loop().close()

View File

@@ -1,124 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Run prepare phase."""
import argparse
import copy
import logging
import os
import sys
import zaza.controller
import zaza.model
import zaza.charm_lifecycle.utils as utils
MODEL_DEFAULTS = {
# Model defaults from charm-test-infra
# https://jujucharms.com/docs/2.1/models-config
'default-series': 'xenial',
'image-stream': 'daily',
'test-mode': 'true',
'transmit-vendor-metrics': 'false',
# https://bugs.launchpad.net/juju/+bug/1685351
# enable-os-refresh-update: false
'enable-os-upgrade': 'false',
'automatically-retry-hooks': 'false',
'use-default-secgroup': 'true',
}
def parse_option_list_string(option_list, delimiter=None):
"""Convert the given string to a dictionary of options.
Each pair must be of the form 'k=v', the delimiter seperates the
pairs from each other not the key from the value.
:param option_list: A string representation of key value pairs.
:type option_list: str
:param delimiter: Delimiter to use to seperate each pair.
:type delimiter: str
:returns: A dictionary of settings.
:rtype: Dict
"""
settings = {}
if delimiter is None:
delimiter = ';'
for setting in option_list.split(delimiter):
if not setting:
continue
key, value = setting.split('=')
settings[key.strip()] = value.strip()
return settings
def get_model_settings():
"""Construct settings for model from defaults and env variables.
:returns: Settings to use for model
:rtype: Dict
"""
model_settings = copy.deepcopy(MODEL_DEFAULTS)
model_settings.update(
parse_option_list_string(os.environ.get('MODEL_SETTINGS', '')))
return model_settings
def get_model_constraints():
"""Construct constraints for model.
:returns: Constraints to apply to model
:rtype: Dict
"""
return parse_option_list_string(os.environ.get('MODEL_CONSTRAINTS', ''))
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
"""
zaza.controller.add_model(model_name, config=get_model_settings())
zaza.model.set_model_constraints(
model_name=model_name,
constraints=get_model_constraints())
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 add')
parser.add_argument('--log', dest='loglevel',
help='Loglevel [DEBUG|INFO|WARN|ERROR|CRITICAL]')
parser.set_defaults(loglevel='INFO')
parser.set_defaults(model_name=utils.generate_model_name())
return parser.parse_args(args)
def main():
"""Add a new model."""
args = parse_args(sys.argv[1:])
level = getattr(logging, args.loglevel.upper(), None)
if not isinstance(level, int):
raise ValueError('Invalid log level: "{}"'.format(args.loglevel))
logging.basicConfig(level=level)
print('model_name: {}'.format(args.model_name))
prepare(args.model_name)

View File

@@ -1,80 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Run test phase."""
import asyncio
import argparse
import logging
import unittest
import sys
import zaza.model
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:
logging.info('## Running Test {} ##'.format(_testcase))
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(model_name, tests):
"""Run all steps to execute tests against the model."""
zaza.model.set_juju_model(model_name)
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 separated list of test classes',
required=False)
parser.add_argument('-m', '--model-name', help='Name of model to remove',
required=True)
parser.add_argument('--log', dest='loglevel',
help='Loglevel [DEBUG|INFO|WARN|ERROR|CRITICAL]')
parser.set_defaults(loglevel='INFO')
return parser.parse_args(args)
def main():
"""Run the tests defined by the command line args.
Run the tests defined by the command line args or if none were provided
read the tests from the charms tests.yaml config file
"""
args = parse_args(sys.argv[1:])
level = getattr(logging, args.loglevel.upper(), None)
if not isinstance(level, int):
raise ValueError('Invalid log level: "{}"'.format(args.loglevel))
logging.basicConfig(level=level)
tests = args.tests or utils.get_charm_config()['tests']
test(args.model_name, tests)
asyncio.get_event_loop().close()

View File

@@ -1,65 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Utilities to support running lifecycle phases."""
import importlib
import uuid
import sys
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.safe_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
"""
old_syspath = sys.path
sys.path.append('.')
module_name = '.'.join(class_str.split('.')[:-1])
class_name = class_str.split('.')[-1]
module = importlib.import_module(module_name)
sys.path = old_syspath
return getattr(module, class_name)
def generate_model_name():
"""Generate a unique model name.
:returns: Model name
:rtype: str
"""
return 'zaza-{}'.format(str(uuid.uuid4())[-12:])

View File

@@ -1,100 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# 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.
"""Module for interacting with a juju controller."""
import logging
from juju.controller import Controller
import subprocess
from zaza import sync_wrapper
async def async_add_model(model_name, config=None):
"""Add a model to the current controller.
:param model_name: Name to give the new model.
:type model_name: str
:param config: Model configuration.
:type config: dict
"""
controller = Controller()
await controller.connect()
logging.debug("Adding model {}".format(model_name))
model = await controller.add_model(model_name, config=config)
await model.disconnect()
await controller.disconnect()
# NOTE: This is necessary to guarantee juju is aware of the newly created
# model.
go_list_models()
add_model = sync_wrapper(async_add_model)
async def async_destroy_model(model_name):
"""Remove a model from the current controller.
:param model_name: Name of model to remove
:type model_name: str
"""
controller = Controller()
await controller.connect()
logging.debug("Destroying model {}".format(model_name))
await controller.destroy_model(model_name)
await controller.disconnect()
destroy_model = sync_wrapper(async_destroy_model)
async def async_get_cloud():
"""Return the name of the current cloud.
:returns: Name of cloud
:rtype: str
"""
controller = Controller()
await controller.connect()
cloud = await controller.get_cloud()
await controller.disconnect()
return cloud
get_cloud = sync_wrapper(async_get_cloud)
async def async_list_models():
"""Return a list of tha available clouds.
:returns: List of clouds
:rtype: list
"""
controller = Controller()
await controller.connect()
models = await controller.list_models()
await controller.disconnect()
return models
list_models = sync_wrapper(async_list_models)
def go_list_models():
"""Execute juju models.
NOTE: Excuting the juju models command updates the local cache of models.
Python-juju currently does not update the local cache on add model.
https://github.com/juju/python-libjuju/issues/267
:returns: None
:rtype: None
"""
cmd = ["juju", "models"]
subprocess.check_call(cmd)

File diff suppressed because it is too large Load Diff