Merge "Implement Cleaning in DriverInterfaces"
This commit is contained in:
commit
6d925f1f80
@ -123,9 +123,55 @@ class BaseDriver(object):
|
|||||||
return properties
|
return properties
|
||||||
|
|
||||||
|
|
||||||
|
class BaseInterface(object):
|
||||||
|
"""A base interface implementing common functions for Driver Interfaces."""
|
||||||
|
interface_type = 'base'
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
# Cache the clean step iteration. We use __new__ instead of __init___
|
||||||
|
# to avoid breaking backwards compatibility with all the drivers.
|
||||||
|
# We want to return all steps, regardless of priority.
|
||||||
|
instance = super(BaseInterface, cls).__new__(cls, *args, **kwargs)
|
||||||
|
instance.clean_steps = []
|
||||||
|
for n, method in inspect.getmembers(instance, inspect.ismethod):
|
||||||
|
if getattr(method, '_is_clean_step', False):
|
||||||
|
# Create a CleanStep to represent this method
|
||||||
|
step = {'step': method.__name__,
|
||||||
|
'priority': method._clean_step_priority,
|
||||||
|
'interface': instance.interface_type}
|
||||||
|
instance.clean_steps.append(step)
|
||||||
|
LOG.debug('Found clean steps %(steps)s for interface %(interface)s' %
|
||||||
|
{'steps': instance.clean_steps,
|
||||||
|
'interface': instance.interface_type})
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def get_clean_steps(self):
|
||||||
|
"""Get a list of enabled and disabled CleanSteps for the interface."""
|
||||||
|
return self.clean_steps
|
||||||
|
|
||||||
|
def execute_clean_step(self, task, step):
|
||||||
|
"""Execute a the clean step on task.node.
|
||||||
|
|
||||||
|
Clean steps should take a single argument: a TaskManager object.
|
||||||
|
Steps can be executed synchronously or asynchronously. Steps should
|
||||||
|
return None if the method has completed synchronously or
|
||||||
|
states.CLEANING if the step will continue to execute asynchronously.
|
||||||
|
If the step executes asynchronously, it should issue a call to the
|
||||||
|
'continue_node_clean' RPC, so the conductor can begin the next
|
||||||
|
clean step.
|
||||||
|
|
||||||
|
:param task: A TaskManager object
|
||||||
|
:param step: A CleanStep object to execute
|
||||||
|
:returns: states.CLEANING if the method is complete, or None if
|
||||||
|
the step will continue to execute asynchronously.
|
||||||
|
"""
|
||||||
|
return getattr(self, step.get('step'))(task)
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class DeployInterface(object):
|
class DeployInterface(BaseInterface):
|
||||||
"""Interface for deploy-related actions."""
|
"""Interface for deploy-related actions."""
|
||||||
|
interface_type = 'deploy'
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
@ -228,8 +274,9 @@ class DeployInterface(object):
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class PowerInterface(object):
|
class PowerInterface(BaseInterface):
|
||||||
"""Interface for power-related actions."""
|
"""Interface for power-related actions."""
|
||||||
|
interface_type = 'power'
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
@ -510,8 +557,9 @@ class VendorInterface(object):
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class ManagementInterface(object):
|
class ManagementInterface(BaseInterface):
|
||||||
"""Interface for management related actions."""
|
"""Interface for management related actions."""
|
||||||
|
interface_type = 'management'
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
@ -655,6 +703,48 @@ class InspectInterface(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def clean_step(priority):
|
||||||
|
"""Decorator for cleaning and zapping steps.
|
||||||
|
|
||||||
|
If priority is greater than 0, the function will be executed as part of the
|
||||||
|
CLEANING state for any node using the interface with the decorated clean
|
||||||
|
step. During CLEANING, a list of steps will be ordered by priority for all
|
||||||
|
interfaces associated with the node, and then execute_clean_step() will be
|
||||||
|
called on each step. Steps will be executed based on priority, with the
|
||||||
|
highest priority step being called first, the next highest priority
|
||||||
|
being call next, and so on.
|
||||||
|
|
||||||
|
Decorated clean steps should take a single argument, a TaskManager object.
|
||||||
|
|
||||||
|
Any step with this decorator will be available for ZAPPING, even if
|
||||||
|
priority is set to 0. Zapping steps will be executed in a similar fashion
|
||||||
|
to cleaning and with the same TaskManager object, but the priority ordering
|
||||||
|
is determined by the user when calling the zapping API.
|
||||||
|
|
||||||
|
Clean steps can be either synchronous or asynchronous. If the step is
|
||||||
|
synchronous, it should return `None` when finished, and the conductor will
|
||||||
|
continue on to the next step. If the step is asynchronous, the step should
|
||||||
|
return `states.CLEANING` to signal to the conductor. When the step is
|
||||||
|
complete, the step should make an RPC call to `continue_node_clean` to move
|
||||||
|
to the next step in cleaning.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
class MyInterface(base.BaseInterface):
|
||||||
|
# CONF.example_cleaning_priority should be an int CONF option
|
||||||
|
@base.clean_step(priority=CONF.example_cleaning_priority)
|
||||||
|
def example_cleaning(self, task):
|
||||||
|
# do some cleaning
|
||||||
|
|
||||||
|
:param priority: an integer priority, should be a CONF option
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
func._is_clean_step = True
|
||||||
|
func._clean_step_priority = priority
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def driver_periodic_task(parallel=True, **other):
|
def driver_periodic_task(parallel=True, **other):
|
||||||
"""Decorator for a driver-specific periodic task.
|
"""Decorator for a driver-specific periodic task.
|
||||||
|
|
||||||
|
@ -111,3 +111,66 @@ class DriverPeriodicTaskTestCase(base.TestCase):
|
|||||||
function()
|
function()
|
||||||
function_mock.assert_called_once_with()
|
function_mock.assert_called_once_with()
|
||||||
self.assertEqual(1, spawn_mock.call_count)
|
self.assertEqual(1, spawn_mock.call_count)
|
||||||
|
|
||||||
|
|
||||||
|
class CleanStepTestCase(base.TestCase):
|
||||||
|
def test_get_and_execute_clean_steps(self):
|
||||||
|
# Create a fake Driver class, create some clean steps, make sure
|
||||||
|
# they are listed correctly, and attempt to execute one of them
|
||||||
|
|
||||||
|
method_mock = mock.Mock()
|
||||||
|
task_mock = mock.Mock()
|
||||||
|
|
||||||
|
class TestClass(driver_base.BaseInterface):
|
||||||
|
interface_type = 'test'
|
||||||
|
|
||||||
|
@driver_base.clean_step(priority=0)
|
||||||
|
def zap_method(self, task):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@driver_base.clean_step(priority=10)
|
||||||
|
def clean_method(self, task):
|
||||||
|
method_mock(task)
|
||||||
|
|
||||||
|
def not_clean_method(self, task):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TestClass2(driver_base.BaseInterface):
|
||||||
|
interface_type = 'test2'
|
||||||
|
|
||||||
|
@driver_base.clean_step(priority=0)
|
||||||
|
def zap_method2(self, task):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@driver_base.clean_step(priority=20)
|
||||||
|
def clean_method2(self, task):
|
||||||
|
method_mock(task)
|
||||||
|
|
||||||
|
def not_clean_method2(self, task):
|
||||||
|
pass
|
||||||
|
|
||||||
|
obj = TestClass()
|
||||||
|
obj2 = TestClass2()
|
||||||
|
|
||||||
|
self.assertEqual(2, len(obj.get_clean_steps()))
|
||||||
|
# Ensure the steps look correct
|
||||||
|
self.assertEqual(10, obj.get_clean_steps()[0]['priority'])
|
||||||
|
self.assertEqual('test', obj.get_clean_steps()[0]['interface'])
|
||||||
|
self.assertEqual('clean_method', obj.get_clean_steps()[0]['step'])
|
||||||
|
self.assertEqual(0, obj.get_clean_steps()[1]['priority'])
|
||||||
|
self.assertEqual('test', obj.get_clean_steps()[1]['interface'])
|
||||||
|
self.assertEqual('zap_method', obj.get_clean_steps()[1]['step'])
|
||||||
|
|
||||||
|
# Ensure the second obj get different clean steps
|
||||||
|
self.assertEqual(2, len(obj2.get_clean_steps()))
|
||||||
|
# Ensure the steps look correct
|
||||||
|
self.assertEqual(20, obj2.get_clean_steps()[0]['priority'])
|
||||||
|
self.assertEqual('test2', obj2.get_clean_steps()[0]['interface'])
|
||||||
|
self.assertEqual('clean_method2', obj2.get_clean_steps()[0]['step'])
|
||||||
|
self.assertEqual(0, obj2.get_clean_steps()[1]['priority'])
|
||||||
|
self.assertEqual('test2', obj2.get_clean_steps()[1]['interface'])
|
||||||
|
self.assertEqual('zap_method2', obj2.get_clean_steps()[1]['step'])
|
||||||
|
|
||||||
|
# Ensure we can execute the function.
|
||||||
|
obj.execute_clean_step(task_mock, obj.get_clean_steps()[0])
|
||||||
|
method_mock.assert_called_once_with(task_mock)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user