Introduce ability to execute custom flow

Flow deploy driver is now capable of executing customly defined flow
of actions.
When custom flow is chooen, then the flow itself must be provided
within input data as a list of actions under 'custom_flow' key.
This is the only beginning and only 2 flows are made available:
custom and nailgun.

Partially implements blueprint: pluggable-do-actions

Change-Id: I3cf3fcabb47bdc7d1aadb97614310ee3184ed77c
This commit is contained in:
Alexander Gordeev 2016-05-18 20:37:12 +03:00
parent 3fb08a05ec
commit 2409996980
6 changed files with 67 additions and 26 deletions

View File

@ -28,9 +28,8 @@ class BaseDataDriver(object):
def __init__(self, data): def __init__(self, data):
self.data = copy.deepcopy(data) self.data = copy.deepcopy(data)
self.flow = [] if 'flow' in self.data:
if self.data and self.data.get('flow'): self.flow = self.data['flow']
self.flow = self.data.get('flow', [])
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)

View File

@ -15,21 +15,42 @@
from bareon.drivers.deploy import base from bareon.drivers.deploy import base
from bareon.drivers.deploy import mixins from bareon.drivers.deploy import mixins
from bareon import errors
from bareon.openstack.common import log as logging from bareon.openstack.common import log as logging
import stevedore.named import stevedore.named
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
FLOWS = {
'nailgun': [
'do_partitioning',
'do_configdrive',
'do_copyimage',
'do_bootloader'
],
'custom': [],
}
class Flow(base.SimpleDeployDriver, mixins.MountableMixin): class Flow(base.SimpleDeployDriver, mixins.MountableMixin):
def __init__(self, data_driver): def __init__(self, data_driver):
super(Flow, self).__init__(data_driver) super(Flow, self).__init__(data_driver)
requested_flow = self.driver.flow
if requested_flow in FLOWS:
if requested_flow == 'custom':
flow = self.driver.data.get('custom_flow', [])
if not flow:
raise errors.EmptyCustomFlow(
'Requested custom flow was empty')
else:
flow = FLOWS[requested_flow]
else:
raise errors.NonexistingFlow(
"Requested flow doesn't exist: %s" % requested_flow)
self.ext_mgr = stevedore.named.NamedExtensionManager( self.ext_mgr = stevedore.named.NamedExtensionManager(
'bareon.actions', names=self.driver.flow, 'bareon.actions', names=flow, invoke_on_load=True,
invoke_on_load=True, invoke_args=(self.driver, ), invoke_args=(self.driver, ), name_order=True)
name_order=True)
def execute_flow(self): def execute_flow(self):
for action in self.ext_mgr: for action in self.ext_mgr:

View File

@ -182,3 +182,11 @@ class IncorrectChroot(BaseError):
class TooManyKernels(BaseError): class TooManyKernels(BaseError):
pass pass
class EmptyCustomFlow(BaseError):
pass
class NonexistingFlow(BaseError):
pass

View File

@ -17,6 +17,7 @@ import unittest2
from bareon.actions import base as base_action from bareon.actions import base as base_action
from bareon.drivers.deploy import flow from bareon.drivers.deploy import flow
from bareon import errors
import stevedore import stevedore
@ -28,12 +29,18 @@ elif six.PY3:
class TestFlowDriver(unittest2.TestCase): class TestFlowDriver(unittest2.TestCase):
def setUp(self):
super(TestFlowDriver, self).setUp()
self.data_drv = mock.Mock(data={}, flow='custom')
@mock.patch.object(flow.Flow, '__init__', @mock.patch.object(flow.Flow, '__init__',
return_value=None) return_value=None)
def test_execute_flow(self, mock_init): @mock.patch.object(flow, 'FLOWS', return_value={'fake_flow': ['foo']})
def test_execute_flow(self, mock_flows, mock_init):
fake_ext = mock.Mock(spec=base_action.BaseAction) fake_ext = mock.Mock(spec=base_action.BaseAction)
fake_ext.name = 'foo' fake_ext.name = 'foo'
self.drv = flow.Flow('fake_data_driver') self.data_drv.flow = 'fake_flow'
self.drv = flow.Flow(self.data_drv)
self.drv.ext_mgr = stevedore.NamedExtensionManager.make_test_instance( self.drv.ext_mgr = stevedore.NamedExtensionManager.make_test_instance(
[fake_ext], namespace='TESTING') [fake_ext], namespace='TESTING')
self.drv.execute_flow() self.drv.execute_flow()
@ -42,12 +49,18 @@ class TestFlowDriver(unittest2.TestCase):
fake_ext.execute.assert_called_once_with() fake_ext.execute.assert_called_once_with()
@mock.patch('stevedore.named.NamedExtensionManager') @mock.patch('stevedore.named.NamedExtensionManager')
def test_init(self, mock_stevedore): def test_init_succesfull_custom_flow(self, mock_stevedore):
fake_data_driver = mock.Mock()
expected_flow = ['action1', 'action3'] expected_flow = ['action1', 'action3']
fake_data_driver.flow = expected_flow self.data_drv.data['custom_flow'] = expected_flow
self.drv = flow.Flow(fake_data_driver) self.drv = flow.Flow(self.data_drv)
mock_stevedore.assert_called_once_with( mock_stevedore.assert_called_once_with(
'bareon.actions', names=expected_flow, 'bareon.actions', names=expected_flow,
invoke_on_load=True, invoke_args=(fake_data_driver,), invoke_on_load=True, invoke_args=(self.data_drv,),
name_order=True) name_order=True)
def test_init_empty_custom_flow_failed(self):
self.assertRaises(errors.EmptyCustomFlow, flow.Flow, self.data_drv)
def test_init_invalid_flow_failed(self):
self.data_drv.flow = 'invalid'
self.assertRaises(errors.NonexistingFlow, flow.Flow, self.data_drv)

View File

@ -22,7 +22,7 @@ from bareon.drivers.data import generic
class TestKsDisks(unittest2.TestCase): class TestKsDisks(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestKsDisks, self).__init__(*args, **kwargs) super(TestKsDisks, self).__init__(*args, **kwargs)
self.driver = generic.GenericDataDriver(None) self.driver = generic.GenericDataDriver({})
self.driver._partition_data = self.mock_part_data = mock.MagicMock() self.driver._partition_data = self.mock_part_data = mock.MagicMock()
def test_no_partition_data(self): def test_no_partition_data(self):
@ -74,7 +74,7 @@ class TestKsDisks(unittest2.TestCase):
class TestKsVgs(unittest2.TestCase): class TestKsVgs(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestKsVgs, self).__init__(*args, **kwargs) super(TestKsVgs, self).__init__(*args, **kwargs)
self.driver = generic.GenericDataDriver(None) self.driver = generic.GenericDataDriver({})
self.driver._partition_data = self.mock_part_data = mock.MagicMock() self.driver._partition_data = self.mock_part_data = mock.MagicMock()
def test_no_partition_data(self): def test_no_partition_data(self):
@ -114,7 +114,7 @@ class TestKsVgs(unittest2.TestCase):
class TestSmallKsDisks(unittest2.TestCase): class TestSmallKsDisks(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestSmallKsDisks, self).__init__(*args, **kwargs) super(TestSmallKsDisks, self).__init__(*args, **kwargs)
self.driver = generic.GenericDataDriver(None) self.driver = generic.GenericDataDriver({})
self.driver._partition_data = self.mock_part_data = mock.MagicMock() self.driver._partition_data = self.mock_part_data = mock.MagicMock()
def test_no_partition_data(self): def test_no_partition_data(self):
@ -154,7 +154,7 @@ class TestSmallKsDisks(unittest2.TestCase):
class TestGetLabel(unittest2.TestCase): class TestGetLabel(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestGetLabel, self).__init__(*args, **kwargs) super(TestGetLabel, self).__init__(*args, **kwargs)
self.driver = generic.GenericDataDriver(None) self.driver = generic.GenericDataDriver({})
def test_no_label(self): def test_no_label(self):
label = None label = None

View File

@ -52,7 +52,7 @@ class TestGetImageSchema(unittest2.TestCase):
class TestMatchDevice(unittest2.TestCase): class TestMatchDevice(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestMatchDevice, self).__init__(*args, **kwargs) super(TestMatchDevice, self).__init__(*args, **kwargs)
self.driver = ironic.Ironic(None) self.driver = ironic.Ironic({})
def test_match_list_value(self): def test_match_list_value(self):
test_type = 'path' test_type = 'path'
@ -98,7 +98,7 @@ class TestMatchDevice(unittest2.TestCase):
class TestDiskDev(unittest2.TestCase): class TestDiskDev(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestDiskDev, self).__init__(*args, **kwargs) super(TestDiskDev, self).__init__(*args, **kwargs)
self.driver = ironic.Ironic(None) self.driver = ironic.Ironic({})
self.driver._match_device = self.mock_match_device = mock.MagicMock() self.driver._match_device = self.mock_match_device = mock.MagicMock()
def test_no_valid_disks(self): def test_no_valid_disks(self):
@ -136,7 +136,7 @@ class TestDiskDev(unittest2.TestCase):
class TestMatchPartition(unittest2.TestCase): class TestMatchPartition(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestMatchPartition, self).__init__(*args, **kwargs) super(TestMatchPartition, self).__init__(*args, **kwargs)
self.driver = ironic.Ironic(None) self.driver = ironic.Ironic({})
def test_match_list_value(self): def test_match_list_value(self):
test_type = 'path' test_type = 'path'
@ -224,7 +224,7 @@ class TestMatchPartition(unittest2.TestCase):
class TestDiskPartition(unittest2.TestCase): class TestDiskPartition(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestDiskPartition, self).__init__(*args, **kwargs) super(TestDiskPartition, self).__init__(*args, **kwargs)
self.driver = ironic.Ironic(None) self.driver = ironic.Ironic({})
self.driver._match_data_by_pattern = \ self.driver._match_data_by_pattern = \
self.mock_match_part = mock.MagicMock() self.mock_match_part = mock.MagicMock()
@ -265,7 +265,7 @@ class TestDiskPartition(unittest2.TestCase):
class TestGetPartitionIds(unittest2.TestCase): class TestGetPartitionIds(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestGetPartitionIds, self).__init__(*args, **kwargs) super(TestGetPartitionIds, self).__init__(*args, **kwargs)
self.driver = ironic.Ironic(None) self.driver = ironic.Ironic({})
def test_no_devices(self, mock_ids, mock_partitions): def test_no_devices(self, mock_ids, mock_partitions):
mock_partitions.return_value = [] mock_partitions.return_value = []
@ -306,7 +306,7 @@ class TestFindHwFstab(unittest2.TestCase):
fss = [fs(mount='/', type='ext4', device='/dev/sda', os_id='1'), fss = [fs(mount='/', type='ext4', device='/dev/sda', os_id='1'),
fs(mount='/usr', type='ext4', device='/dev/sdb', os_id='1')] fs(mount='/usr', type='ext4', device='/dev/sdb', os_id='1')]
data_driver = ironic.Ironic(None) data_driver = ironic.Ironic({})
data_driver._partition_scheme = ironic.objects.PartitionScheme() data_driver._partition_scheme = ironic.objects.PartitionScheme()
data_driver.partition_scheme.fss = fss data_driver.partition_scheme.fss = fss
@ -325,7 +325,7 @@ class TestFindHwFstab(unittest2.TestCase):
fs(mount='/usr', type='ext4', device='/dev/sdb', os_id='1'), fs(mount='/usr', type='ext4', device='/dev/sdb', os_id='1'),
fs(mount='/', type='ext4', device='/dev/sda', os_id='2')] fs(mount='/', type='ext4', device='/dev/sda', os_id='2')]
data_driver = ironic.Ironic(None) data_driver = ironic.Ironic({})
data_driver._partition_scheme = ironic.objects.PartitionScheme() data_driver._partition_scheme = ironic.objects.PartitionScheme()
data_driver.partition_scheme.fss = fss data_driver.partition_scheme.fss = fss
@ -346,7 +346,7 @@ class TestFindHwFstab(unittest2.TestCase):
fss = [fs(mount='/etc', type='ext4', device='/dev/sda', os_id='1'), fss = [fs(mount='/etc', type='ext4', device='/dev/sda', os_id='1'),
fs(mount='/', type='ext4', device='/dev/sda', os_id='1')] fs(mount='/', type='ext4', device='/dev/sda', os_id='1')]
data_driver = ironic.Ironic(None) data_driver = ironic.Ironic({})
data_driver._partition_scheme = ironic.objects.PartitionScheme() data_driver._partition_scheme = ironic.objects.PartitionScheme()
data_driver.partition_scheme.fss = fss data_driver.partition_scheme.fss = fss
exec_mock.side_effect = [('stdout', 'stderr'), exec_mock.side_effect = [('stdout', 'stderr'),
@ -863,7 +863,7 @@ class TestConvertPercentSizes(unittest2.TestCase):
class TestProcessPartition(unittest2.TestCase): class TestProcessPartition(unittest2.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestProcessPartition, self).__init__(*args, **kwargs) super(TestProcessPartition, self).__init__(*args, **kwargs)
self.driver = ironic.Ironic(None) self.driver = ironic.Ironic({})
self.driver._partition_data = self.mock_part_data = mock.MagicMock() self.driver._partition_data = self.mock_part_data = mock.MagicMock()
self.driver._add_partition = self.mock_add_part = mock.MagicMock() self.driver._add_partition = self.mock_add_part = mock.MagicMock()
self.mock_add_part.return_value = self.mock_part = mock.MagicMock() self.mock_add_part.return_value = self.mock_part = mock.MagicMock()