mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #43571 from nadvornik/raid_add
Add missing devices to RAID array
This commit is contained in:
commit
68fa74e88f
3 changed files with 241 additions and 57 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
'''
|
||||
|
|
Loading…
Add table
Reference in a new issue