示例#1
0
        def _do_test(mock_test, mock_chartbuilder, mock_pre_flight,
                     mock_post_flight):
            # Instantiate Armada object.
            yaml_documents = list(yaml.safe_load_all(TEST_YAML))

            m_tiller = mock.MagicMock()
            m_tiller.list_releases.return_value = known_releases

            armada_obj = armada.Armada(yaml_documents, m_tiller)
            armada_obj.chart_deploy.get_diff = mock.Mock()

            chart_group = armada_obj.manifest['armada']['chart_groups'][0]
            charts = chart_group['chart_group']
            cg_test_all_charts = chart_group.get('test_charts')

            mock_test_release = mock_test.return_value.test_release_for_success
            if test_failure_to_run:

                def fail(tiller, release, timeout=None, cleanup=False):
                    status = AttrDict(
                        **{'info': AttrDict(**{'Description': 'Failed'})})
                    raise tiller_exceptions.ReleaseException(
                        release, status, 'Test')

                mock_test_release.side_effect = fail
            else:
                mock_test_release.return_value = test_success

            # Stub out irrelevant methods called by `armada.sync()`.
            mock_chartbuilder.get_source_path.return_value = None
            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_update_release_calls = []
            expected_uninstall_release_calls = []
            expected_test_constructor_calls = []

            for c in charts:
                chart = c['chart']
                release = chart['release']
                prefix = armada_obj.manifest['armada']['release_prefix']
                release_name = release_prefixer(prefix, release)
                # 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(
                            mock_chartbuilder().get_helm_chart(),
                            "{}-{}".format(
                                armada_obj.manifest['armada']
                                ['release_prefix'], chart['release']),
                            chart['namespace'],
                            values=yaml.safe_dump(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 == const.STATUS_FAILED:
                            protected = chart.get('protected', {})
                            if not protected:
                                expected_uninstall_release_calls.append(
                                    mock.call(release_name))
                                expected_install_release_calls.append(
                                    mock.call(
                                        mock_chartbuilder().get_helm_chart(),
                                        "{}-{}".format(
                                            armada_obj.manifest['armada']
                                            ['release_prefix'],
                                            chart['release']),
                                        chart['namespace'],
                                        values=yaml.safe_dump(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 == const.STATUS_DEPLOYED:
                            if diff:
                                upgrade = chart.get('upgrade', {})
                                disable_hooks = upgrade.get('no_hooks', False)
                                force = upgrade.get('force', False)
                                recreate_pods = upgrade.get(
                                    'recreate_pods', False)

                                expected_update_release_calls.append(
                                    mock.call(
                                        mock_chartbuilder().get_helm_chart(),
                                        "{}-{}".format(
                                            armada_obj.manifest['armada']
                                            ['release_prefix'],
                                            chart['release']),
                                        chart['namespace'],
                                        pre_actions={},
                                        post_actions={},
                                        disable_hooks=disable_hooks,
                                        force=force,
                                        recreate_pods=recreate_pods,
                                        values=yaml.safe_dump(chart['values']),
                                        wait=native_wait_enabled,
                                        timeout=mock.ANY))

                test_chart_override = chart.get('test')
                expected_test_constructor_calls.append(
                    mock.call(
                        release_name,
                        m_tiller,
                        cg_test_charts=cg_test_all_charts,
                        test_values=test_chart_override))

            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_update_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_tiller.install_release.call_count)
            m_tiller.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_update_release_calls),
                m_tiller.update_release.call_count)
            m_tiller.update_release.assert_has_calls(
                expected_update_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_tiller.uninstall_release.call_count)
            m_tiller.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)
示例#2
0
    def list_releases(self):
        '''
        List Helm Releases
        '''
        # TODO(MarshM possibly combine list_releases() with list_charts()
        # since they do the same thing, grouping output differently
        stub = ReleaseServiceStub(self.channel)

        # NOTE(seaneagan): Paging through releases to prevent hitting the
        # maximum message size limit that tiller sets for it's reponses.
        def get_results():
            releases = []
            done = False
            next_release_expected = ""
            initial_total = None
            while not done:
                req = ListReleasesRequest(offset=next_release_expected,
                                          limit=LIST_RELEASES_PAGE_SIZE,
                                          status_codes=const.STATUS_ALL)

                LOG.debug('Tiller ListReleases() with timeout=%s, request=%s',
                          self.timeout, req)
                response = stub.ListReleases(req,
                                             self.timeout,
                                             metadata=self.metadata)

                found_message = False
                for message in response:
                    found_message = True
                    page = message.releases

                    if initial_total:
                        if message.total != initial_total:
                            LOG.warning(
                                'Total releases changed between '
                                'pages from (%s) to (%s)', initial_total,
                                message.count)
                            raise ex.TillerListReleasesPagingException()
                    else:
                        initial_total = message.total

                    # Add page to results.
                    releases.extend(page)

                    if message.next:
                        next_release_expected = message.next
                    else:
                        done = True

                # Ensure we break out was no message found which
                # is seen if there are no releases in tiller.
                if not found_message:
                    done = True

            return releases

        for index in range(LIST_RELEASES_ATTEMPTS):
            attempt = index + 1
            try:
                releases = get_results()
            except ex.TillerListReleasesPagingException:
                LOG.warning('List releases paging failed on attempt %s/%s',
                            attempt, LIST_RELEASES_ATTEMPTS)
                if attempt == LIST_RELEASES_ATTEMPTS:
                    raise
            else:
                # Filter out old releases, similar to helm cli:
                # https://github.com/helm/helm/blob/1e26b5300b5166fabb90002535aacd2f9cc7d787/cmd/helm/list.go#L196
                latest_versions = {}

                for r in releases:
                    max = latest_versions.get(r.name)
                    if max is not None:
                        if max > r.version:
                            continue
                    latest_versions[r.name] = r.version

                latest_releases = []
                for r in releases:
                    if latest_versions[r.name] == r.version:
                        LOG.debug('Found release %s, version %s, status: %s',
                                  r.name, r.version, get_release_status(r))
                        latest_releases.append(r)

                return latest_releases
示例#3
0
    def execute(self, chart, cg_test_all_charts, prefix, known_releases):
        namespace = chart.get('namespace')
        release = chart.get('release')
        release_name = r.release_prefixer(prefix, release)
        LOG.info('Processing Chart, release=%s', release_name)

        values = chart.get('values', {})
        pre_actions = {}
        post_actions = {}

        result = {}

        protected = chart.get('protected', {})
        p_continue = protected.get('continue_processing', False)

        old_release = self.find_chart_release(known_releases, release_name)

        status = None
        if old_release:
            status = r.get_release_status(old_release)

            if status not in [const.STATUS_FAILED, const.STATUS_DEPLOYED]:
                raise armada_exceptions.UnexpectedReleaseStatusException(
                    release_name, status)

        chart_wait = ChartWait(
            self.tiller.k8s,
            release_name,
            chart,
            namespace,
            k8s_wait_attempts=self.k8s_wait_attempts,
            k8s_wait_attempt_sleep=self.k8s_wait_attempt_sleep,
            timeout=self.timeout)

        native_wait_enabled = chart_wait.is_native_enabled()

        # Begin Chart timeout deadline
        deadline = time.time() + chart_wait.get_timeout()

        chartbuilder = ChartBuilder(chart)
        new_chart = chartbuilder.get_helm_chart()

        # Check for existing FAILED release, and purge
        if status == const.STATUS_FAILED:
            LOG.info('Purging FAILED release %s before deployment.',
                     release_name)
            if protected:
                if p_continue:
                    LOG.warn(
                        'Release %s is `protected`, '
                        'continue_processing=True. Operator must '
                        'handle FAILED release manually.', release_name)
                    result['protected'] = release_name
                    return result
                else:
                    LOG.error(
                        'Release %s is `protected`, '
                        'continue_processing=False.', release_name)
                    raise armada_exceptions.ProtectedReleaseException(
                        release_name)
            else:
                # Purge the release
                self.tiller.uninstall_release(release_name)
                result['purge'] = release_name

        # TODO(mark-burnett): It may be more robust to directly call
        # tiller status to decide whether to install/upgrade rather
        # than checking for list membership.
        if status == const.STATUS_DEPLOYED:

            # indicate to the end user what path we are taking
            LOG.info("Existing release %s found in namespace %s", release_name,
                     namespace)

            # extract the installed chart and installed values from the
            # latest release so we can compare to the intended state
            old_chart = old_release.chart
            old_values_string = old_release.config.raw

            upgrade = chart.get('upgrade', {})
            disable_hooks = upgrade.get('no_hooks', False)
            options = upgrade.get('options', {})
            force = options.get('force', False)
            recreate_pods = options.get('recreate_pods', False)

            if upgrade:
                upgrade_pre = upgrade.get('pre', {})
                upgrade_post = upgrade.get('post', {})

                if not self.disable_update_pre and upgrade_pre:
                    pre_actions = upgrade_pre

                if not self.disable_update_post and upgrade_post:
                    LOG.warning('Post upgrade actions are ignored by Armada'
                                'and will not affect deployment.')
                    post_actions = upgrade_post

            try:
                old_values = yaml.safe_load(old_values_string)
            except yaml.YAMLError:
                chart_desc = '{} (previously deployed)'.format(
                    old_chart.metadata.name)
                raise armada_exceptions.\
                    InvalidOverrideValuesYamlException(chart_desc)

            LOG.info('Checking for updates to chart release inputs.')
            diff = self.get_diff(old_chart, old_values, new_chart, values)

            if not diff:
                LOG.info("Found no updates to chart release inputs")
            else:
                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

                # 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
        else:
            timer = int(round(deadline - time.time()))
            LOG.info(
                "Installing release %s in namespace %s, wait=%s, "
                "timeout=%ss", release_name, namespace, native_wait_enabled,
                timer)
            tiller_result = self.tiller.install_release(
                new_chart,
                release_name,
                namespace,
                values=yaml.safe_dump(values),
                wait=native_wait_enabled,
                timeout=timer)

            LOG.info('Install completed with results from Tiller: %s',
                     tiller_result.__dict__)
            result['install'] = release_name

        # Wait
        timer = int(round(deadline - time.time()))
        chart_wait.wait(timer)

        # Test
        just_deployed = ('install' in result) or ('upgrade' in result)
        last_test_passed = old_release and r.get_last_test_result(old_release)

        test_values = chart.get('test')
        test_handler = Test(release_name,
                            self.tiller,
                            cg_test_charts=cg_test_all_charts,
                            test_values=test_values)

        run_test = test_handler.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_handler)

        return result
示例#4
0
        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)
示例#5
0
    def _execute(self, ch, cg_test_all_charts, prefix):
        manifest_name = self.manifest['metadata']['name']
        chart = ch[const.KEYWORD_DATA]
        chart_name = ch['metadata']['name']
        namespace = chart.get('namespace')
        release = chart.get('release')
        release_name = r.release_prefixer(prefix, release)
        release_id = helm.HelmReleaseId(namespace, release_name)
        source_dir = chart['source_dir']
        source_directory = os.path.join(*source_dir)
        LOG.info('Processing Chart, release=%s', release_id)

        result = {}

        chart_wait = ChartWait(
            self.helm.k8s,
            release_id,
            ch,
            k8s_wait_attempts=self.k8s_wait_attempts,
            k8s_wait_attempt_sleep=self.k8s_wait_attempt_sleep,
            timeout=self.timeout)
        wait_timeout = chart_wait.get_timeout()

        # Begin Chart timeout deadline
        deadline = time.time() + wait_timeout
        old_release = self.helm.release_metadata(release_id)
        action = metrics.ChartDeployAction.NOOP

        def noop():
            pass

        deploy = noop

        # Resolve action
        values = chart.get('values', {})
        pre_actions = {}

        status = None
        if old_release:
            status = r.get_release_status(old_release)

        native_wait_enabled = chart_wait.is_native_enabled()

        chartbuilder = ChartBuilder.from_chart_doc(ch, self.helm)

        if status == helm.STATUS_DEPLOYED:

            # indicate to the end user what path we are taking
            LOG.info("Existing release %s found", release_id)

            # extract the installed chart and installed values from the
            # latest release so we can compare to the intended state
            old_chart = old_release['chart']
            old_values = old_release.get('config', {})

            upgrade = chart.get('upgrade', {})
            options = upgrade.get('options', {})

            # TODO: Remove when v1 doc support is removed.
            schema_info = get_schema_info(ch['schema'])
            if schema_info.version < 2:
                no_hooks_location = upgrade
            else:
                no_hooks_location = options

            disable_hooks = no_hooks_location.get('no_hooks', False)
            force = options.get('force', False)

            if upgrade:
                upgrade_pre = upgrade.get('pre', {})
                upgrade_post = upgrade.get('post', {})

                if not self.disable_update_pre and upgrade_pre:
                    pre_actions = upgrade_pre

                if not self.disable_update_post and upgrade_post:
                    LOG.warning('Post upgrade actions are ignored by Armada'
                                'and will not affect deployment.')

            LOG.info('Checking for updates to chart release inputs.')
            new_chart = chartbuilder.get_helm_chart(release_id, values)
            diff = self.get_diff(old_chart, old_values, new_chart, values)

            if not diff:
                LOG.info("Found no updates to chart release inputs")
            else:
                action = metrics.ChartDeployAction.UPGRADE
                LOG.info("Found updates to chart release inputs")

                def upgrade():
                    # do actual update
                    timer = int(round(deadline - time.time()))
                    PreUpdateActions(self.helm.k8s).execute(
                        pre_actions, release, namespace, chart, disable_hooks,
                        values, timer)
                    LOG.info("Upgrading release=%s, wait=%s, "
                             "timeout=%ss", release_id, native_wait_enabled,
                             timer)
                    self.helm.upgrade_release(source_directory,
                                              release_id,
                                              disable_hooks=disable_hooks,
                                              values=values,
                                              wait=native_wait_enabled,
                                              timeout=timer,
                                              force=force)

                    LOG.info('Upgrade completed')
                    result['upgrade'] = release_id

                deploy = upgrade
        else:

            def install():
                timer = int(round(deadline - time.time()))
                LOG.info("Installing release=%s, wait=%s, "
                         "timeout=%ss", release_id, native_wait_enabled, timer)
                self.helm.install_release(source_directory,
                                          release_id,
                                          values=values,
                                          wait=native_wait_enabled,
                                          timeout=timer)

                LOG.info('Install completed')
                result['install'] = release_id

            # Check for release with status other than DEPLOYED
            if status:
                if status != helm.STATUS_FAILED:
                    LOG.warn(
                        'Unexpected release status encountered '
                        'release=%s, status=%s', release_id, status)

                    # Make best effort to determine whether a deployment is
                    # likely pending, by checking if the last deployment
                    # was started within the timeout window of the chart.
                    last_deployment_age = r.get_last_deployment_age(
                        old_release)
                    likely_pending = last_deployment_age <= wait_timeout
                    if likely_pending:
                        # We don't take any deploy action and wait for the
                        # to get deployed.
                        deploy = noop
                        deadline = deadline - last_deployment_age
                    else:
                        # Release is likely stuck in an unintended
                        # state. Log and continue on with remediation steps
                        # below.
                        LOG.info(
                            'Old release %s likely stuck in status %s, '
                            '(last deployment age=%ss) >= '
                            '(chart wait timeout=%ss)', release, status,
                            last_deployment_age, wait_timeout)
                        res = self.purge_release(chart, release_id, status,
                                                 manifest_name, chart_name,
                                                 result)
                        if isinstance(res, dict):
                            if 'protected' in res:
                                return res
                        action = metrics.ChartDeployAction.INSTALL
                        deploy = install
                else:
                    # The chart is in Failed state, hence we purge
                    # the chart and attempt to install it again.
                    res = self.purge_release(chart, release_id, status,
                                             manifest_name, chart_name, result)
                    if isinstance(res, dict):
                        if 'protected' in res:
                            return res
                    action = metrics.ChartDeployAction.INSTALL
                    deploy = install

        if status is None:
            action = metrics.ChartDeployAction.INSTALL
            deploy = install

        # Deploy
        with metrics.CHART_DEPLOY.get_context(wait_timeout, manifest_name,
                                              chart_name,
                                              action.get_label_value()):
            deploy()

            # Wait
            timer = int(round(deadline - time.time()))
            chart_wait.wait(timer)

        # Test
        just_deployed = ('install' in result) or ('upgrade' in result)
        last_test_passed = old_release and r.get_last_test_result(old_release)

        test_handler = Test(chart,
                            release_id,
                            self.helm,
                            cg_test_charts=cg_test_all_charts)

        run_test = test_handler.test_enabled and (just_deployed
                                                  or not last_test_passed)
        if run_test:
            with metrics.CHART_TEST.get_context(test_handler.timeout,
                                                manifest_name, chart_name):
                self._test_chart(test_handler)

        return result
示例#6
0
    def _execute(self, ch, cg_test_all_charts, prefix, known_releases):
        manifest_name = self.manifest['metadata']['name']
        chart = ch[const.KEYWORD_DATA]
        chart_name = ch['metadata']['name']
        namespace = chart.get('namespace')
        release = chart.get('release')
        release_name = r.release_prefixer(prefix, release)
        LOG.info('Processing Chart, release=%s', release_name)

        result = {}

        chart_wait = ChartWait(
            self.tiller.k8s,
            release_name,
            ch,
            namespace,
            k8s_wait_attempts=self.k8s_wait_attempts,
            k8s_wait_attempt_sleep=self.k8s_wait_attempt_sleep,
            timeout=self.timeout)
        wait_timeout = chart_wait.get_timeout()

        # Begin Chart timeout deadline
        deadline = time.time() + wait_timeout
        old_release = self.find_chart_release(known_releases, release_name)
        action = metrics.ChartDeployAction.NOOP

        def noop():
            pass

        deploy = noop

        # Resolve action
        values = chart.get('values', {})
        pre_actions = {}
        post_actions = {}

        status = None
        if old_release:
            status = r.get_release_status(old_release)

        native_wait_enabled = chart_wait.is_native_enabled()

        chartbuilder = ChartBuilder.from_chart_doc(ch)
        new_chart = chartbuilder.get_helm_chart()

        if status == const.STATUS_DEPLOYED:

            # indicate to the end user what path we are taking
            LOG.info("Existing release %s found in namespace %s", release_name,
                     namespace)

            # extract the installed chart and installed values from the
            # latest release so we can compare to the intended state
            old_chart = old_release.chart
            old_values_string = old_release.config.raw

            upgrade = chart.get('upgrade', {})
            options = upgrade.get('options', {})

            # TODO: Remove when v1 doc support is removed.
            schema_info = get_schema_info(ch['schema'])
            if schema_info.version < 2:
                no_hooks_location = upgrade
            else:
                no_hooks_location = options

            disable_hooks = no_hooks_location.get('no_hooks', False)
            force = options.get('force', False)
            recreate_pods = options.get('recreate_pods', False)

            if upgrade:
                upgrade_pre = upgrade.get('pre', {})
                upgrade_post = upgrade.get('post', {})

                if not self.disable_update_pre and upgrade_pre:
                    pre_actions = upgrade_pre

                if not self.disable_update_post and upgrade_post:
                    LOG.warning('Post upgrade actions are ignored by Armada'
                                'and will not affect deployment.')
                    post_actions = upgrade_post

            try:
                old_values = yaml.safe_load(old_values_string)
            except yaml.YAMLError:
                chart_desc = '{} (previously deployed)'.format(
                    old_chart.metadata.name)
                raise armada_exceptions.\
                    InvalidOverrideValuesYamlException(chart_desc)

            LOG.info('Checking for updates to chart release inputs.')
            diff = self.get_diff(old_chart, old_values, new_chart, values)

            if not diff:
                LOG.info("Found no updates to chart release inputs")
            else:
                action = metrics.ChartDeployAction.UPGRADE
                LOG.info("Found updates to chart release inputs")
                LOG.debug("%s", diff)
                result['diff'] = {chart['release']: str(diff)}

                def upgrade():
                    # 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

                deploy = upgrade
        else:
            # Check for release with status other than DEPLOYED
            if status:
                if status != const.STATUS_FAILED:
                    LOG.warn(
                        'Unexpected release status encountered '
                        'release=%s, status=%s', release_name, status)

                    # Make best effort to determine whether a deployment is
                    # likely pending, by checking if the last deployment
                    # was started within the timeout window of the chart.
                    last_deployment_age = r.get_last_deployment_age(
                        old_release)
                    likely_pending = last_deployment_age <= wait_timeout
                    if likely_pending:
                        # Give up if a deployment is likely pending, we do not
                        # want to have multiple operations going on for the
                        # same release at the same time.
                        raise armada_exceptions.\
                            DeploymentLikelyPendingException(
                                release_name, status, last_deployment_age,
                                wait_timeout)
                    else:
                        # Release is likely stuck in an unintended (by tiller)
                        # state. Log and continue on with remediation steps
                        # below.
                        LOG.info(
                            'Old release %s likely stuck in status %s, '
                            '(last deployment age=%ss) >= '
                            '(chart wait timeout=%ss)', release, status,
                            last_deployment_age, wait_timeout)

                protected = chart.get('protected', {})
                if protected:
                    p_continue = protected.get('continue_processing', False)
                    if p_continue:
                        LOG.warn(
                            'Release %s is `protected`, '
                            'continue_processing=True. Operator must '
                            'handle %s release manually.', release_name,
                            status)
                        result['protected'] = release_name
                        return result
                    else:
                        LOG.error(
                            'Release %s is `protected`, '
                            'continue_processing=False.', release_name)
                        raise armada_exceptions.ProtectedReleaseException(
                            release_name, status)
                else:
                    # Purge the release
                    with metrics.CHART_DELETE.get_context(
                            manifest_name, chart_name):

                        LOG.info('Purging release %s with status %s',
                                 release_name, status)
                        chart_delete = ChartDelete(chart, release_name,
                                                   self.tiller)
                        chart_delete.delete()
                        result['purge'] = release_name

            action = metrics.ChartDeployAction.INSTALL

            def install():
                timer = int(round(deadline - time.time()))
                LOG.info(
                    "Installing release %s in namespace %s, wait=%s, "
                    "timeout=%ss", release_name, namespace,
                    native_wait_enabled, timer)
                tiller_result = self.tiller.install_release(
                    new_chart,
                    release_name,
                    namespace,
                    values=yaml.safe_dump(values),
                    wait=native_wait_enabled,
                    timeout=timer)

                LOG.info('Install completed with results from Tiller: %s',
                         tiller_result.__dict__)
                result['install'] = release_name

            deploy = install

        # Deploy
        with metrics.CHART_DEPLOY.get_context(wait_timeout, manifest_name,
                                              chart_name,
                                              action.get_label_value()):
            deploy()

            # Wait
            timer = int(round(deadline - time.time()))
            chart_wait.wait(timer)

        # Test
        just_deployed = ('install' in result) or ('upgrade' in result)
        last_test_passed = old_release and r.get_last_test_result(old_release)

        test_handler = Test(chart,
                            release_name,
                            self.tiller,
                            cg_test_charts=cg_test_all_charts)

        run_test = test_handler.test_enabled and (just_deployed
                                                  or not last_test_passed)
        if run_test:
            with metrics.CHART_TEST.get_context(test_handler.timeout,
                                                manifest_name, chart_name):
                self._test_chart(release_name, test_handler)

        return result