680 lines
26 KiB
Python
680 lines
26 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# 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 os
|
|
|
|
import mock
|
|
import yaml
|
|
|
|
from armada import const
|
|
from armada.handlers import armada
|
|
from armada.tests.unit import base
|
|
from armada.handlers import helm
|
|
from armada.utils.release import release_prefixer, get_release_status
|
|
from armada.exceptions import ManifestException
|
|
from armada.exceptions.override_exceptions import InvalidOverrideValueException
|
|
from armada.exceptions.validate_exceptions import InvalidManifestException
|
|
from armada.exceptions.armada_exceptions import ChartDeployException
|
|
|
|
TEST_YAML = """
|
|
---
|
|
schema: armada/Manifest/v1
|
|
metadata:
|
|
schema: metadata/Document/v1
|
|
name: example-manifest
|
|
data:
|
|
release_prefix: armada
|
|
chart_groups:
|
|
- example-group
|
|
---
|
|
schema: armada/ChartGroup/v1
|
|
metadata:
|
|
schema: metadata/Document/v1
|
|
name: example-group
|
|
data:
|
|
description: this is a test
|
|
sequenced: True
|
|
chart_group:
|
|
- example-chart-1
|
|
- example-chart-2
|
|
- example-chart-3
|
|
- example-chart-4
|
|
---
|
|
schema: armada/Chart/v1
|
|
metadata:
|
|
schema: metadata/Document/v1
|
|
name: example-chart-4
|
|
data:
|
|
chart_name: test_chart_4
|
|
release: test_chart_4
|
|
namespace: test
|
|
values: {}
|
|
source:
|
|
type: local
|
|
location: /tmp/dummy/armada
|
|
subpath: chart_4
|
|
dependencies: []
|
|
test: true
|
|
wait:
|
|
timeout: 10
|
|
upgrade:
|
|
no_hooks: false
|
|
---
|
|
schema: armada/Chart/v1
|
|
metadata:
|
|
schema: metadata/Document/v1
|
|
name: example-chart-3
|
|
data:
|
|
chart_name: test_chart_3
|
|
release: test_chart_3
|
|
namespace: test
|
|
values: {}
|
|
source:
|
|
type: local
|
|
location: /tmp/dummy/armada
|
|
subpath: chart_3
|
|
dependencies: []
|
|
protected:
|
|
continue_processing: false
|
|
wait:
|
|
timeout: 10
|
|
upgrade:
|
|
no_hooks: false
|
|
---
|
|
schema: armada/Chart/v1
|
|
metadata:
|
|
schema: metadata/Document/v1
|
|
name: example-chart-2
|
|
data:
|
|
chart_name: test_chart_2
|
|
release: test_chart_2
|
|
namespace: test
|
|
values: {}
|
|
source:
|
|
type: local
|
|
location: /tmp/dummy/armada
|
|
subpath: chart_2
|
|
dependencies: []
|
|
protected:
|
|
continue_processing: true
|
|
wait:
|
|
timeout: 10
|
|
upgrade:
|
|
no_hooks: false
|
|
options:
|
|
force: true
|
|
test:
|
|
enabled: true
|
|
---
|
|
schema: armada/Chart/v1
|
|
metadata:
|
|
schema: metadata/Document/v1
|
|
name: example-chart-1
|
|
data:
|
|
chart_name: test_chart_1
|
|
release: test_chart_1
|
|
namespace: test
|
|
values: {}
|
|
source:
|
|
type: git
|
|
location: git://opendev.org/dummy/armada.git
|
|
subpath: chart_1
|
|
reference: master
|
|
dependencies: []
|
|
wait:
|
|
timeout: 10
|
|
native:
|
|
enabled: true
|
|
test:
|
|
enabled: true
|
|
"""
|
|
|
|
CHART_SOURCES = (
|
|
('git://opendev.org/dummy/armada.git',
|
|
'chart_1'), ('/tmp/dummy/armada', 'chart_2'),
|
|
('/tmp/dummy/armada', 'chart_3'), ('/tmp/dummy/armada', 'chart_4'))
|
|
|
|
|
|
# TODO(seaneagan): Add unit tests with dependencies, including transitive.
|
|
def set_source_dir(ch, manifest=None):
|
|
d = ch['data']
|
|
d['source_dir'] = (d['source']['location'], d['source']['subpath'])
|
|
|
|
|
|
class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
|
def _test_pre_flight_ops(self, armada_obj, MockChartDownload):
|
|
MockChartDownload.return_value.get_chart.side_effect = set_source_dir
|
|
armada_obj.pre_flight_ops()
|
|
|
|
expected_config = {
|
|
'schema': 'armada/Manifest/v1',
|
|
'metadata': {
|
|
'schema': 'metadata/Document/v1',
|
|
'name': 'example-manifest'
|
|
},
|
|
'data': {
|
|
'release_prefix': 'armada',
|
|
'chart_groups': [{
|
|
'schema': 'armada/ChartGroup/v1',
|
|
'metadata': {
|
|
'schema': 'metadata/Document/v1',
|
|
'name': 'example-group'
|
|
},
|
|
'data': {
|
|
'chart_group': [{
|
|
'schema': 'armada/Chart/v1',
|
|
'metadata': {
|
|
'schema': 'metadata/Document/v1',
|
|
'name': 'example-chart-1'
|
|
},
|
|
'data': {
|
|
'dependencies': [],
|
|
'chart_name': 'test_chart_1',
|
|
'namespace': 'test',
|
|
'release': 'test_chart_1',
|
|
'source': {
|
|
'location':
|
|
'git://opendev.org/dummy/armada.git',
|
|
'reference': 'master',
|
|
'subpath': 'chart_1',
|
|
'type': 'git'
|
|
},
|
|
'source_dir': CHART_SOURCES[0],
|
|
'values': {},
|
|
'wait': {
|
|
'timeout': 10,
|
|
'native': {
|
|
'enabled': True
|
|
}
|
|
},
|
|
'test': {
|
|
'enabled': True
|
|
}
|
|
}
|
|
}, {
|
|
'schema': 'armada/Chart/v1',
|
|
'metadata': {
|
|
'schema': 'metadata/Document/v1',
|
|
'name': 'example-chart-2'
|
|
},
|
|
'data': {
|
|
'dependencies': [],
|
|
'chart_name': 'test_chart_2',
|
|
'namespace': 'test',
|
|
'protected': {
|
|
'continue_processing': True
|
|
},
|
|
'release': 'test_chart_2',
|
|
'source': {
|
|
'location': '/tmp/dummy/armada',
|
|
'subpath': 'chart_2',
|
|
'type': 'local'
|
|
},
|
|
'source_dir': CHART_SOURCES[1],
|
|
'values': {},
|
|
'wait': {
|
|
'timeout': 10
|
|
},
|
|
'upgrade': {
|
|
'no_hooks': False,
|
|
'options': {
|
|
'force': True
|
|
}
|
|
},
|
|
'test': {
|
|
'enabled': True
|
|
}
|
|
}
|
|
}, {
|
|
'schema': 'armada/Chart/v1',
|
|
'metadata': {
|
|
'schema': 'metadata/Document/v1',
|
|
'name': 'example-chart-3'
|
|
},
|
|
'data': {
|
|
'dependencies': [],
|
|
'chart_name': 'test_chart_3',
|
|
'namespace': 'test',
|
|
'protected': {
|
|
'continue_processing': False
|
|
},
|
|
'release': 'test_chart_3',
|
|
'source': {
|
|
'location': '/tmp/dummy/armada',
|
|
'subpath': 'chart_3',
|
|
'type': 'local'
|
|
},
|
|
'source_dir': CHART_SOURCES[2],
|
|
'values': {},
|
|
'wait': {
|
|
'timeout': 10
|
|
},
|
|
'upgrade': {
|
|
'no_hooks': False
|
|
}
|
|
}
|
|
}, {
|
|
'schema': 'armada/Chart/v1',
|
|
'metadata': {
|
|
'schema': 'metadata/Document/v1',
|
|
'name': 'example-chart-4'
|
|
},
|
|
'data': {
|
|
'dependencies': [],
|
|
'chart_name': 'test_chart_4',
|
|
'namespace': 'test',
|
|
'release': 'test_chart_4',
|
|
'source': {
|
|
'location': '/tmp/dummy/armada',
|
|
'subpath': 'chart_4',
|
|
'type': 'local'
|
|
},
|
|
'source_dir': CHART_SOURCES[3],
|
|
'values': {},
|
|
'wait': {
|
|
'timeout': 10
|
|
},
|
|
'upgrade': {
|
|
'no_hooks': False
|
|
},
|
|
'test': True
|
|
}
|
|
}],
|
|
'description': 'this is a test',
|
|
'sequenced': True
|
|
}
|
|
}]
|
|
}
|
|
} # yapf: disable
|
|
|
|
self.assertTrue(hasattr(armada_obj, 'manifest'))
|
|
self.assertIsInstance(armada_obj.manifest, dict)
|
|
self.assertIn('data', armada_obj.manifest)
|
|
self.assertEqual(expected_config, armada_obj.manifest)
|
|
|
|
@mock.patch.object(armada, 'ChartDownload')
|
|
def test_pre_flight_ops(self, MockChartDownload):
|
|
"""Test pre-flight checks and operations."""
|
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
|
m_helm = mock.Mock()
|
|
armada_obj = armada.Armada(yaml_documents, m_helm)
|
|
|
|
self._test_pre_flight_ops(armada_obj, MockChartDownload)
|
|
|
|
MockChartDownload.return_value.get_chart.assert_called()
|
|
|
|
@mock.patch.object(armada, 'ChartDownload')
|
|
def test_post_flight_ops(self, MockChartDownload):
|
|
"""Test post-flight operations."""
|
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
|
|
|
# Mock methods called by `pre_flight_ops()`.
|
|
m_helm = mock.Mock()
|
|
|
|
armada_obj = armada.Armada(yaml_documents, m_helm)
|
|
|
|
self._test_pre_flight_ops(armada_obj, MockChartDownload)
|
|
|
|
armada_obj.post_flight_ops()
|
|
|
|
for group in armada_obj.manifest['data']['chart_groups']:
|
|
for counter, chart in enumerate(group.get(const.KEYWORD_DATA).get(
|
|
const.KEYWORD_CHARTS)):
|
|
if chart.get(
|
|
const.KEYWORD_DATA).get('source').get('type') == 'git':
|
|
MockChartDownload.return_value.cleanup.assert_called_with()
|
|
|
|
# TODO(seaneagan): Separate ChartDeploy tests into separate module.
|
|
# TODO(seaneagan): Once able to make mock library sufficiently thread safe,
|
|
# run sync tests for unsequenced as well by moving them to separate test
|
|
# class with two separate subclasses which set chart group `sequenced`
|
|
# field, one to true, one to false.
|
|
def _test_sync(
|
|
self,
|
|
known_releases,
|
|
test_success=True,
|
|
test_failure_to_run=False,
|
|
expected_last_test_result=None,
|
|
diff={'some_key': {'some diff'}}):
|
|
"""Test install functionality from the sync() method."""
|
|
|
|
@mock.patch.object(armada.Armada, 'post_flight_ops')
|
|
@mock.patch.object(armada, 'ChartDownload')
|
|
@mock.patch('armada.handlers.chart_deploy.ChartBuilder.from_chart_doc')
|
|
@mock.patch('armada.handlers.chart_deploy.Test')
|
|
def _do_test(
|
|
mock_test, mock_chartbuilder, MockChartDownload,
|
|
mock_post_flight):
|
|
MockChartDownload.return_value.get_chart.side_effect = \
|
|
set_source_dir
|
|
# Instantiate Armada object.
|
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
|
m_helm = mock.MagicMock()
|
|
armada_obj = armada.Armada(yaml_documents, m_helm)
|
|
prefix = armada_obj.manifest['data']['release_prefix']
|
|
|
|
def release_metadata(release_id, **kwargs):
|
|
try:
|
|
return next(
|
|
r for r in known_releases
|
|
if release_id.name == r['name']
|
|
and release_id.namespace == r['namespace'])
|
|
except StopIteration:
|
|
return None
|
|
|
|
m_helm.release_metadata.side_effect = release_metadata
|
|
armada_obj.chart_deploy.get_diff = mock.Mock()
|
|
|
|
cg = armada_obj.manifest['data']['chart_groups'][0]
|
|
chart_group = cg['data']
|
|
charts = chart_group['chart_group']
|
|
cg_test_all_charts = chart_group.get('test_charts')
|
|
|
|
mock_test_release = mock_test.return_value.test_release
|
|
if test_failure_to_run:
|
|
mock_test_release.side_effect = Exception('test failed to run')
|
|
else:
|
|
if not test_success:
|
|
mock_test_release.side_effect = Exception('test failed')
|
|
mock_test.return_value.timeout = const.DEFAULT_TEST_TIMEOUT
|
|
|
|
# Stub out irrelevant methods called by `armada.sync()`.
|
|
mock_chartbuilder.get_helm_chart.return_value = None
|
|
|
|
# Simulate chart diff, upgrade should only happen if non-empty.
|
|
armada_obj.chart_deploy.get_diff.return_value = diff
|
|
|
|
armada_obj.sync()
|
|
|
|
expected_install_release_calls = []
|
|
expected_upgrade_release_calls = []
|
|
expected_uninstall_release_calls = []
|
|
expected_test_constructor_calls = []
|
|
|
|
for c in charts:
|
|
chart = c['data']
|
|
release = chart['release']
|
|
release_name = release_prefixer(prefix, release)
|
|
release_id = helm.HelmReleaseId(
|
|
chart['namespace'], release_name)
|
|
source_dir = chart['source_dir']
|
|
source_directory = os.path.join(*source_dir)
|
|
|
|
# Simplified check because the actual code uses logical-or's
|
|
# multiple conditions, so this is enough.
|
|
native_wait_enabled = (
|
|
chart['wait'].get('native', {}).get('enabled', True))
|
|
|
|
if release_name not in [x['name'] for x in known_releases]:
|
|
expected_install_release_calls.append(
|
|
mock.call(
|
|
source_directory,
|
|
release_id,
|
|
values=chart['values'],
|
|
wait=native_wait_enabled,
|
|
timeout=mock.ANY))
|
|
else:
|
|
target_release = None
|
|
for known_release in known_releases:
|
|
if known_release['name'] == release_name:
|
|
target_release = known_release
|
|
break
|
|
if target_release:
|
|
status = get_release_status(target_release)
|
|
if status == helm.STATUS_FAILED:
|
|
protected = chart.get('protected', {})
|
|
if not protected:
|
|
expected_uninstall_release_calls.append(
|
|
mock.call(
|
|
release_id,
|
|
purge=True,
|
|
timeout=const.DEFAULT_DELETE_TIMEOUT))
|
|
expected_install_release_calls.append(
|
|
mock.call(
|
|
source_directory,
|
|
release_id,
|
|
values=chart['values'],
|
|
wait=native_wait_enabled,
|
|
timeout=mock.ANY))
|
|
else:
|
|
p_continue = protected.get(
|
|
'continue_processing', False)
|
|
if p_continue:
|
|
continue
|
|
else:
|
|
if chart_group['sequenced']:
|
|
break
|
|
|
|
if status == helm.STATUS_DEPLOYED:
|
|
if diff:
|
|
upgrade = chart.get('upgrade', {})
|
|
disable_hooks = upgrade.get('no_hooks', False)
|
|
options = upgrade.get('options', {})
|
|
force = options.get('force', False)
|
|
|
|
expected_upgrade_release_calls.append(
|
|
mock.call(
|
|
source_directory,
|
|
release_id,
|
|
disable_hooks=disable_hooks,
|
|
force=force,
|
|
values=chart['values'],
|
|
wait=native_wait_enabled,
|
|
timeout=mock.ANY))
|
|
|
|
expected_test_constructor_calls.append(
|
|
mock.call(
|
|
chart,
|
|
release_id,
|
|
m_helm,
|
|
cg_test_charts=cg_test_all_charts))
|
|
|
|
any_order = not chart_group['sequenced']
|
|
# Verify that at least 1 release is either installed or updated.
|
|
self.assertTrue(
|
|
len(expected_install_release_calls) >= 1
|
|
or len(expected_upgrade_release_calls) >= 1)
|
|
# Verify that the expected number of non-deployed releases are
|
|
# installed with expected arguments.
|
|
self.assertEqual(
|
|
len(expected_install_release_calls),
|
|
m_helm.install_release.call_count)
|
|
m_helm.install_release.assert_has_calls(
|
|
expected_install_release_calls, any_order=any_order)
|
|
# Verify that the expected number of deployed releases are
|
|
# updated with expected arguments.
|
|
self.assertEqual(
|
|
len(expected_upgrade_release_calls),
|
|
m_helm.upgrade_release.call_count)
|
|
m_helm.upgrade_release.assert_has_calls(
|
|
expected_upgrade_release_calls, any_order=any_order)
|
|
# Verify that the expected number of deployed releases are
|
|
# uninstalled with expected arguments.
|
|
self.assertEqual(
|
|
len(expected_uninstall_release_calls),
|
|
m_helm.uninstall_release.call_count)
|
|
m_helm.uninstall_release.assert_has_calls(
|
|
expected_uninstall_release_calls, any_order=any_order)
|
|
# Verify that the expected number of deployed releases are
|
|
# tested with expected arguments.
|
|
self.assertEqual(
|
|
len(expected_test_constructor_calls), mock_test.call_count)
|
|
|
|
mock_test.assert_has_calls(
|
|
expected_test_constructor_calls, any_order=True)
|
|
|
|
_do_test()
|
|
|
|
def _get_chart_by_name(self, name):
|
|
name = name.split('armada-')[-1]
|
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
|
return [
|
|
c for c in yaml_documents if c['data'].get('chart_name') == name
|
|
][0]
|
|
|
|
def get_mock_release(self, name, status, last_test_results=[]):
|
|
chart = self._get_chart_by_name(name)
|
|
|
|
def get_test_hook(index, success):
|
|
return {
|
|
"kind": "Pod",
|
|
"events": ["test"],
|
|
"last_run": {
|
|
"phase": ""
|
|
}
|
|
}
|
|
|
|
hooks = [get_test_hook(i, r) for i, r in enumerate(last_test_results)]
|
|
|
|
return {
|
|
"name": name,
|
|
"namespace": "test",
|
|
"version": 1,
|
|
"chart": chart,
|
|
"config": {},
|
|
"info": {
|
|
"status": status
|
|
},
|
|
"hooks": hooks
|
|
}
|
|
|
|
def test_armada_sync_with_no_deployed_releases(self):
|
|
known_releases = []
|
|
self._test_sync(known_releases)
|
|
|
|
def test_armada_sync_with_one_deployed_release(self):
|
|
c1 = 'armada-test_chart_1'
|
|
|
|
known_releases = [self.get_mock_release(c1, helm.STATUS_DEPLOYED)]
|
|
self._test_sync(known_releases)
|
|
|
|
def test_armada_sync_with_one_deployed_release_no_diff(self):
|
|
c1 = 'armada-test_chart_1'
|
|
|
|
known_releases = [self.get_mock_release(c1, helm.STATUS_DEPLOYED)]
|
|
self._test_sync(known_releases, diff=set())
|
|
|
|
def test_armada_sync_with_failed_test_result(self):
|
|
c1 = 'armada-test_chart_1'
|
|
|
|
known_releases = [
|
|
self.get_mock_release(
|
|
c1, helm.STATUS_DEPLOYED, last_test_results=[False])
|
|
]
|
|
self._test_sync(
|
|
known_releases, diff=set(), expected_last_test_result=False)
|
|
|
|
def test_armada_sync_with_success_test_result(self):
|
|
c1 = 'armada-test_chart_1'
|
|
|
|
known_releases = [
|
|
self.get_mock_release(
|
|
c1, helm.STATUS_DEPLOYED, last_test_results=[True])
|
|
]
|
|
self._test_sync(
|
|
known_releases, diff=set(), expected_last_test_result=True)
|
|
|
|
def test_armada_sync_with_success_test_result_no_tests(self):
|
|
c1 = 'armada-test_chart_1'
|
|
|
|
known_releases = [
|
|
self.get_mock_release(
|
|
c1, helm.STATUS_DEPLOYED, last_test_results=[])
|
|
]
|
|
self._test_sync(
|
|
known_releases, diff=set(), expected_last_test_result=True)
|
|
|
|
def test_armada_sync_with_both_deployed_releases(self):
|
|
c1 = 'armada-test_chart_1'
|
|
c2 = 'armada-test_chart_2'
|
|
|
|
known_releases = [
|
|
self.get_mock_release(c1, helm.STATUS_DEPLOYED),
|
|
self.get_mock_release(c2, helm.STATUS_DEPLOYED)
|
|
]
|
|
self._test_sync(known_releases)
|
|
|
|
def test_armada_sync_with_unprotected_releases(self):
|
|
c1 = 'armada-test_chart_1'
|
|
|
|
known_releases = [self.get_mock_release(c1, helm.STATUS_FAILED)]
|
|
self._test_sync(known_releases)
|
|
|
|
def test_armada_sync_with_protected_releases_continue(self):
|
|
c1 = 'armada-test_chart_1'
|
|
c2 = 'armada-test_chart_2'
|
|
|
|
known_releases = [
|
|
self.get_mock_release(c2, helm.STATUS_FAILED),
|
|
self.get_mock_release(c1, helm.STATUS_FAILED)
|
|
]
|
|
self._test_sync(known_releases)
|
|
|
|
def test_armada_sync_with_protected_releases_halt(self):
|
|
c3 = 'armada-test_chart_3'
|
|
|
|
known_releases = [self.get_mock_release(c3, helm.STATUS_FAILED)]
|
|
|
|
def _test_method():
|
|
self._test_sync(known_releases)
|
|
|
|
self.assertRaises(ChartDeployException, _test_method)
|
|
|
|
def test_armada_sync_test_failure(self):
|
|
def _test_method():
|
|
self._test_sync([], test_success=False)
|
|
|
|
self.assertRaises(ChartDeployException, _test_method)
|
|
|
|
def test_armada_sync_test_failure_to_run(self):
|
|
def _test_method():
|
|
self._test_sync([], test_failure_to_run=True)
|
|
|
|
self.assertRaises(ChartDeployException, _test_method)
|
|
|
|
|
|
class ArmadaNegativeHandlerTestCase(base.ArmadaTestCase):
|
|
@mock.patch.object(armada, 'ChartDownload')
|
|
def test_armada_get_manifest_exception(self, MockChartDownload):
|
|
"""Test armada handling with invalid manifest."""
|
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
|
error_re = ('.*Documents must include at least one of each of .*')
|
|
self.assertRaisesRegexp(
|
|
ManifestException, error_re, armada.Armada, yaml_documents[:1],
|
|
mock.MagicMock())
|
|
|
|
@mock.patch.object(armada, 'ChartDownload')
|
|
def test_armada_override_exception(self, MockChartDownload):
|
|
"""Test Armada checks with invalid chart override."""
|
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
|
override = ('chart:example-chart-2:name=' 'overridden', )
|
|
|
|
error_re = ('is not a valid override statement')
|
|
with self.assertRaisesRegexp(InvalidOverrideValueException, error_re):
|
|
armada.Armada(yaml_documents, mock.MagicMock(), set_ovr=override)
|
|
|
|
@mock.patch.object(armada, 'ChartDownload')
|
|
def test_armada_manifest_exception_override_none(self, MockChartDownload):
|
|
"""Test Armada checks with invalid manifest."""
|
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
|
example_document = [
|
|
d for d in yaml_documents
|
|
if d['metadata']['name'] == 'example-chart-4'
|
|
][0]
|
|
del example_document['data']['release']
|
|
|
|
error_re = ('Invalid document .*')
|
|
with self.assertRaisesRegexp(InvalidManifestException, error_re):
|
|
armada.Armada(yaml_documents, mock.MagicMock(), set_ovr=None)
|