Merge "Implement Cleaning in DriverInterfaces"

This commit is contained in:
Jenkins 2015-03-02 20:41:09 +00:00 committed by Gerrit Code Review
commit 6d925f1f80
2 changed files with 156 additions and 3 deletions

View File

@ -123,9 +123,55 @@ class BaseDriver(object):
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)
class DeployInterface(object):
class DeployInterface(BaseInterface):
"""Interface for deploy-related actions."""
interface_type = 'deploy'
@abc.abstractmethod
def get_properties(self):
@ -228,8 +274,9 @@ class DeployInterface(object):
@six.add_metaclass(abc.ABCMeta)
class PowerInterface(object):
class PowerInterface(BaseInterface):
"""Interface for power-related actions."""
interface_type = 'power'
@abc.abstractmethod
def get_properties(self):
@ -510,8 +557,9 @@ class VendorInterface(object):
@six.add_metaclass(abc.ABCMeta)
class ManagementInterface(object):
class ManagementInterface(BaseInterface):
"""Interface for management related actions."""
interface_type = 'management'
@abc.abstractmethod
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):
"""Decorator for a driver-specific periodic task.

View File

@ -111,3 +111,66 @@ class DriverPeriodicTaskTestCase(base.TestCase):
function()
function_mock.assert_called_once_with()
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)