diff --git a/armada/handlers/chart_deploy.py b/armada/handlers/chart_deploy.py index 51a379ca..c7644dd8 100644 --- a/armada/handlers/chart_deploy.py +++ b/armada/handlers/chart_deploy.py @@ -23,7 +23,7 @@ from armada.handlers.test import test_release_for_success from armada.handlers.release_diff import ReleaseDiff from armada.handlers.wait import ChartWait from armada.exceptions import tiller_exceptions -from armada.utils.release import release_prefixer, get_release_status +import armada.utils.release as r LOG = logging.getLogger(__name__) @@ -43,7 +43,7 @@ class ChartDeploy(object): def execute(self, chart, cg_test_all_charts, prefix, known_releases): namespace = chart.get('namespace') release = chart.get('release') - release_name = release_prefixer(prefix, release) + release_name = r.release_prefixer(prefix, release) LOG.info('Processing Chart, release=%s', release_name) values = chart.get('values', {}) @@ -59,7 +59,7 @@ class ChartDeploy(object): status = None if old_release: - status = get_release_status(old_release) + status = r.get_release_status(old_release) if status not in [const.STATUS_FAILED, const.STATUS_DEPLOYED]: raise armada_exceptions.UnexpectedReleaseStatusException( @@ -147,39 +147,36 @@ class ChartDeploy(object): if not diff: LOG.info("Found no updates to chart release inputs") - return result + else: + LOG.info("Found updates to chart release inputs") + LOG.debug("%s", diff) + result['diff'] = {chart['release']: str(diff)} - LOG.info("Found updates to chart release inputs") - LOG.debug("%s", diff) - result['diff'] = {chart['release']: str(diff)} + # TODO(MarshM): Add tiller dry-run before upgrade and + # consider deadline impacts - # TODO(MarshM): Add tiller dry-run before upgrade and - # consider deadline impacts + # do actual update + timer = int(round(deadline - time.time())) + LOG.info( + "Upgrading release %s in namespace %s, wait=%s, " + "timeout=%ss", release_name, namespace, + native_wait_enabled, timer) + tiller_result = self.tiller.update_release( + new_chart, + release_name, + namespace, + pre_actions=pre_actions, + post_actions=post_actions, + disable_hooks=disable_hooks, + values=yaml.safe_dump(values), + wait=native_wait_enabled, + timeout=timer, + force=force, + recreate_pods=recreate_pods) - # do actual update - timer = int(round(deadline - time.time())) - LOG.info( - "Upgrading release %s in namespace %s, wait=%s, " - "timeout=%ss", release_name, namespace, native_wait_enabled, - timer) - tiller_result = self.tiller.update_release( - new_chart, - release_name, - namespace, - pre_actions=pre_actions, - post_actions=post_actions, - disable_hooks=disable_hooks, - values=yaml.safe_dump(values), - wait=native_wait_enabled, - timeout=timer, - force=force, - recreate_pods=recreate_pods) - - LOG.info('Upgrade completed with results from Tiller: %s', - tiller_result.__dict__) - result['upgrade'] = release_name - - # process install + LOG.info('Upgrade completed with results from Tiller: %s', + tiller_result.__dict__) + result['upgrade'] = release_name else: timer = int(round(deadline - time.time())) LOG.info( @@ -198,26 +195,33 @@ class ChartDeploy(object): tiller_result.__dict__) result['install'] = release_name + # Wait timer = int(round(deadline - time.time())) chart_wait.wait(timer) + # Test test_chart_override = chart.get('test') # Use old default value when not using newer `test` key test_cleanup = True if test_chart_override is None: - test_this_chart = cg_test_all_charts + test_enabled = cg_test_all_charts elif isinstance(test_chart_override, bool): LOG.warn('Boolean value for chart `test` key is' ' deprecated and support for this will' ' be removed. Use `test.enabled` ' 'instead.') - test_this_chart = test_chart_override + test_enabled = test_chart_override else: # NOTE: helm tests are enabled by default - test_this_chart = test_chart_override.get('enabled', True) + test_enabled = test_chart_override.get('enabled', True) test_cleanup = test_chart_override.get('options', {}).get( 'cleanup', False) - if test_this_chart: + + just_deployed = ('install' in result) or ('upgrade' in result) + last_test_passed = old_release and r.get_last_test_result(old_release) + run_test = test_enabled and (just_deployed or not last_test_passed) + + if run_test: timer = int(round(deadline - time.time())) self._test_chart(release_name, timer, test_cleanup) diff --git a/armada/handlers/test.py b/armada/handlers/test.py index c27f0dcd..a463682f 100644 --- a/armada/handlers/test.py +++ b/armada/handlers/test.py @@ -26,6 +26,9 @@ def test_release_for_success(tiller, cleanup=False): test_suite_run = tiller.test_release( release, timeout=timeout, cleanup=cleanup) - results = getattr(test_suite_run, 'results', []) - failed_results = [r for r in results if r.status != TESTRUN_STATUS_SUCCESS] - return len(failed_results) == 0 + return get_test_suite_run_success(test_suite_run) + + +def get_test_suite_run_success(test_suite_run): + return all( + r.status == TESTRUN_STATUS_SUCCESS for r in test_suite_run.results) diff --git a/armada/tests/unit/handlers/test_armada.py b/armada/tests/unit/handlers/test_armada.py index bfecb309..92eecdf3 100644 --- a/armada/tests/unit/handlers/test_armada.py +++ b/armada/tests/unit/handlers/test_armada.py @@ -18,6 +18,7 @@ import yaml from armada import const from armada.handlers import armada from armada.handlers import chart_deploy +from armada.handlers.test import TESTRUN_STATUS_SUCCESS, TESTRUN_STATUS_FAILURE from armada.tests.unit import base from armada.tests.test_utils import AttrDict, makeMockThreadSafe from armada.utils.release import release_prefixer, get_release_status @@ -334,6 +335,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase): 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.""" @@ -476,7 +478,8 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase): test_cleanup = test_chart_override.get('options', {}).get( 'cleanup', False) - if test_this_chart and expected_apply: + if test_this_chart and (expected_apply or + not expected_last_test_result): expected_test_release_for_success_calls.append( mock.call( m_tiller, @@ -527,16 +530,35 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase): c for c in yaml_documents if c['data'].get('chart_name') == name ][0] - def get_mock_release(self, name, status): + def get_mock_release(self, name, status, last_test_results=None): status_mock = mock.Mock() status_mock.return_value = status chart = self._get_chart_by_name(name) + + def get_test_result(success): + status = (TESTRUN_STATUS_SUCCESS + if success else TESTRUN_STATUS_FAILURE) + return mock.Mock(status=status) + + last_test_suite_run = None + if last_test_results is not None: + results = [get_test_result(r) for r in last_test_results] + last_test_suite_run = mock.Mock(results=results) + + def has_last_test(name): + if name == 'last_test_suite_run': + return last_test_results is not None + self.fail('Called HasField() with unexpected field') + mock_release = mock.Mock( version=1, chart=chart, config=mock.Mock(raw="{}"), info=mock.Mock( - status=mock.Mock(Code=mock.MagicMock(Name=status_mock)))) + status=mock.Mock( + Code=mock.MagicMock(Name=status_mock), + HasField=mock.MagicMock(side_effect=has_last_test), + last_test_suite_run=last_test_suite_run))) mock_release.name = name return mock_release @@ -556,6 +578,36 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase): known_releases = [self.get_mock_release(c1, const.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, const.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, const.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, const.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' diff --git a/armada/utils/release.py b/armada/utils/release.py index c40f5ad0..dfd02df2 100644 --- a/armada/utils/release.py +++ b/armada/utils/release.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from armada.handlers.test import get_test_suite_run_success + def release_prefixer(prefix, release): ''' @@ -36,4 +38,18 @@ def get_release_status(release): :return: status name of release """ - return release.info.status.Code.Name(release.info.status.code) + status = release.info.status + return status.Code.Name(status.code) + + +def get_last_test_result(release): + """ + :param release: protobuf release object + + :return: status name of release + """ + + status = release.info.status + if not status.HasField('last_test_suite_run'): + return None + return get_test_suite_run_success(status.last_test_suite_run)