Merge "Fix recovery action passing"

This commit is contained in:
Jenkins 2017-02-07 07:47:52 +00:00 committed by Gerrit Code Review
commit 4d02927633
10 changed files with 92 additions and 34 deletions

View File

@ -651,11 +651,9 @@ class ClusterAction(base.Action):
recover_action = pd.get('recover_action', None)
fencing = pd.get('fencing', None)
if recover_action is not None:
# TODO(Qiming): Implement the support to action sequences
inputs['operation'] = recover_action[0]['name']
inputs['params'] = recover_action[0]['params'] or {}
inputs['operation'] = recover_action
if fencing is not None and 'COMPUTE' in fencing:
inputs['params']['force'] = True
inputs['params'] = {'fence_compute': True}
children = []
for node in self.entity.nodes:

View File

@ -338,6 +338,9 @@ class Node(object):
"""recover a node.
This function is supposed to be invoked from a NODE_RECOVER action.
:param context: The request context of the action.
:param dict options: A map containing the recovery actions (with
parameters if any) and fencing settings.
"""
if not self.physical_id:
return False

View File

@ -1765,7 +1765,13 @@ class EngineService(service.Service):
'status': action_mod.Action.READY
}
if req.obj_attr_is_set('params') and req.params:
kwargs['inputs'] = req.params
if 'operation' in req.params:
op_name = req.params['operation']
kwargs['inputs'] = {'operation': [{'name': op_name}]}
else:
msg = _("Action parameter is not recognizable.")
raise exception.BadRequest(msg=msg)
action_id = action_mod.Action.create(ctx, db_node.id,
consts.NODE_RECOVER, **kwargs)
dispatcher.start_action()

View File

@ -404,20 +404,26 @@ class Profile(object):
def do_recover(self, obj, **options):
"""Default recover operation.
This is provided as a fallback if a specific profile type does not
override this method.
:param obj: The node object to operate on.
:param options: Keyword arguments for the recover operation.
"""
operation = options.pop('operation', None)
# TODO(Qiming): The operation input could be a list of operations.
# The operation is a list of action names with optional parameters
if operation and not isinstance(operation, six.string_types):
operation = operation[0]
if operation and operation != consts.RECOVER_RECREATE:
if operation and operation['name'] != consts.RECOVER_RECREATE:
LOG.error(_LE("Recover operation not supported: %s"), operation)
return False
extra_params = options.get('params', {})
fence_compute = extra_params.get('fence_compute', False)
try:
self.do_delete(obj, **options)
self.do_delete(obj, force=fence_compute)
except exc.EResourceDeletion as ex:
raise exc.EResourceOperation(op='recovering', type='node',
id=obj.id, message=six.text_type(ex))

View File

@ -13,6 +13,7 @@
import base64
import copy
from oslo_log import log as logging
from oslo_utils import encodeutils
import six
@ -20,11 +21,13 @@ from senlin.common import constraints
from senlin.common import consts
from senlin.common import context
from senlin.common import exception as exc
from senlin.common.i18n import _
from senlin.common.i18n import _, _LE
from senlin.common import schema
from senlin.objects import node as node_obj
from senlin.profiles import base
LOG = logging.getLogger(__name__)
class ServerProfile(base.Profile):
"""Profile for an OpenStack Nova server."""
@ -1076,15 +1079,26 @@ class ServerProfile(base.Profile):
return True
def do_recover(self, obj, **options):
# NOTE: We do a 'get' not a 'pop' here, because the operations may
# get fall back to the base class for handling
"""Handler for recover operation.
:param obj: The node object.
:param dict options: A list for operations each of which has a name
and optionally a map from parameter to values.
"""
operation = options.get('operation', None)
if operation and not isinstance(operation, six.string_types):
operation = operation[0]
if operation == 'REBUILD':
return self.handle_rebuild(obj)
op_name = operation['name']
if op_name.upper() != consts.RECOVER_RECREATE:
op_params = operation.get('params', {})
if op_name.lower() not in self.OP_NAMES:
LOG.error(_LE("The operation '%s' is not supported"), op_name)
return False
method = getattr(self, "handle_" + op_name.lower())
return method(obj, **op_params)
return super(ServerProfile, self).do_recover(obj, **options)

View File

@ -112,7 +112,10 @@ class ClusterRecoverTest(base.SenlinTestCase):
action.context, 'NODE_1', 'NODE_RECOVER',
name='node_recover_NODE_1',
cause=consts.CAUSE_DERIVED,
inputs={'operation': 'REBOOT', 'params': {'force': True}}
inputs={
'operation': [{'name': 'REBOOT', 'params': None}],
'params': {'fence_compute': True}
}
)
mock_dep.assert_called_once_with(action.context, ['NODE_RECOVER_ID'],
'CLUSTER_ACTION_ID')

View File

@ -690,7 +690,7 @@ class NodeTest(base.SenlinTestCase):
mock_find.return_value = mock.Mock(id='12345678AB')
mock_action.return_value = 'ACTION_ID'
params = {'k1': 'v1'}
params = {'operation': 'some_action'}
req = orno.NodeRecoverRequest(identity='FAKE_NODE', params=params)
result = self.eng.node_recover(self.ctx, req.obj_to_primitive())
@ -701,7 +701,7 @@ class NodeTest(base.SenlinTestCase):
name='node_recover_12345678',
cause=consts.CAUSE_RPC,
status=action_mod.Action.READY,
inputs={'k1': 'v1'})
inputs={'operation': [{'name': 'some_action'}]})
mock_start.assert_called_once_with()
@mock.patch.object(no.Node, 'find')
@ -718,6 +718,24 @@ class NodeTest(base.SenlinTestCase):
six.text_type(ex.exc_info[1]))
mock_find.assert_called_once_with(self.ctx, 'Bogus')
@mock.patch.object(action_mod.Action, 'create')
@mock.patch.object(no.Node, 'find')
def test_node_recover_invalid_operation(self, mock_find, mock_action):
mock_find.return_value = mock.Mock(id='12345678AB')
mock_action.return_value = 'ACTION_ID'
params = {'bogus': 'illegal'}
req = orno.NodeRecoverRequest(identity='FAKE_NODE', params=params)
ex = self.assertRaises(rpc.ExpectedException,
self.eng.node_recover,
self.ctx, req.obj_to_primitive())
self.assertEqual(exc.BadRequest, ex.exc_info[0])
self.assertEqual("Action parameter is not recognizable.",
six.text_type(ex.exc_info[1]))
mock_find.assert_called_once_with(self.ctx, 'FAKE_NODE')
self.assertEqual(0, mock_action.call_count)
@mock.patch.object(dispatcher, 'start_action')
@mock.patch.object(action_mod.Action, 'create')
@mock.patch.object(node_mod.Node, 'load')

View File

@ -630,7 +630,7 @@ class TestNode(base.SenlinTestCase):
node = nodem.Node('node1', PROFILE_ID, None)
node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
params = {'operation': 'foo'}
params = {'operation': [{'name': 'foo'}]}
res = node.do_recover(self.context, **params)
self.assertFalse(res)

View File

@ -889,7 +889,7 @@ class TestNovaServerBasic(base.SenlinTestCase):
profile = server.ServerProfile('t', self.spec)
node_obj = mock.Mock(physical_id='FAKE_ID')
res = profile.do_recover(node_obj, operation='REBUILD')
res = profile.do_recover(node_obj, operation=[{'name': 'REBUILD'}])
self.assertEqual(mock_rebuild.return_value, res)
mock_rebuild.assert_called_once_with(node_obj)
@ -899,21 +899,30 @@ class TestNovaServerBasic(base.SenlinTestCase):
profile = server.ServerProfile('t', self.spec)
node_obj = mock.Mock(physical_id='FAKE_ID')
res = profile.do_recover(node_obj, operation=['REBUILD'])
res = profile.do_recover(node_obj, operation=[{'name': 'REBUILD'}])
self.assertEqual(mock_rebuild.return_value, res)
mock_rebuild.assert_called_once_with(node_obj)
@mock.patch.object(profiles_base.Profile, 'do_recover')
def test_do_recover_bad_operation(self, mock_base_recover):
profile = server.ServerProfile('t', self.spec)
node_obj = mock.Mock(physical_id='FAKE_ID')
res = profile.do_recover(node_obj, operation=[{'name': 'BLAHBLAH'}])
self.assertFalse(res)
@mock.patch.object(profiles_base.Profile, 'do_recover')
def test_do_recover_fallback(self, mock_base_recover):
profile = server.ServerProfile('t', self.spec)
node_obj = mock.Mock(physical_id='FAKE_ID')
res = profile.do_recover(node_obj, operation='blahblah')
res = profile.do_recover(node_obj, operation=[{'name': 'RECREATE'}])
self.assertEqual(mock_base_recover.return_value, res)
mock_base_recover.assert_called_once_with(node_obj,
operation='blahblah')
mock_base_recover.assert_called_once_with(
node_obj, operation=[{'name': 'RECREATE'}])
def test_handle_reboot(self):
obj = mock.Mock(physical_id='FAKE_ID')

View File

@ -710,10 +710,7 @@ class TestProfileBase(base.SenlinTestCase):
res = profile.do_recover(mock.Mock())
self.assertTrue(res)
res = profile.do_recover(mock.Mock(), foo='bar')
self.assertTrue(res)
res = profile.do_recover(mock.Mock(), operation='bar')
res = profile.do_recover(mock.Mock(), operation=[{'name': 'bar'}])
self.assertFalse(res)
def test_do_recover_with_fencing(self):
@ -722,19 +719,20 @@ class TestProfileBase(base.SenlinTestCase):
self.patchobject(profile, 'do_delete', return_value=True)
obj = mock.Mock()
res = profile.do_recover(obj, ignore_missing=True, force=True)
self.assertTrue(res)
res = profile.do_recover(obj, ignore_missing=True,
params={"fence_compute": True})
profile.do_delete.assert_called_once_with(
obj, ignore_missing=True, force=True)
self.assertTrue(res)
profile.do_delete.assert_called_once_with(obj, force=True)
profile.do_create.assert_called_once_with(obj)
def test_do_recover_with_recreate_succeeded(self):
profile = self._create_profile('test-profile')
self.patchobject(profile, 'do_delete', return_value=True)
self.patchobject(profile, 'do_create', return_value=True)
res = profile.do_recover(mock.Mock(), operation='RECREATE')
operation = [{"name": "RECREATE"}]
res = profile.do_recover(mock.Mock(), operation=operation)
self.assertTrue(res)
def test_do_recover_with_recreate_failed_delete(self):
@ -742,10 +740,11 @@ class TestProfileBase(base.SenlinTestCase):
err = exception.EResourceDeletion(type='STACK', id='ID',
message='BANG')
self.patchobject(profile, 'do_delete', side_effect=err)
operation = [{"name": "RECREATE"}]
ex = self.assertRaises(exception.EResourceOperation,
profile.do_recover,
mock.Mock(id='NODE_ID'), operation='RECREATE')
mock.Mock(id='NODE_ID'), operation=operation)
self.assertEqual("Failed in recovering node 'NODE_ID': "
"Failed in deleting STACK 'ID': BANG.",
six.text_type(ex))
@ -755,10 +754,12 @@ class TestProfileBase(base.SenlinTestCase):
self.patchobject(profile, 'do_delete', return_value=True)
err = exception.EResourceCreation(type='STACK', message='BANNG')
self.patchobject(profile, 'do_create', side_effect=err)
operation = [{"name": "RECREATE"}]
ex = self.assertRaises(exception.EResourceOperation,
profile.do_recover,
mock.Mock(id='NODE_ID'), operation='RECREATE')
mock.Mock(id='NODE_ID'), operation=operation)
msg = ("Failed in recovering node 'NODE_ID': Failed in creating "
"STACK: BANNG.")
self.assertEqual(msg, six.text_type(ex))