Add dispatch to all managers

This allows the agent to call a method on all hardware managers
that support the hardware and return a list of their responses. This
will be useful for implementing get_clean_steps() in cleaning.

Also fixes a leaky test in dispatch_to_managers. The function
wrapper was holding global state, which leaked into other tests
if a function was called again later, messing up the call count.

Implements blueprint implement-cleaning-states
Change-Id: I76bf8ec18df1dc16c4b9d942800b8a1efcde9e65
This commit is contained in:
Josh Gachnang
2015-03-03 14:12:44 -08:00
parent 7f8b8871ec
commit 3e6b57a8bc
2 changed files with 136 additions and 19 deletions

View File

@@ -379,6 +379,46 @@ def _get_managers():
return _global_managers return _global_managers
def dispatch_to_all_managers(method, *args, **kwargs):
"""Dispatch a method to all hardware managers.
Dispatches the given method in priority order as sorted by
`_get_managers`. If the method doesn't exist or raises
IncompatibleHardwareMethodError, it continues to the next hardware manager.
All managers that have hardware support for this node will be called,
and their responses will be added to a dictionary of the form
{HardwareManagerClassName: response}.
:param method: hardware manager method to dispatch
:param *args: arguments to dispatched method
:param **kwargs: keyword arguments to dispatched method
:raises errors.HardwareManagerMethodNotFound: if all managers raise
IncompatibleHardwareMethodError.
:returns: a dictionary with keys for each hardware manager that returns
a response and the value as a list of results from that hardware
manager.
"""
responses = {}
managers = _get_managers()
for manager in managers:
if getattr(manager, method, None):
try:
response = getattr(manager, method)(*args, **kwargs)
except errors.IncompatibleHardwareMethodError:
LOG.debug('HardwareManager {0} does not support {1}'
.format(manager, method))
continue
responses[manager.__class__.__name__] = response
else:
LOG.debug('HardwareManager {0} does not have method {1}'
.format(manager, method))
if responses == {}:
raise errors.HardwareManagerMethodNotFound(method)
return responses
def dispatch_to_managers(method, *args, **kwargs): def dispatch_to_managers(method, *args, **kwargs):
"""Dispatch a method to best suited hardware manager. """Dispatch a method to best suited hardware manager.

View File

@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import collections
import mock import mock
from oslotest import base as test_base from oslotest import base as test_base
from stevedore import extension from stevedore import extension
@@ -21,18 +23,32 @@ from ironic_python_agent import hardware
def counted(fn): def counted(fn):
def wrapper(*args, **kwargs): def wrapper(self, *args, **kwargs):
wrapper.called += 1 try:
return fn(*args, **kwargs) counts = self._call_counts
wrapper.called = 0 except AttributeError:
wrapper.__name__ = fn.__name__ counts = self._call_counts = collections.Counter()
counts[fn.__name__] += 1
return fn(self, *args, **kwargs)
return wrapper return wrapper
class FakeGenericHardwareManager(hardware.HardwareManager): class FakeGenericHardwareManager(hardware.HardwareManager):
@counted @counted
def generic_only(self): def generic_only(self):
return True return 'generic_only'
@counted
def generic_none(self):
return None
@counted
def specific_none(self):
return 'generic'
@counted
def return_list(self):
return ['generic']
@counted @counted
def specific_only(self): def specific_only(self):
@@ -40,11 +56,11 @@ class FakeGenericHardwareManager(hardware.HardwareManager):
@counted @counted
def mainline_fail(self): def mainline_fail(self):
return True return 'generic_mainline_fail'
@counted @counted
def both_succeed(self): def both_succeed(self):
return True return 'generic_both'
@counted @counted
def unexpected_fail(self): def unexpected_fail(self):
@@ -58,7 +74,19 @@ class FakeGenericHardwareManager(hardware.HardwareManager):
class FakeMainlineHardwareManager(hardware.HardwareManager): class FakeMainlineHardwareManager(hardware.HardwareManager):
@counted @counted
def specific_only(self): def specific_only(self):
return True return 'specific_only'
@counted
def generic_none(self):
return 'specific'
@counted
def specific_none(self):
return None
@counted
def return_list(self):
return ['specific']
@counted @counted
def mainline_fail(self): def mainline_fail(self):
@@ -66,7 +94,7 @@ class FakeMainlineHardwareManager(hardware.HardwareManager):
@counted @counted
def both_succeed(self): def both_succeed(self):
return True return 'specific_both'
@counted @counted
def unexpected_fail(self): def unexpected_fail(self):
@@ -83,12 +111,12 @@ class TestMultipleHardwareManagerLoading(test_base.BaseTestCase):
fake_ep = mock.Mock() fake_ep = mock.Mock()
fake_ep.module_name = 'fake' fake_ep.module_name = 'fake'
fake_ep.attrs = ['fake attrs'] fake_ep.attrs = ['fake attrs']
ext1 = extension.Extension('fake_generic', fake_ep, None, self.generic_hwm = extension.Extension('fake_generic', fake_ep, None,
FakeGenericHardwareManager()) FakeGenericHardwareManager())
ext2 = extension.Extension('fake_mainline', fake_ep, None, self.mainline_hwm = extension.Extension('fake_mainline', fake_ep, None,
FakeMainlineHardwareManager()) FakeMainlineHardwareManager())
self.fake_ext_mgr = extension.ExtensionManager.make_test_instance([ self.fake_ext_mgr = extension.ExtensionManager.make_test_instance([
ext1, ext2 self.generic_hwm, self.mainline_hwm
]) ])
self.extension_mgr_patcher = mock.patch('stevedore.ExtensionManager') self.extension_mgr_patcher = mock.patch('stevedore.ExtensionManager')
@@ -103,12 +131,13 @@ class TestMultipleHardwareManagerLoading(test_base.BaseTestCase):
def test_mainline_method_only(self): def test_mainline_method_only(self):
hardware.dispatch_to_managers('specific_only') hardware.dispatch_to_managers('specific_only')
self.assertEqual(1, FakeMainlineHardwareManager.specific_only.called) self.assertEqual(
1, self.mainline_hwm.obj._call_counts['specific_only'])
def test_generic_method_only(self): def test_generic_method_only(self):
hardware.dispatch_to_managers('generic_only') hardware.dispatch_to_managers('generic_only')
self.assertEqual(1, FakeGenericHardwareManager.generic_only.called) self.assertEqual(1, self.generic_hwm.obj._call_counts['generic_only'])
def test_both_succeed(self): def test_both_succeed(self):
"""In the case where both managers will work; only the most specific """In the case where both managers will work; only the most specific
@@ -116,8 +145,8 @@ class TestMultipleHardwareManagerLoading(test_base.BaseTestCase):
""" """
hardware.dispatch_to_managers('both_succeed') hardware.dispatch_to_managers('both_succeed')
self.assertEqual(1, FakeMainlineHardwareManager.both_succeed.called) self.assertEqual(1, self.mainline_hwm.obj._call_counts['both_succeed'])
self.assertEqual(0, FakeGenericHardwareManager.both_succeed.called) self.assertEqual(0, self.generic_hwm.obj._call_counts['both_succeed'])
def test_mainline_fails(self): def test_mainline_fails(self):
"""Ensure that if the mainline manager is unable to run the method """Ensure that if the mainline manager is unable to run the method
@@ -125,8 +154,9 @@ class TestMultipleHardwareManagerLoading(test_base.BaseTestCase):
""" """
hardware.dispatch_to_managers('mainline_fail') hardware.dispatch_to_managers('mainline_fail')
self.assertEqual(1, FakeMainlineHardwareManager.mainline_fail.called) self.assertEqual(
self.assertEqual(1, FakeGenericHardwareManager.mainline_fail.called) 1, self.mainline_hwm.obj._call_counts['mainline_fail'])
self.assertEqual(1, self.generic_hwm.obj._call_counts['mainline_fail'])
def test_manager_method_not_found(self): def test_manager_method_not_found(self):
self.assertRaises(errors.HardwareManagerMethodNotFound, self.assertRaises(errors.HardwareManagerMethodNotFound,
@@ -138,6 +168,53 @@ class TestMultipleHardwareManagerLoading(test_base.BaseTestCase):
hardware.dispatch_to_managers, hardware.dispatch_to_managers,
'unexpected_fail') 'unexpected_fail')
def test_dispatch_to_all_managers_mainline_only(self):
results = hardware.dispatch_to_all_managers('generic_none')
self.assertEqual(1, self.generic_hwm.obj._call_counts['generic_none'])
self.assertEqual({'FakeGenericHardwareManager': None,
'FakeMainlineHardwareManager': 'specific'},
results)
def test_dispatch_to_all_managers_generic_method_only(self):
results = hardware.dispatch_to_all_managers('specific_none')
self.assertEqual(1, self.generic_hwm.obj._call_counts['specific_none'])
self.assertEqual({'FakeGenericHardwareManager': 'generic',
'FakeMainlineHardwareManager': None}, results)
def test_dispatch_to_all_managers_both_succeed(self):
"""In the case where both managers will work; only the most specific
manager should have it's function called.
"""
results = hardware.dispatch_to_all_managers('both_succeed')
self.assertEqual({'FakeGenericHardwareManager': 'generic_both',
'FakeMainlineHardwareManager': 'specific_both'},
results)
self.assertEqual(1, self.mainline_hwm.obj._call_counts['both_succeed'])
self.assertEqual(1, self.generic_hwm.obj._call_counts['both_succeed'])
def test_dispatch_to_all_managers_mainline_fails(self):
"""Ensure that if the mainline manager is unable to run the method
that we properly fall back to generic.
"""
hardware.dispatch_to_all_managers('mainline_fail')
self.assertEqual(
1, self.mainline_hwm.obj._call_counts['mainline_fail'])
self.assertEqual(1, self.generic_hwm.obj._call_counts['mainline_fail'])
def test_dispatch_to_all_managers_manager_method_not_found(self):
self.assertRaises(errors.HardwareManagerMethodNotFound,
hardware.dispatch_to_all_managers,
'unknown_method')
def test_dispatch_to_all_managers_method_fails(self):
self.assertRaises(RuntimeError,
hardware.dispatch_to_all_managers,
'unexpected_fail')
class TestNoHardwareManagerLoading(test_base.BaseTestCase): class TestNoHardwareManagerLoading(test_base.BaseTestCase):
def setUp(self): def setUp(self):