Merge "Fix recovery action passing"
This commit is contained in:
commit
4d02927633
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue