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):
self.data = copy.deepcopy(data)
self.flow = []
if self.data and self.data.get('flow'):
self.flow = self.data.get('flow', [])
if 'flow' in self.data:
self.flow = self.data['flow']
@six.add_metaclass(abc.ABCMeta)

View File

@ -15,21 +15,42 @@
from bareon.drivers.deploy import base
from bareon.drivers.deploy import mixins
from bareon import errors
from bareon.openstack.common import log as logging
import stevedore.named
LOG = logging.getLogger(__name__)
FLOWS = {
'nailgun': [
'do_partitioning',
'do_configdrive',
'do_copyimage',
'do_bootloader'
],
'custom': [],
}
class Flow(base.SimpleDeployDriver, mixins.MountableMixin):
def __init__(self, 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(
'bareon.actions', names=self.driver.flow,
invoke_on_load=True, invoke_args=(self.driver, ),
name_order=True)
'bareon.actions', names=flow, invoke_on_load=True,
invoke_args=(self.driver, ), name_order=True)
def execute_flow(self):
for action in self.ext_mgr:

View File

@ -182,3 +182,11 @@ class IncorrectChroot(BaseError):
class TooManyKernels(BaseError):
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.drivers.deploy import flow
from bareon import errors
import stevedore
@ -28,12 +29,18 @@ elif six.PY3:
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__',
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.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(
[fake_ext], namespace='TESTING')
self.drv.execute_flow()
@ -42,12 +49,18 @@ class TestFlowDriver(unittest2.TestCase):
fake_ext.execute.assert_called_once_with()
@mock.patch('stevedore.named.NamedExtensionManager')
def test_init(self, mock_stevedore):
fake_data_driver = mock.Mock()
def test_init_succesfull_custom_flow(self, mock_stevedore):
expected_flow = ['action1', 'action3']
fake_data_driver.flow = expected_flow
self.drv = flow.Flow(fake_data_driver)
self.data_drv.data['custom_flow'] = expected_flow
self.drv = flow.Flow(self.data_drv)
mock_stevedore.assert_called_once_with(
'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)
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):
def __init__(self, *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()
def test_no_partition_data(self):
@ -74,7 +74,7 @@ class TestKsDisks(unittest2.TestCase):
class TestKsVgs(unittest2.TestCase):
def __init__(self, *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()
def test_no_partition_data(self):
@ -114,7 +114,7 @@ class TestKsVgs(unittest2.TestCase):
class TestSmallKsDisks(unittest2.TestCase):
def __init__(self, *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()
def test_no_partition_data(self):
@ -154,7 +154,7 @@ class TestSmallKsDisks(unittest2.TestCase):
class TestGetLabel(unittest2.TestCase):
def __init__(self, *args, **kwargs):
super(TestGetLabel, self).__init__(*args, **kwargs)
self.driver = generic.GenericDataDriver(None)
self.driver = generic.GenericDataDriver({})
def test_no_label(self):
label = None

View File

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