Rollback cluster/bay on update failure

Rollback mechanism was added in Magnum in patch
https://review.openstack.org/343478 but currently
there is no support on the client side.

This patch add rollback support for Magnum cluster/bay,
user can enable rollback on cluster update failure by specifying
option '--rollback' when executing 'magnum cluster-update',
for example:

magnum cluster-update XXX replace node_count=5 --rollback

The code is based on outdated patch
https://review.openstack.org/347586 and improves it:
 * added rollback support for clusters
 * added API version check in CLI (should be >= 1.3)
 * added several unit tests

Change-Id: Iaae51e084e8ffd69be38512eec8c4c54b158b55e
Co-authored-by: Wenzhi Yu <wenzhi_yu@163.com>
Implements: blueprint bay-rollback-on-update-failure
This commit is contained in:
Mike Fedosin 2016-12-13 18:11:29 +03:00
parent b3d5948c77
commit 1aed004a50
7 changed files with 95 additions and 8 deletions

View File

@ -81,6 +81,13 @@ fake_responses = {
UPDATED_BAY,
),
},
'/v1/bays/%s/?rollback=True' % BAY1['id']:
{
'PATCH': (
{},
UPDATED_BAY,
),
},
'/v1/bays/%s' % BAY1['name']:
{
'GET': (
@ -297,3 +304,14 @@ class BayManagerTest(testtools.TestCase):
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_NAME, bay.name)
def test_bay_update_with_rollback(self):
patch = {'op': 'replace',
'value': NEW_NAME,
'path': '/name'}
bay = self.mgr.update(id=BAY1['id'], patch=patch, rollback=True)
expect = [
('PATCH', '/v1/bays/%s/?rollback=True' % BAY1['id'], {}, patch),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_NAME, bay.name)

View File

@ -245,14 +245,29 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
def test_bay_update_success(self, mock_update):
self._test_arg_success('bay-update test add test=test')
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
mock_update.assert_called_once_with('test', patch)
mock_update.assert_called_once_with('test', patch, False)
@mock.patch('magnumclient.v1.bays.BayManager.update')
def test_bay_update_success_many_attribute(self, mock_update):
self._test_arg_success('bay-update test add test=test test1=test1')
patch = [{'op': 'add', 'path': '/test', 'value': 'test'},
{'op': 'add', 'path': '/test1', 'value': 'test1'}]
mock_update.assert_called_once_with('test', patch)
mock_update.assert_called_once_with('test', patch, False)
@mock.patch('magnumclient.v1.bays.BayManager.update')
def test_bay_update_success_rollback(self, mock_update):
self._test_arg_success('bay-update test add test=test --rollback')
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
mock_update.assert_called_once_with('test', patch, True)
@mock.patch('magnumclient.v1.bays.BayManager.update')
def test_bay_update_rollback_old_api_version(self, mock_update):
self.assertRaises(
exceptions.CommandError,
self.shell,
'--magnum-api-version 1.2 bay-update '
'test add test=test --rollback')
mock_update.assert_not_called()
@mock.patch('magnumclient.v1.bays.BayManager.update')
def test_bay_update_failure_wrong_op(self, mock_update):

View File

@ -81,6 +81,13 @@ fake_responses = {
UPDATED_CLUSTER,
),
},
'/v1/clusters/%s/?rollback=True' % CLUSTER1['id']:
{
'PATCH': (
{},
UPDATED_CLUSTER,
),
},
'/v1/clusters/%s' % CLUSTER1['name']:
{
'GET': (
@ -313,3 +320,16 @@ class ClusterManagerTest(testtools.TestCase):
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_NAME, cluster.name)
def test_cluster_update_with_rollback(self):
patch = {'op': 'replace',
'value': NEW_NAME,
'path': '/name'}
cluster = self.mgr.update(id=CLUSTER1['id'], patch=patch,
rollback=True)
expect = [
('PATCH', '/v1/clusters/%s/?rollback=True' % CLUSTER1['id'],
{}, patch),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_NAME, cluster.name)

View File

@ -295,14 +295,29 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
def test_cluster_update_success(self, mock_update):
self._test_arg_success('cluster-update test add test=test')
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
mock_update.assert_called_once_with('test', patch)
mock_update.assert_called_once_with('test', patch, False)
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
def test_cluster_update_success_many_attribute(self, mock_update):
self._test_arg_success('cluster-update test add test=test test1=test1')
patch = [{'op': 'add', 'path': '/test', 'value': 'test'},
{'op': 'add', 'path': '/test1', 'value': 'test1'}]
mock_update.assert_called_once_with('test', patch)
mock_update.assert_called_once_with('test', patch, False)
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
def test_cluster_update_success_rollback(self, mock_update):
self._test_arg_success('cluster-update test add test=test --rollback')
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
mock_update.assert_called_once_with('test', patch, True)
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
def test_cluster_update_rollback_old_api_version(self, mock_update):
self.assertRaises(
exceptions.CommandError,
self.shell,
'--magnum-api-version 1.2 cluster-update '
'test add test=test --rollback')
mock_update.assert_not_called()
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
def test_cluster_update_failure_wrong_op(self, mock_update):

View File

@ -104,5 +104,8 @@ class BaseTemplateManager(base.Manager):
def delete(self, id):
return self._delete(self._path(id))
def update(self, id, patch):
return self._update(self._path(id), patch)
def update(self, id, patch, rollback=False):
url = self._path(id)
if rollback:
url += '/?rollback=True'
return self._update(url, patch)

View File

@ -176,6 +176,9 @@ def do_bay_show(cs, args):
@utils.arg('bay', metavar='<bay>', help="UUID or name of bay")
@utils.arg('--rollback',
action='store_true', default=False,
help='Rollback bay on update failure.')
@utils.arg(
'op',
metavar='<op>',
@ -195,8 +198,13 @@ def do_bay_update(cs, args):
(Deprecated in favor of cluster-update.)
"""
if args.rollback and args.magnum_api_version and \
args.magnum_api_version in ('1.0', '1.1', '1.2'):
raise exceptions.CommandError(
"Rollback is not supported in API v%s. "
"Please use API v1.3+." % args.magnum_api_version)
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
bay = cs.bays.update(args.bay, patch)
bay = cs.bays.update(args.bay, patch, args.rollback)
if args.magnum_api_version and args.magnum_api_version == '1.1':
_show_bay(bay)
else:

View File

@ -184,6 +184,9 @@ def do_cluster_show(cs, args):
@utils.arg('cluster', metavar='<cluster>', help="UUID or name of cluster")
@utils.arg('--rollback',
action='store_true', default=False,
help='Rollback cluster on update failure.')
@utils.arg(
'op',
metavar='<op>',
@ -199,8 +202,13 @@ def do_cluster_show(cs, args):
"(only PATH is necessary on remove)")
def do_cluster_update(cs, args):
"""Update information about the given cluster."""
if args.rollback and args.magnum_api_version and \
args.magnum_api_version in ('1.0', '1.1', '1.2'):
raise exceptions.CommandError(
"Rollback is not supported in API v%s. "
"Please use API v1.3+." % args.magnum_api_version)
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
cluster = cs.clusters.update(args.cluster, patch)
cluster = cs.clusters.update(args.cluster, patch, args.rollback)
if args.magnum_api_version and args.magnum_api_version == '1.1':
_show_cluster(cluster)
else: