Merge "Expose helm's upgrade/rollback force and recreate pods flags"

This commit is contained in:
Zuul 2018-06-13 19:38:12 +00:00 committed by Gerrit Code Review
commit 5c8b4eb8e2
10 changed files with 296 additions and 27 deletions

View File

@ -45,7 +45,9 @@ class Rollback(api.BaseResource):
release,
req.get_param_as_int('version') or 0,
wait=req.get_param_as_bool('wait'),
timeout=req.get_param_as_int('timeout') or 0)
timeout=req.get_param_as_int('timeout') or 0,
force=req.get_param_as_bool('force'),
recreate_pods=req.get_param_as_bool('recreate_pods'))
resp.body = json.dumps(
{

View File

@ -73,15 +73,24 @@ SHORT_DESC = "Command performs a release rollback."
@click.option('--wait',
help=("Wait until rollback is complete before returning."),
is_flag=True)
@click.option('--force',
help=("Force resource update through delete/recreate if"
" needed."),
is_flag=True)
@click.option('--recreate-pods',
help=("Restarts pods for the resource if applicable."),
is_flag=True)
@click.option('--debug',
help="Enable debug logging.",
is_flag=True)
@click.pass_context
def rollback_charts(ctx, release, version, dry_run, tiller_host, tiller_port,
tiller_namespace, timeout, wait, debug):
tiller_namespace, timeout, wait, force, recreate_pods,
debug):
CONF.debug = debug
Rollback(ctx, release, version, dry_run, tiller_host, tiller_port,
tiller_namespace, timeout, wait).safe_invoke()
tiller_namespace, timeout, wait, force,
recreate_pods).safe_invoke()
class Rollback(CliAction):
@ -94,7 +103,9 @@ class Rollback(CliAction):
tiller_port,
tiller_namespace,
timeout,
wait):
wait,
force,
recreate_pods):
super(Rollback, self).__init__()
self.ctx = ctx
self.release = release
@ -105,6 +116,8 @@ class Rollback(CliAction):
self.tiller_namespace = tiller_namespace
self.timeout = timeout
self.wait = wait
self.force = force
self.recreate_pods = recreate_pods
def invoke(self):
tiller = Tiller(
@ -115,7 +128,9 @@ class Rollback(CliAction):
self.release,
self.version,
wait=self.wait,
timeout=self.timeout)
timeout=self.timeout,
force=self.force,
recreate_pods=self.recreate_pods)
self.output(response)

View File

@ -333,6 +333,8 @@ class Armada(object):
upgrade = chart.get('upgrade', {})
disable_hooks = upgrade.get('no_hooks', False)
force = upgrade.get('force', False)
recreate_pods = upgrade.get('recreate_pods', False)
LOG.info("Checking Pre/Post Actions")
if upgrade:
@ -374,7 +376,9 @@ class Armada(object):
disable_hooks=disable_hooks,
values=yaml.safe_dump(values),
wait=this_chart_should_wait,
timeout=timer)
timeout=timer,
force=force,
recreate_pods=recreate_pods)
if this_chart_should_wait:
self._wait_until_ready(

View File

@ -51,8 +51,18 @@ CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class TillerResult(object):
class CommonEqualityMixin(object):
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
class TillerResult(CommonEqualityMixin):
'''Object to hold Tiller results for Armada.'''
def __init__(self, release, namespace, status, description, version):
self.release = release
self.namespace = namespace
@ -318,15 +328,18 @@ class Tiller(object):
disable_hooks=False,
values=None,
wait=False,
timeout=None):
timeout=None,
force=False,
recreate_pods=False):
'''
Update a Helm Release
'''
timeout = self._check_timeout(wait, timeout)
LOG.info('Helm update release%s: wait=%s, timeout=%s',
LOG.info('Helm update release%s: wait=%s, timeout=%s, force=%s, '
'recreate_pods=%s',
(' (dry run)' if self.dry_run else ''),
wait, timeout)
wait, timeout, force, recreate_pods)
if values is None:
values = Config(raw='')
@ -336,6 +349,7 @@ class Tiller(object):
self._pre_update_actions(pre_actions, release, namespace, chart,
disable_hooks, values, timeout)
update_msg = None
# build release install request
try:
stub = ReleaseServiceStub(self.channel)
@ -346,21 +360,14 @@ class Tiller(object):
values=values,
name=release,
wait=wait,
timeout=timeout)
timeout=timeout,
force=force,
recreate=recreate_pods)
update_msg = stub.UpdateRelease(
release_request, timeout + GRPC_EPSILON,
metadata=self.metadata)
tiller_result = TillerResult(
update_msg.release.name,
update_msg.release.namespace,
update_msg.release.info.status.Code.Name(
update_msg.release.info.status.code),
update_msg.release.info.Description,
update_msg.release.version)
return tiller_result
except Exception:
LOG.exception('Error while updating release %s', release)
status = self.get_release_status(release)
@ -368,6 +375,16 @@ class Tiller(object):
self._post_update_actions(post_actions, namespace)
tiller_result = TillerResult(
update_msg.release.name,
update_msg.release.namespace,
update_msg.release.info.status.Code.Name(
update_msg.release.info.status.code),
update_msg.release.info.Description,
update_msg.release.version)
return tiller_result
def install_release(self, chart, release, namespace,
values=None,
wait=False,
@ -686,7 +703,9 @@ class Tiller(object):
release_name,
version,
wait=False,
timeout=None):
timeout=None,
force=False,
recreate_pods=False):
'''
Rollback a helm release.
'''
@ -704,7 +723,9 @@ class Tiller(object):
version=version,
dry_run=self.dry_run,
wait=wait,
timeout=timeout)
timeout=timeout,
force=force,
recreate=recreate_pods)
rollback_msg = stub.RollbackRelease(
rollback_request,

View File

@ -109,6 +109,14 @@ data:
properties:
create:
$ref: '#/definitions/hook_action'
options:
type: object
properties:
force:
type: boolean
recreate_pods:
type: boolean
additionalProperties: false
required:
- no_hooks
additionalProperties: false

View File

@ -32,7 +32,45 @@ class RollbackReleaseControllerTest(base.BaseControllerTest):
rollback_release = mock_tiller.return_value.rollback_release
rollback_release.return_value = None
resp = self.app.simulate_post('/api/v1.0/rollback/test-release')
tiller_host = 'host'
tiller_port = '8080'
tiller_namespace = 'tn'
release = 'test-release'
version = '2'
dry_run = 'false'
wait = 'true'
timeout = '123'
force = 'true'
recreate_pods = 'true'
resp = self.app.simulate_post(
'/api/v1.0/rollback/{}'.format(release),
params={
'tiller_host': tiller_host,
'tiller_port': tiller_port,
'tiller_namespace': tiller_namespace,
'dry_run': dry_run,
'version': version,
'wait': wait,
'timeout': timeout,
'force': force,
'recreate_pods': recreate_pods
})
mock_tiller.assert_called_once_with(
tiller_host=tiller_host,
tiller_port=8080,
tiller_namespace=tiller_namespace,
dry_run=False)
rollback_release.assert_called_once_with(
release,
2,
wait=True,
timeout=123,
force=True,
recreate_pods=True)
self.assertEqual(200, resp.status_code)
self.assertEqual('Rollback of test-release complete.',
json.loads(resp.text)['message'])

View File

@ -59,6 +59,11 @@ data:
dependencies: []
wait:
timeout: 10
upgrade:
no_hooks: false
options:
force: true
recreate_pods: true
---
schema: armada/Chart/v1
metadata:
@ -129,6 +134,13 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
'values': {},
'wait': {
'timeout': 10
},
'upgrade': {
'no_hooks': False,
'options': {
'force': True,
'recreate_pods': True
}
}
}
}
@ -279,6 +291,11 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
)
)
else:
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(),
@ -288,7 +305,9 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
chart['namespace'],
pre_actions={},
post_actions={},
disable_hooks=False,
disable_hooks=disable_hooks,
force=force,
recreate_pods=recreate_pods,
values=yaml.safe_dump(chart['values']),
wait=this_chart_should_wait,
timeout=chart['wait']['timeout']

View File

@ -13,6 +13,7 @@
# limitations under the License.
import mock
from mock import MagicMock
from armada.exceptions import tiller_exceptions as ex
from armada.handlers import tiller
@ -289,9 +290,29 @@ class TillerTestCase(base.ArmadaTestCase):
mock_release_service_stub.return_value.RollbackRelease\
.return_value = {}
tiller_obj = tiller.Tiller('host', '8080', None)
dry_run = True
self.assertIsNone(tiller_obj.rollback_release('release', 0))
tiller_obj = tiller.Tiller('host', '8080', None, dry_run=dry_run)
release = 'release'
version = 0
wait = True
timeout = 123
recreate_pods = True
force = True
self.assertIsNone(tiller_obj.rollback_release(
release, version, wait=wait, timeout=timeout, force=force,
recreate_pods=recreate_pods))
mock_rollback_release_request.assert_called_once_with(
name=release,
version=version,
dry_run=dry_run,
wait=wait,
timeout=timeout,
force=force,
recreate=recreate_pods)
mock_release_service_stub.assert_called_once_with(
tiller_obj.channel)
@ -299,6 +320,109 @@ class TillerTestCase(base.ArmadaTestCase):
RollbackRelease
rollback_release_stub.assert_called_once_with(
mock_rollback_release_request.return_value, tiller_obj.timeout +
mock_rollback_release_request.return_value, timeout +
tiller.GRPC_EPSILON,
metadata=tiller_obj.metadata)
@mock.patch('armada.handlers.tiller.K8s')
@mock.patch('armada.handlers.tiller.grpc')
@mock.patch('armada.handlers.tiller.Config')
@mock.patch.object(tiller, 'UpdateReleaseRequest')
@mock.patch.object(tiller, 'ReleaseServiceStub')
def test_update_release(self, mock_release_service_stub,
mock_update_release_request, mock_config,
_, __):
release = 'release'
chart = {}
namespace = 'namespace'
code = 0
status = 'DEPLOYED'
description = 'desc'
version = 2
values = mock_config(raw=None)
mock_release_service_stub.return_value.UpdateRelease.return_value =\
AttrDict(**{
'release': AttrDict(**{
'name': release,
'namespace': namespace,
'info': AttrDict(**{
'status': AttrDict(**{
'Code': AttrDict(**{
'Name': lambda c:
status if c == code else None
}),
'code': code
}),
'Description': description
}),
'version': version
})
})
tiller_obj = tiller.Tiller('host', '8080', None, dry_run=False)
# TODO: Test these methods as well, either by unmocking, or adding
# separate tests for them.
tiller_obj._pre_update_actions = MagicMock()
tiller_obj._post_update_actions = MagicMock()
pre_actions = {}
post_actions = {}
disable_hooks = False
wait = True
timeout = 123
force = True
recreate_pods = True
result = tiller_obj.update_release(
chart, release, namespace,
pre_actions=pre_actions,
post_actions=post_actions,
disable_hooks=disable_hooks,
values=values,
wait=wait,
timeout=timeout,
force=force,
recreate_pods=recreate_pods)
tiller_obj._pre_update_actions.assert_called_once_with(
pre_actions, release, namespace, chart, disable_hooks, values,
timeout)
tiller_obj._post_update_actions.assert_called_once_with(
post_actions, namespace)
mock_update_release_request.assert_called_once_with(
chart=chart,
name=release,
dry_run=tiller_obj.dry_run,
disable_hooks=False,
values=values,
wait=wait,
timeout=timeout,
force=force,
recreate=recreate_pods)
mock_release_service_stub.assert_called_once_with(
tiller_obj.channel)
update_release_stub = mock_release_service_stub.return_value. \
UpdateRelease
update_release_stub.assert_called_once_with(
mock_update_release_request.return_value, timeout +
tiller.GRPC_EPSILON,
metadata=tiller_obj.metadata)
expected_result = tiller.TillerResult(
release,
namespace,
status,
description,
version)
self.assertEqual(expected_result, result)
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self

View File

@ -197,6 +197,8 @@ paths:
- $ref: "#/parameters/dry-run"
- $ref: "#/parameters/wait"
- $ref: "#/parameters/timeout"
- $ref: "#/parameters/force"
- $ref: "#/parameters/recreate-pods"
responses:
'200':
$ref: "#/responses/response-post-rollback-release"
@ -307,6 +309,22 @@ parameters:
description: Specifies time in seconds Tiller should wait for the action to
complete before timing out.
default: 3600
force:
in: query
name: force
required: false
type: boolean
description: Specifies whether to force resource update through
delete/recreate if needed.
default: False
recreate-pods:
in: query
name: recreate_pods
required: false
type: boolean
description: Specifies whether to restart pods for the resource if
applicable.
default: False
responses:
# HTTP error responses
err-bad-request:

View File

@ -221,6 +221,8 @@ paths:
- $ref: "#/components/parameters/dry-run"
- $ref: "#/components/parameters/wait"
- $ref: "#/components/parameters/timeout"
- $ref: "#/components/parameters/force"
- $ref: "#/components/parameters/recreate-pods"
responses:
'200':
$ref: "#/components/responses/response-post-rollback-release"
@ -356,6 +358,24 @@ components:
description: Specifies whether Tiller should wait until all charts are deployed
schema:
type: boolean
force:
in: query
name: force
required: false
description: Specifies whether to force resource update through
delete/recreate if needed.
schema:
type: boolean
default: false
recreate-pods:
in: query
name: recreate_pods
required: false
description: Specifies whether to restart pods for the resource if
applicable.
schema:
type: boolean
default: false
requestBodies:
apply-body:
required: true