Merge pull request #43571 from nadvornik/raid_add

Add missing devices to RAID array
This commit is contained in:
Mike Place 2017-09-24 19:54:10 -06:00 committed by GitHub
commit 68fa74e88f
3 changed files with 241 additions and 57 deletions

View file

@ -356,3 +356,40 @@ def assemble(name,
return cmd
elif test_mode is False:
return __salt__['cmd.run'](cmd, python_shell=False)
def examine(device):
'''
Show detail for a specified RAID component device
CLI Example:
.. code-block:: bash
salt '*' raid.examine '/dev/sda1'
'''
res = __salt__['cmd.run_stdout']('mdadm -Y -E {0}'.format(device), output_loglevel='trace', python_shell=False)
ret = {}
for line in res.splitlines():
name, var = line.partition("=")[::2]
ret[name] = var
return ret
def add(name, device):
'''
Add new device to RAID array.
CLI Example:
.. code-block:: bash
salt '*' raid.add /dev/md0 /dev/sda1
'''
cmd = 'mdadm --manage {0} --add {1}'.format(name, device)
if __salt__['cmd.retcode'](cmd) == 0:
return True
return False

View file

@ -25,9 +25,6 @@ import logging
# Import salt libs
import salt.utils.path
# Import 3rd-party libs
from salt.ext import six
# Set up logger
log = logging.getLogger(__name__)
@ -88,69 +85,127 @@ def present(name,
# Device exists
raids = __salt__['raid.list']()
if raids.get(name):
ret['comment'] = 'Raid {0} already present'.format(name)
return ret
present = raids.get(name)
# Decide whether to create or assemble
can_assemble = {}
for dev in devices:
# mdadm -E exits with 0 iff all devices given are part of an array
cmd = 'mdadm -E {0}'.format(dev)
can_assemble[dev] = __salt__['cmd.retcode'](cmd) == 0
missing = []
uuid_dict = {}
new_devices = []
if True in six.itervalues(can_assemble) and False in six.itervalues(can_assemble):
in_raid = sorted([x[0] for x in six.iteritems(can_assemble) if x[1]])
not_in_raid = sorted([x[0] for x in six.iteritems(can_assemble) if not x[1]])
ret['comment'] = 'Devices are a mix of RAID constituents ({0}) and '\
'non-RAID-constituents({1}).'.format(in_raid, not_in_raid)
for dev in devices:
if dev == 'missing' or not __salt__['file.access'](dev, 'f'):
missing.append(dev)
continue
superblock = __salt__['raid.examine'](dev)
if 'MD_UUID' in superblock:
uuid = superblock['MD_UUID']
if uuid not in uuid_dict:
uuid_dict[uuid] = []
uuid_dict[uuid].append(dev)
else:
new_devices.append(dev)
if len(uuid_dict) > 1:
ret['comment'] = 'Devices are a mix of RAID constituents with multiple MD_UUIDs: {0}.'.format(
sorted(uuid_dict.keys()))
ret['result'] = False
return ret
elif next(six.itervalues(can_assemble)):
elif len(uuid_dict) == 1:
uuid = list(uuid_dict.keys())[0]
if present and present['uuid'] != uuid:
ret['comment'] = 'Devices MD_UUIDs: {0} differs from present RAID uuid {1}.'.format(uuid, present['uuid'])
ret['result'] = False
return ret
devices_with_superblock = uuid_dict[uuid]
else:
devices_with_superblock = []
if present:
do_assemble = False
do_create = False
elif len(devices_with_superblock) > 0:
do_assemble = True
do_create = False
verb = 'assembled'
else:
if len(new_devices) == 0:
ret['comment'] = 'All devices are missing: {0}.'.format(missing)
ret['result'] = False
return ret
do_assemble = False
do_create = True
verb = 'created'
# If running with test use the test_mode with create or assemble
if __opts__['test']:
if do_assemble:
res = __salt__['raid.assemble'](name,
devices,
devices_with_superblock,
test_mode=True,
**kwargs)
else:
elif do_create:
res = __salt__['raid.create'](name,
level,
devices,
new_devices + ['missing'] * len(missing),
test_mode=True,
**kwargs)
ret['comment'] = 'Raid will be {0} with: {1}'.format(verb, res)
ret['result'] = None
if present:
ret['comment'] = 'Raid {0} already present.'.format(name)
if do_assemble or do_create:
ret['comment'] = 'Raid will be {0} with: {1}'.format(verb, res)
ret['result'] = None
if (do_assemble or present) and len(new_devices) > 0:
ret['comment'] += ' New devices will be added: {0}'.format(new_devices)
ret['result'] = None
if len(missing) > 0:
ret['comment'] += ' Missing devices: {0}'.format(missing)
return ret
# Attempt to create or assemble the array
if do_assemble:
__salt__['raid.assemble'](name,
devices,
devices_with_superblock,
**kwargs)
else:
elif do_create:
__salt__['raid.create'](name,
level,
devices,
new_devices + ['missing'] * len(missing),
**kwargs)
raids = __salt__['raid.list']()
changes = raids.get(name)
if changes:
ret['comment'] = 'Raid {0} {1}.'.format(name, verb)
ret['changes'] = changes
# Saving config
__salt__['raid.save_config']()
if not present:
raids = __salt__['raid.list']()
changes = raids.get(name)
if changes:
ret['comment'] = 'Raid {0} {1}.'.format(name, verb)
ret['changes'] = changes
# Saving config
__salt__['raid.save_config']()
else:
ret['comment'] = 'Raid {0} failed to be {1}.'.format(name, verb)
ret['result'] = False
else:
ret['comment'] = 'Raid {0} failed to be {1}.'.format(name, verb)
ret['result'] = False
ret['comment'] = 'Raid {0} already present.'.format(name)
if (do_assemble or present) and len(new_devices) > 0 and ret['result']:
for d in new_devices:
res = __salt__['raid.add'](name, d)
if not res:
ret['comment'] += ' Unable to add {0} to {1}.\n'.format(d, name)
ret['result'] = False
else:
ret['comment'] += ' Added new device {0} to {1}.\n'.format(d, name)
if ret['result']:
ret['changes']['added'] = new_devices
if len(missing) > 0:
ret['comment'] += ' Missing devices: {0}'.format(missing)
return ret

View file

@ -32,41 +32,133 @@ class MdadmTestCase(TestCase, LoaderModuleMockMixin):
'''
Test to verify that the raid is present
'''
ret = [{'changes': {}, 'comment': 'Raid salt already present',
ret = [{'changes': {}, 'comment': 'Raid salt already present.',
'name': 'salt', 'result': True},
{'changes': {},
'comment': "Devices are a mix of RAID constituents"
" (['dev0']) and non-RAID-constituents(['dev1']).",
'comment': "Devices are a mix of RAID constituents with multiple MD_UUIDs:"
" ['6be5fc45:05802bba:1c2d6722:666f0e03', 'ffffffff:ffffffff:ffffffff:ffffffff'].",
'name': 'salt', 'result': False},
{'changes': {},
'comment': 'Raid will be created with: True', 'name': 'salt',
'result': None},
{'changes': {}, 'comment': 'Raid salt failed to be created.',
'name': 'salt', 'result': False},
{'changes': {'uuid': '6be5fc45:05802bba:1c2d6722:666f0e03'}, 'comment': 'Raid salt created.',
'name': 'salt', 'result': True},
{'changes': {'added': ['dev1'], 'uuid': '6be5fc45:05802bba:1c2d6722:666f0e03'},
'comment': 'Raid salt assembled. Added new device dev1 to salt.\n',
'name': 'salt', 'result': True},
{'changes': {'added': ['dev1']},
'comment': 'Raid salt already present. Added new device dev1 to salt.\n',
'name': 'salt', 'result': True},
{'changes': {}, 'comment': 'Raid salt failed to be assembled.',
'name': 'salt', 'result': False}]
mock = MagicMock(side_effect=[{'salt': True}, {'salt': False},
{'salt': False}, {'salt': False},
{'salt': False}])
with patch.dict(mdadm.__salt__, {'raid.list': mock}):
self.assertEqual(mdadm.present("salt", 5, "dev0"), ret[0])
mock_raid_list_exists = MagicMock(return_value={'salt': {'uuid': '6be5fc45:05802bba:1c2d6722:666f0e03'}})
mock_raid_list_missing = MagicMock(return_value={})
mock = MagicMock(side_effect=[0, 1])
with patch.dict(mdadm.__salt__, {'cmd.retcode': mock}):
self.assertDictEqual(mdadm.present("salt", 5,
["dev0", "dev1"]),
ret[1])
mock_file_access_ok = MagicMock(return_value=True)
mock = MagicMock(return_value=True)
with patch.dict(mdadm.__salt__, {'cmd.retcode': mock}):
with patch.dict(mdadm.__opts__, {'test': True}):
with patch.dict(mdadm.__salt__, {'raid.create': mock}):
self.assertDictEqual(mdadm.present("salt", 5, "dev0"),
ret[2])
mock_raid_examine_ok = MagicMock(return_value={'MD_UUID': '6be5fc45:05802bba:1c2d6722:666f0e03'})
mock_raid_examine_missing = MagicMock(return_value={})
with patch.dict(mdadm.__opts__, {'test': False}):
with patch.dict(mdadm.__salt__, {'raid.create': mock}):
self.assertDictEqual(mdadm.present("salt", 5, "dev0"),
ret[3])
mock_raid_create_success = MagicMock(return_value=True)
mock_raid_create_fail = MagicMock(return_value=False)
mock_raid_assemble_success = MagicMock(return_value=True)
mock_raid_assemble_fail = MagicMock(return_value=False)
mock_raid_add_success = MagicMock(return_value=True)
mock_raid_save_config = MagicMock(return_value=True)
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_exists,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_ok
}):
with patch.dict(mdadm.__opts__, {'test': False}):
self.assertEqual(mdadm.present("salt", 5, "dev0"), ret[0])
mock_raid_examine_mixed = MagicMock(side_effect=[
{'MD_UUID': '6be5fc45:05802bba:1c2d6722:666f0e03'}, {'MD_UUID': 'ffffffff:ffffffff:ffffffff:ffffffff'},
])
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_missing,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_mixed
}):
with patch.dict(mdadm.__opts__, {'test': False}):
self.assertEqual(mdadm.present("salt", 5, ["dev0", "dev1"]), ret[1])
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_missing,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_missing,
'raid.create': mock_raid_create_success
}):
with patch.dict(mdadm.__opts__, {'test': True}):
self.assertDictEqual(mdadm.present("salt", 5, "dev0"), ret[2])
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_missing,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_missing,
'raid.create': mock_raid_create_fail
}):
with patch.dict(mdadm.__opts__, {'test': False}):
self.assertDictEqual(mdadm.present("salt", 5, "dev0"), ret[3])
mock_raid_list_create = MagicMock(side_effect=[{}, {'salt': {'uuid': '6be5fc45:05802bba:1c2d6722:666f0e03'}}])
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_create,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_missing,
'raid.create': mock_raid_create_success,
'raid.save_config': mock_raid_save_config
}):
with patch.dict(mdadm.__opts__, {'test': False}):
self.assertDictEqual(mdadm.present("salt", 5, "dev0"), ret[4])
mock_raid_examine_replaced = MagicMock(side_effect=[
{'MD_UUID': '6be5fc45:05802bba:1c2d6722:666f0e03'}, {},
])
mock_raid_list_create = MagicMock(side_effect=[{}, {'salt': {'uuid': '6be5fc45:05802bba:1c2d6722:666f0e03'}}])
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_create,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_replaced,
'raid.assemble': mock_raid_assemble_success,
'raid.add': mock_raid_add_success,
'raid.save_config': mock_raid_save_config
}):
with patch.dict(mdadm.__opts__, {'test': False}):
self.assertDictEqual(mdadm.present("salt", 5, ["dev0", "dev1"]), ret[5])
mock_raid_examine_replaced = MagicMock(side_effect=[
{'MD_UUID': '6be5fc45:05802bba:1c2d6722:666f0e03'}, {},
])
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_exists,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_replaced,
'raid.add': mock_raid_add_success,
'raid.save_config': mock_raid_save_config
}):
with patch.dict(mdadm.__opts__, {'test': False}):
self.assertDictEqual(mdadm.present("salt", 5, ["dev0", "dev1"]), ret[6])
mock_raid_examine_replaced = MagicMock(side_effect=[
{'MD_UUID': '6be5fc45:05802bba:1c2d6722:666f0e03'}, {},
])
with patch.dict(mdadm.__salt__, {
'raid.list': mock_raid_list_missing,
'file.access': mock_file_access_ok,
'raid.examine': mock_raid_examine_replaced,
'raid.assemble': mock_raid_assemble_fail,
}):
with patch.dict(mdadm.__opts__, {'test': False}):
self.assertDictEqual(mdadm.present("salt", 5, ["dev0", "dev1"]), ret[7])
def test_absent(self):
'''