armada/armada/handlers/chart_deploy.py

227 lines
8.4 KiB
Python

# Copyright 2018 The Armada Authors.
#
# 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 time
from kubernetes.client.rest import ApiException
from oslo_log import log as logging
from armada import const
from armada.exceptions import armada_exceptions
from armada.handlers import metrics
from armada.handlers import helm
from armada.handlers.release_diff import ReleaseDiff
from armada.handlers.chart_delete import ChartDelete
import armada.utils.release as r
LOG = logging.getLogger(__name__)
class ChartDeploy(object):
def __init__(
self, manifest, disable_update_pre, disable_update_post,
k8s_wait_attempts, k8s_wait_attempt_sleep, timeout, helm):
self.manifest = manifest
self.disable_update_pre = disable_update_pre
self.disable_update_post = disable_update_post
self.k8s_wait_attempts = k8s_wait_attempts
self.k8s_wait_attempt_sleep = k8s_wait_attempt_sleep
self.timeout = timeout
self.k8s = helm.k8s
def execute(self, ch, cg_test_all_charts, prefix, concurrency):
chart_name = ch['metadata']['name']
manifest_name = self.manifest['metadata']['name']
with metrics.CHART_HANDLE.get_context(concurrency, manifest_name,
chart_name):
return self._execute(ch, cg_test_all_charts, prefix)
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)
LOG.info('Processing Chart, release=%s', release_id)
result = {}
wait_timeout = self._get_timeout(ch)
# Begin Chart timeout deadline
deadline = time.time() + wait_timeout
def noop():
pass
deploy = noop
# Resolve action
values = chart.get('values', {})
self._convert_min_ready(chart)
try:
self.k8s.read_custom_resource(
group=const.CHART_GROUP,
version=const.CHART_VERSION,
namespace=namespace,
plural=const.CHART_PLURAL,
name=release_name)
# indicate to the end user what path we are taking
LOG.info("Existing release %s found", release_name)
def upgrade():
# do actual update
timer = int(round(deadline - time.time()))
LOG.info(
"Upgrading (replacing) release=%s, "
"timeout=%ss", release_name, timer)
self.k8s.replace_custom_resource(
"armada.airshipit.io",
"v1",
namespace,
"armadacharts",
release_name,
self._gen_crd_template(chart_name, release_id,
chart['source']['location'],
values, chart.get('wait', {}),
chart.get('upgrade', {}).
get('pre', {}).get('delete', []),
chart.get('test', {}).
get('enabled', True))
)
LOG.info('Upgrade completed')
result['upgrade'] = release_id
action = metrics.ChartDeployAction.UPGRADE
deploy = upgrade
except ApiException as err:
if err.status == 404:
def install():
timer = int(round(deadline - time.time()))
LOG.info(
"Installing release=%s, "
"timeout=%ss", release_name, timer)
self.k8s.create_custom_resource(
const.CHART_GROUP,
const.CHART_VERSION,
namespace,
const.CHART_PLURAL,
self._gen_crd_template(chart_name, release_id,
chart['source']['location'],
values, chart.get('wait', {}),
chart.get('upgrade', {}).
get('pre', {}).get('delete', []),
chart.get('test', {}).
get('enabled', True)
)
)
LOG.info('Install completed')
result['install'] = release_id
action = metrics.ChartDeployAction.INSTALL
deploy = install
else:
raise
# 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()))
LOG.info('Starting to wait')
self.k8s.wait_custom_resource(const.CHART_GROUP,
const.CHART_VERSION,
namespace,
const.CHART_PLURAL,
"{}={}".format(const.CHART_LABEL,
release_id.name),
timer)
return result
def _get_timeout(self, ch):
chart_data = ch[const.KEYWORD_DATA]
wait_config = chart_data.get('wait', {})
# Calculate timeout
wait_timeout = self.timeout
if wait_timeout is None:
wait_timeout = wait_config.get('timeout')
# TODO: Remove when v1 doc support is removed.
deprecated_timeout = chart_data.get('timeout')
if deprecated_timeout is not None:
LOG.warn(
'The `timeout` key is deprecated and support '
'for this will be removed soon. Use '
'`wait.timeout` instead.')
if wait_timeout is None:
wait_timeout = deprecated_timeout
if wait_timeout is None:
LOG.info(
'No Chart timeout specified, using default: %ss',
const.DEFAULT_CHART_TIMEOUT)
wait_timeout = const.DEFAULT_CHART_TIMEOUT
return wait_timeout
@staticmethod
def _convert_min_ready(chart):
wait_opts = chart.get('wait', {})
if len(wait_opts) > 0:
res_opts = wait_opts.get('resources', [])
for opt in res_opts:
if 'min_ready' in opt:
opt['min_ready'] = str(opt['min_ready'])
@staticmethod
def _gen_crd_template(chart_name, release_id, location,
values, wait_opts, pre_opts=[], test_enabled=True):
return {
'apiVersion': const.CHART_APIVERSION,
'kind': const.CHART_KIND,
'metadata': {
'labels': {
const.CHART_LABEL: release_id.name
},
'name': release_id.name,
'namespace': release_id.namespace,
},
'data': {
'chart_name': chart_name,
'namespace': release_id.namespace,
'release': release_id.name,
'source': {
'location': location,
},
'values': values,
'test': {'enabled': test_enabled},
'delete': pre_opts,
'wait': wait_opts,
}
}