modify base zaza poackage into zaza.openstack package
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,7 +3,7 @@
|
||||
build/
|
||||
dist/
|
||||
.local
|
||||
zaza.egg-info/
|
||||
zaza.openstack.egg-info/
|
||||
.coverage
|
||||
.vscode/
|
||||
# Sphinx
|
||||
|
||||
20
.travis.yml
20
.travis.yml
@@ -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
|
||||
@@ -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
|
||||
22
setup.cfg
22
setup.cfg
@@ -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
|
||||
18
setup.py
18
setup.py
@@ -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
29
tox.ini
@@ -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
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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))
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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))
|
||||
@@ -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
@@ -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."""
|
||||
@@ -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."""
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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:])
|
||||
@@ -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)
|
||||
1361
zaza/model.py
1361
zaza/model.py
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user