Merge pull request #48803 from dmurphy18/aix_filesystems

Support for execution modules and states mount on AIX
This commit is contained in:
Nicole Thomas 2018-08-09 13:51:54 -04:00 committed by GitHub
commit 7d6b9ed0a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 805 additions and 55 deletions

View file

@ -17,12 +17,14 @@ import salt.utils.path
import salt.utils.platform
import salt.utils.mount
import salt.utils.stringutils
from salt.utils.odict import OrderedDict
from salt.exceptions import CommandNotFoundError, CommandExecutionError
# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import filter, zip # pylint: disable=import-error,redefined-builtin
# Set up logger
log = logging.getLogger(__name__)
@ -432,6 +434,141 @@ class _vfstab_entry(object):
return True
class _FileSystemsEntry(object):
'''
Utility class for manipulating filesystem entries. Primarily we're parsing,
formatting, and comparing lines. Parsing emits dicts expected from
fstab() or raises a ValueError.
Note: We'll probably want to use os.normpath and os.normcase on 'name'
'''
class ParseError(ValueError):
'''
Error raised when a line isn't parsible as an fstab entry
'''
filesystems_keys = ('device', 'name', 'fstype', 'vfstype', 'opts', 'mount')
# preserve data format of filesystems
compatibility_keys = ('dev', 'dev', 'name', 'fstype', 'vfstype', 'opts', 'mount', 'type', 'vfs', 'account', 'boot', 'check', 'free', 'nodename', 'quota', 'size', 'vol', 'log')
@classmethod
def dict_from_lines(cls, lines, keys=filesystems_keys):
if len(lines) < 2:
raise ValueError('Invalid number of lines: {0}'.format(lines))
if not keys:
# if empty force default filesystems_keys
keys = _FileSystemsEntry.filesystems_keys
elif len(keys) < 6:
raise ValueError('Invalid key name array: {0}'.format(keys))
blk_lines = lines
orddict = OrderedDict()
orddict['name'] = blk_lines[0].split(':')[0].strip()
blk_lines.pop(0)
for line in blk_lines:
if line.startswith('#'):
raise cls.ParseError("Comment!")
comps = line.split('= ')
if len(comps) != 2:
raise cls.ParseError("Invalid Entry!")
key_name = comps[0].strip()
if key_name in keys:
orddict[key_name] = comps[1].strip()
else:
raise ValueError('Invalid name for use in filesystems: {0}'.format(key_name))
return orddict
@classmethod
def dict_from_cmd_line(cls, ipargs, keys):
cmdln_dict = ipargs
if keys:
for key, value in keys:
# ignore unknown or local scope keys
if key.startswith('__'):
continue
if key in _FileSystemsEntry.compatibility_keys:
cmdln_dict[key] = value
return cmdln_dict
@classmethod
def from_line(cls, *args, **kwargs):
return cls(** cls.dict_from_cmd_line(*args, **kwargs))
@classmethod
def dict_to_lines(cls, fsys_dict_entry):
entry = fsys_dict_entry
strg_out = entry['name'] + ':' + os.linesep
for k, v in six.viewitems(entry):
if 'name' not in k:
strg_out += '\t{0}\t\t= {1}'.format(k, v) + os.linesep
strg_out += os.linesep
return six.text_type(strg_out)
def dict_from_entry(self):
ret = OrderedDict()
ret[self.criteria['name']] = self.criteria
return ret
def __str__(self):
'''
String value, only works for full repr
'''
return self.dict_to_lines(self.criteria)
def __repr__(self):
'''
Always works
'''
return repr(self.criteria)
def pick(self, keys):
'''
Returns an instance with just those keys
'''
subset = dict([(key, self.criteria[key]) for key in keys])
return self.__class__(**subset)
def __init__(self, **criteria):
'''
Store non-empty, non-null values to use as filter
'''
items = [key_value for key_value in six.iteritems(criteria) if key_value[1] is not None]
items = [(key_value1[0], six.text_type(key_value1[1])) for key_value1 in items]
self.criteria = OrderedDict(items)
@staticmethod
def norm_path(path):
'''
Resolve equivalent paths equivalently
'''
return os.path.normcase(os.path.normpath(path))
def match(self, fsys_view):
'''
Compare potentially partial criteria against built filesystems entry dictionary
'''
evalue_dict = fsys_view[1]
for key, value in six.viewitems(self.criteria):
if key in evalue_dict:
if evalue_dict[key] != value:
return False
else:
return False
return True
def __getitem__(self, key):
'''
Return value for input key
'''
return self.criteria[key]
def fstab(config='/etc/fstab'):
'''
.. versionchanged:: 2016.3.2
@ -1070,8 +1207,12 @@ def mount(name, device, mkmnt=False, fstype='', opts='defaults', user=None, util
lopts = ','.join(opts)
args = '-o {0}'.format(lopts)
# use of fstype on AIX is with /etc/filesystems
if fstype and 'AIX' not in __grains__['os']:
# use of fstype on AIX differs from typical Linux use of -t functionality
# AIX uses -v vfsname, -t fstype mounts all with fstype in /etc/filesystems
if 'AIX' in __grains__['os']:
if fstype:
args += ' -v {0}'.format(fstype)
else:
args += ' -t {0}'.format(fstype)
cmd = 'mount {0} {1} {2} '.format(args, device, name)
out = __salt__['cmd.run_all'](cmd, runas=user, python_shell=False)
@ -1115,9 +1256,15 @@ def remount(name, device, mkmnt=False, fstype='', opts='defaults', user=None):
lopts = ','.join(opts)
args = '-o {0}'.format(lopts)
# use of fstype on AIX is with /etc/filesystems
if fstype and 'AIX' not in __grains__['os']:
# use of fstype on AIX differs from typical Linux use of -t functionality
# AIX uses -v vfsname, -t fstype mounts all with fstype in /etc/filesystems
if 'AIX' in __grains__['os']:
if fstype:
args += ' -v {0}'.format(fstype)
args += ' -o remount'
else:
args += ' -t {0}'.format(fstype)
if __grains__['os'] not in ['OpenBSD', 'MacOS', 'Darwin'] or force_mount:
cmd = 'mount {0} {1} {2} '.format(args, device, name)
else:
@ -1422,3 +1569,278 @@ def delete_mount_cache(real_name):
if not cache_write:
raise CommandExecutionError('Unable to write mount cache.')
return True
def _filesystems(config='/etc/filesystems', leading_key=True):
'''
Return the contents of the filesystems in an OrderedDict
config
File containing filesystem infomation
leading_key
True return dictionary keyed by 'name' and value as dictionary with other keys, values (name excluded)
OrderedDict({ '/dir' : OrderedDict({'dev': '/dev/hd8', .... }}))
False return dictionary keyed by 'name' and value as dictionary with all keys, values (name included)
OrderedDict({ '/dir' : OrderedDict({'name': '/dir', 'dev': '/dev/hd8', ... })})
'''
ret = OrderedDict()
lines = []
parsing_block = False
if not os.path.isfile(config) or 'AIX' not in __grains__['kernel']:
return ret
# read in block of filesystems, block starts with '/' till empty line
with salt.utils.files.fopen(config) as ifile:
for line in ifile:
line = salt.utils.stringutils.to_unicode(line)
# skip till first entry
if not line.startswith('/') and not parsing_block:
continue
if line.startswith('/'):
parsing_block = True
lines.append(line)
elif not line.split():
parsing_block = False
try:
entry = _FileSystemsEntry.dict_from_lines(
lines,
_FileSystemsEntry.compatibility_keys)
lines = []
if 'opts' in entry:
entry['opts'] = entry['opts'].split(',')
while entry['name'] in ret:
entry['name'] += '_'
if leading_key:
ret[entry.pop('name')] = entry
else:
ret[entry['name']] = entry
except _FileSystemsEntry.ParseError:
pass
else:
lines.append(line)
return ret
def filesystems(config='/etc/filesystems'):
'''
.. versionadded:: 2018.3.3
List the contents of the filesystems
CLI Example:
.. code-block:: bash
salt '*' mount.filesystems
'''
ret = {}
if 'AIX' not in __grains__['kernel']:
return ret
ret_dict = _filesystems(config)
if ret_dict:
ret_key = next(iter(ret_dict.keys()))
ret = {ret_key: dict(ret_dict[ret_key])}
return ret
def set_filesystems(
name,
device,
vfstype,
opts='-',
mount='true',
config='/etc/filesystems',
test=False,
match_on='auto',
**kwargs):
'''
.. versionadded:: 2018.3.3
Verify that this mount is represented in the filesystems, change the mount
to match the data passed, or add the mount if it is not present on AIX
Provide information if the path is mounted
:param name: The name of the mount point where the device is mounted.
:param device: The device that is being mounted.
:param vfstype: The file system that is used (AIX has two fstypes, fstype and vfstype - similar to Linux fstype)
:param opts: Additional options used when mounting the device.
:param mount: Mount if not mounted, default True.
:param config: Configuration file, default /etc/filesystems.
:param match: File systems type to match on, default auto
CLI Example:
.. code-block:: bash
salt '*' mount.set_filesystems /mnt/foo /dev/sdz1 jfs2
'''
# Fix the opts type if it is a list
if isinstance(opts, list):
opts = ','.join(opts)
# preserve arguments for updating
entry_args = {
'name': name,
'dev': device.replace('\\ ', '\\040'),
'vfstype': vfstype,
'opts': opts,
'mount': mount,
}
view_lines = []
ret = None
if 'AIX' not in __grains__['kernel']:
return ret
# Transform match_on into list--items will be checked later
if isinstance(match_on, list):
pass
elif not isinstance(match_on, six.string_types):
raise CommandExecutionError('match_on must be a string or list of strings')
elif match_on == 'auto':
# Try to guess right criteria for auto....
# added IBM types from sys/vmount.h after btrfs
# NOTE: missing some special fstypes here
specialFSes = frozenset([
'none',
'tmpfs',
'sysfs',
'proc',
'fusectl',
'debugfs',
'securityfs',
'devtmpfs',
'cgroup',
'btrfs',
'cdrfs',
'procfs',
'jfs',
'jfs2',
'nfs',
'sfs',
'nfs3',
'cachefs',
'udfs',
'cifs',
'namefs',
'pmemfs',
'ahafs',
'nfs4',
'autofs',
'stnfs'])
if vfstype in specialFSes:
match_on = ['name']
else:
match_on = ['dev']
else:
match_on = [match_on]
# generate entry and criteria objects, handle invalid keys in match_on
entry_ip = _FileSystemsEntry.from_line(entry_args, kwargs)
try:
criteria = entry_ip.pick(match_on)
except KeyError:
filterFn = lambda key: key not in _FileSystemsEntry.compatibility_keys
invalid_keys = filter(filterFn, match_on)
raise CommandExecutionError('Unrecognized keys in match_on: "{0}"'.format(invalid_keys))
# parse file, use ret to cache status
if not os.path.isfile(config):
raise CommandExecutionError('Bad config file "{0}"'.format(config))
# read in block of filesystem, block starts with '/' till empty line
try:
fsys_filedict = _filesystems(config, False)
for fsys_view in six.viewitems(fsys_filedict):
if criteria.match(fsys_view):
ret = 'present'
if entry_ip.match(fsys_view):
view_lines.append(fsys_view)
else:
ret = 'change'
kv = entry_ip['name']
view_lines.append((kv, entry_ip))
else:
view_lines.append(fsys_view)
except (IOError, OSError) as exc:
raise CommandExecutionError('Couldn\'t read from {0}: {1}'.format(config, exc))
# add line if not present or changed
if ret is None:
for dict_view in six.viewitems(entry_ip.dict_from_entry()):
view_lines.append(dict_view)
ret = 'new'
if ret != 'present': # ret in ['new', 'change']:
try:
with salt.utils.files.fopen(config, 'wb') as ofile:
# The line was changed, commit it!
for fsys_view in view_lines:
entry = fsys_view[1]
mystrg = _FileSystemsEntry.dict_to_lines(entry)
ofile.writelines(salt.utils.data.encode(mystrg))
except (IOError, OSError):
raise CommandExecutionError('File not writable {0}'.format(config))
return ret
def rm_filesystems(name, device, config='/etc/filesystems'):
'''
.. versionadded:: 2018.3.3
Remove the mount point from the filesystems
CLI Example:
.. code-block:: bash
salt '*' mount.rm_filesystems /mnt/foo /dev/sdg
'''
modified = False
view_lines = []
if 'AIX' not in __grains__['kernel']:
return modified
criteria = _FileSystemsEntry(name=name, dev=device)
try:
fsys_filedict = _filesystems(config, False)
for fsys_view in six.viewitems(fsys_filedict):
try:
if criteria.match(fsys_view):
modified = True
else:
view_lines.append(fsys_view)
except _FileSystemsEntry.ParseError:
view_lines.append(fsys_view)
except (IOError, OSError) as exc:
raise CommandExecutionError("Couldn't read from {0}: {1}".format(config, exc))
if modified:
try:
with salt.utils.files.fopen(config, 'wb') as ofile:
for fsys_view in view_lines:
entry = fsys_view[1]
mystrg = _FileSystemsEntry.dict_to_lines(entry)
ofile.writelines(salt.utils.data.encode(mystrg))
except (IOError, OSError) as exc:
raise CommandExecutionError("Couldn't write to {0}: {1}".format(config, exc))
return modified

View file

@ -206,6 +206,11 @@ def mounted(name,
if __grains__['os'] in ['MacOS', 'Darwin'] and opts == 'defaults':
opts = 'noowners'
# Defaults is not a valid option on AIX
if __grains__['os'] in ['AIX']:
if opts == 'defaults':
opts = ''
# Make sure that opts is correct, it can be a list or a comma delimited
# string
if isinstance(opts, string_types):
@ -571,9 +576,14 @@ def mounted(name,
ret['comment'] = '{0} not mounted'.format(name)
if persist:
# Override default for Mac OS
if __grains__['os'] in ['MacOS', 'Darwin'] and config == '/etc/fstab':
config = "/etc/auto_salt"
if '/etc/fstab' == config:
# Override default for Mac OS
if __grains__['os'] in ['MacOS', 'Darwin']:
config = "/etc/auto_salt"
# Override default for AIX
elif 'AIX' in __grains__['os']:
config = "/etc/filesystems"
if __opts__['test']:
if __grains__['os'] in ['MacOS', 'Darwin']:
@ -583,6 +593,15 @@ def mounted(name,
opts,
config,
test=True)
elif __grains__['os'] in ['AIX']:
out = __salt__['mount.set_filesystems'](name,
device,
fstype,
opts,
mount,
config,
test=True,
match_on=match_on)
else:
out = __salt__['mount.set_fstab'](name,
device,
@ -631,6 +650,14 @@ def mounted(name,
fstype,
opts,
config)
elif __grains__['os'] in ['AIX']:
out = __salt__['mount.set_filesystems'](name,
device,
fstype,
opts,
mount,
config,
match_on=match_on)
else:
out = __salt__['mount.set_fstab'](name,
device,
@ -712,7 +739,15 @@ def swap(name, persist=True, config='/etc/fstab'):
ret['result'] = False
if persist:
fstab_data = __salt__['mount.fstab'](config)
device_key_name = 'device'
if 'AIX' in __grains__['os']:
device_key_name = 'dev'
if '/etc/fstab' == config:
# Override default for AIX
config = "/etc/filesystems"
fstab_data = __salt__['mount.filesystems'](config)
else:
fstab_data = __salt__['mount.fstab'](config)
if __opts__['test']:
if name not in fstab_data:
ret['result'] = None
@ -722,19 +757,25 @@ def swap(name, persist=True, config='/etc/fstab'):
return ret
if 'none' in fstab_data:
if fstab_data['none']['device'] == name and \
if fstab_data['none'][device_key_name] == name and \
fstab_data['none']['fstype'] != 'swap':
return ret
# present, new, change, bad config
# Make sure the entry is in the fstab
out = __salt__['mount.set_fstab']('none',
name,
'swap',
['defaults'],
0,
0,
config)
if 'AIX' in __grains__['os']:
out = None
ret['result'] = False
ret['comment'] += '. swap not present in /etc/filesystems on AIX.'
return ret
else:
# present, new, change, bad config
# Make sure the entry is in the fstab
out = __salt__['mount.set_fstab']('none',
name,
'swap',
['defaults'],
0,
0,
config)
if out == 'present':
return ret
if out == 'new':
@ -823,10 +864,16 @@ def unmounted(name,
cache_result = __salt__['mount.delete_mount_cache'](name)
if persist:
device_key_name = 'device'
# Override default for Mac OS
if __grains__['os'] in ['MacOS', 'Darwin'] and config == '/etc/fstab':
config = "/etc/auto_salt"
fstab_data = __salt__['mount.automaster'](config)
elif 'AIX' in __grains__['os']:
device_key_name = 'dev'
if config == '/etc/fstab':
config = "/etc/filesystems"
fstab_data = __salt__['mount.filesystems'](config)
else:
fstab_data = __salt__['mount.fstab'](config)
@ -834,7 +881,7 @@ def unmounted(name,
ret['comment'] += '. fstab entry not found'
else:
if device:
if fstab_data[name]['device'] != device:
if fstab_data[name][device_key_name] != device:
ret['comment'] += '. fstab entry for device {0} not found'.format(device)
return ret
if __opts__['test']:
@ -846,6 +893,8 @@ def unmounted(name,
else:
if __grains__['os'] in ['MacOS', 'Darwin']:
out = __salt__['mount.rm_automaster'](name, device, config)
elif 'AIX' in __grains__['os']:
out = __salt__['mount.rm_filesystems'](name, device, config)
else:
out = __salt__['mount.rm_fstab'](name, device, config)
if out is not True:

View file

@ -407,7 +407,10 @@ def flopen(*args, **kwargs):
with fopen(*args, **kwargs) as f_handle:
try:
if is_fcntl_available(check_sunos=True):
fcntl.flock(f_handle.fileno(), fcntl.LOCK_SH)
lock_type = fcntl.LOCK_SH
if 'w' in args[1] or 'a' in args[1]:
lock_type = fcntl.LOCK_EX
fcntl.flock(f_handle.fileno(), lock_type)
yield f_handle
finally:
if is_fcntl_available(check_sunos=True):

View file

@ -79,6 +79,11 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.object(mount, '_active_mounts_darwin', mock):
self.assertEqual(mount.active(extended=True), {})
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}):
mock = MagicMock(return_value={})
with patch.object(mount, '_active_mounts_aix', mock):
self.assertEqual(mount.active(), {})
def test_fstab(self):
'''
List the content of the fstab
@ -127,6 +132,48 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'pass_fsck': '-'}
}, vfstab
def test_filesystems(self):
'''
List the content of the filesystems
'''
file_data = textwrap.dedent('''\
#
''')
mock = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
self.assertEqual(mount.filesystems(), {})
file_data = textwrap.dedent('''\
#
/home:
dev = /dev/hd1
vfs = jfs2
log = /dev/hd8
mount = true
check = true
vol = /home
free = false
quota = no
''')
mock = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
fsyst = mount.filesystems()
test_fsyst = {'/home': {'dev': '/dev/hd1',
'vfs': 'jfs2',
'log': '/dev/hd8',
'mount': 'true',
'check': 'true',
'vol': '/home',
'free': 'false',
'quota': 'no'}}
self.assertEqual(test_fsyst, fsyst)
def test_rm_fstab(self):
'''
Remove the mount point from the fstab
@ -190,6 +237,53 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'''
self.assertDictEqual(mount.automaster(), {})
def test_rm_filesystems(self):
'''
Remove the mount point from the filesystems
'''
file_data = textwrap.dedent('''\
#
''')
mock = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
self.assertFalse(mount.rm_filesystems('name', 'device'))
file_data = textwrap.dedent('''\
#
/name:
dev = device
vol = /name
''')
mock = MagicMock(return_value=True)
mock_fsyst = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
self.assertTrue(mount.rm_filesystems('/name', 'device'))
def test_set_filesystems(self):
'''
Tests to verify that this mount is represented in the filesystems,
change the mount to match the data passed, or add the mount
if it is not present.
'''
mock = MagicMock(return_value=False)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}):
with patch.object(os.path, 'isfile', mock):
self.assertRaises(CommandExecutionError,
mount.set_filesystems, 'A', 'B', 'C')
mock_read = MagicMock(side_effect=OSError)
with patch.object(os.path, 'isfile', mock):
with patch.object(salt.utils.files, 'fopen', mock_read):
self.assertRaises(CommandExecutionError,
mount.set_filesystems, 'A', 'B', 'C')
def test_mount(self):
'''
Mount a device
@ -209,6 +303,21 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
with patch.dict(mount.__grains__, {'os': 'AIX'}):
mock = MagicMock(return_value=True)
with patch.object(os.path, 'exists', mock):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'file.mkdir': None}):
mock = MagicMock(return_value={'retcode': True,
'stderr': True})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
mock = MagicMock(return_value={'retcode': False,
'stderr': False})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
def test_remount(self):
'''
Attempt to remount a device, if the device is not already mounted, mount
@ -221,6 +330,13 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.object(mount, 'mount', mock):
self.assertTrue(mount.remount('name', 'device'))
with patch.dict(mount.__grains__, {'os': 'AIX'}):
mock = MagicMock(return_value=[])
with patch.object(mount, 'active', mock):
mock = MagicMock(return_value=True)
with patch.object(mount, 'mount', mock):
self.assertTrue(mount.remount('name', 'device'))
def test_umount(self):
'''
Attempt to unmount a device by specifying the directory it is
@ -274,7 +390,6 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'''
Return a dict containing information on active swap
'''
file_data = textwrap.dedent('''\
Filename Type Size Used Priority
/dev/sda1 partition 31249404 4100 -1
@ -306,6 +421,22 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'used': '4100'}
}, swaps
file_data = textwrap.dedent('''\
device maj,min total free
/dev/hd6 10, 2 11776MB 11765MB
''')
mock = MagicMock(return_value=file_data)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.dict(mount.__salt__, {'cmd.run_stdout': mock}):
swaps = mount.swaps()
assert swaps == {
'/dev/hd6': {
'priority': '-',
'size': 12058624,
'type': 'device',
'used': 11264}
}, swaps
def test_swapon(self):
'''
Activate a swap disk
@ -330,6 +461,27 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertEqual(mount.swapon('name'), {'stats': 'name',
'new': True})
## effects of AIX
mock = MagicMock(return_value={'name': 'name'})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
self.assertEqual(mount.swapon('name'),
{'stats': 'name', 'new': False})
mock = MagicMock(return_value={})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertEqual(mount.swapon('name', False), {})
mock = MagicMock(side_effect=[{}, {'name': 'name'}])
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertEqual(mount.swapon('name'), {'stats': 'name',
'new': True})
def test_swapoff(self):
'''
@ -356,14 +508,38 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertTrue(mount.swapoff('name'))
# check on AIX
mock = MagicMock(return_value={})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
self.assertEqual(mount.swapoff('name'), None)
mock = MagicMock(return_value={'name': 'name'})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
with patch.dict(mount.__grains__, {'os': 'test'}):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertFalse(mount.swapoff('name'))
mock = MagicMock(side_effect=[{'name': 'name'}, {}])
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
with patch.dict(mount.__grains__, {'os': 'test'}):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertTrue(mount.swapoff('name'))
def test_is_mounted(self):
'''
Provide information if the path is mounted
'''
mock = MagicMock(return_value={})
with patch.object(mount, 'active', mock):
with patch.object(mount, 'active', mock), \
patch.dict(mount.__grains__, {'kernel': ''}):
self.assertFalse(mount.is_mounted('name'))
mock = MagicMock(return_value={'name': 'name'})
with patch.object(mount, 'active', mock):
with patch.object(mount, 'active', mock), \
patch.dict(mount.__grains__, {'kernel': ''}):
self.assertTrue(mount.is_mounted('name'))

View file

@ -44,6 +44,13 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
superopts2 = ['uid=510', 'gid=100', 'username=cifsuser',
'domain=cifsdomain']
name3 = os.path.realpath('/mnt/jfs2')
device3 = '/dev/hd1'
fstype3 = 'jfs2'
opts3 = ['']
superopts3 = ['uid=510', 'gid=100', 'username=jfs2user',
'domain=jfs2sdomain']
ret = {'name': name,
'result': False,
'comment': '',
@ -57,7 +64,11 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
mock_mnt = MagicMock(return_value={name: {'device': device, 'opts': [],
'superopts': []},
name2: {'device': device2, 'opts': opts2,
'superopts': superopts2}})
'superopts': superopts2},
name3: {'device': device3, 'opts': opts3,
'superopts': superopts3}})
mock_aixfs_retn = MagicMock(return_value='present')
mock_emt = MagicMock(return_value={})
mock_str = MagicMock(return_value='salt')
mock_user = MagicMock(return_value={'uid': 510})
@ -91,7 +102,8 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
ret)
umount1 = ("Forced unmount because devices don't match. "
"Wanted: {0}, current: {1}, {1}".format(os.path.realpath('/dev/sdb6'), device))
"Wanted: {0}, current: {1}, {1}"
.format(os.path.realpath('/dev/sdb6'), device))
comt = ('Unable to unmount')
ret.update({'comment': comt, 'result': None,
'changes': {'umount': umount1}})
@ -124,7 +136,8 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__opts__, {'test': True}), \
patch('os.path.exists', MagicMock(return_value=False)):
comt = ('{0} does not exist and would neither be created nor mounted. '
'{0} needs to be written to the fstab in order to be made persistent.'.format(name))
'{0} needs to be written to the fstab in order to be made persistent.'
.format(name))
ret.update({'comment': comt, 'result': None})
self.assertDictEqual(mount.mounted(name, device, fstype,
mount=False), ret)
@ -185,6 +198,26 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'gid=group1']),
ret)
with patch.dict(mount.__grains__, {'os': 'AIX'}):
with patch.dict(mount.__salt__, {'mount.active': mock_mnt,
'mount.mount': mock_str,
'mount.umount': mock_f,
'mount.read_mount_cache': mock_read_cache,
'mount.write_mount_cache': mock_write_cache,
'mount.set_filesystems': mock_aixfs_retn,
'user.info': mock_user,
'group.info': mock_group}):
with patch.dict(mount.__opts__, {'test': True}):
with patch.object(os.path, 'exists', mock_t):
comt = 'Target was already mounted. Entry already exists in the fstab.'
ret.update({'name': name3, 'result': True})
ret.update({'comment': comt, 'changes': {}})
self.assertDictEqual(mount.mounted(name3, device3,
fstype3,
opts=['uid=user1',
'gid=group1']),
ret)
# 'swap' function tests: 1
def test_swap(self):
@ -203,44 +236,69 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
mock_swp = MagicMock(return_value=[name])
mock_fs = MagicMock(return_value={'none': {'device': name,
'fstype': 'xfs'}})
mock_aixfs = MagicMock(return_value={name: {'dev': name,
'fstype': 'jfs2'}})
mock_emt = MagicMock(return_value={})
with patch.dict(mount.__salt__, {'mount.swaps': mock_swp,
'mount.fstab': mock_fs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Swap {0} is set to be added to the '
'fstab and to be activated'.format(name))
ret.update({'comment': comt})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__grains__, {'os': 'test'}):
with patch.dict(mount.__salt__, {'mount.swaps': mock_swp,
'mount.fstab': mock_fs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Swap {0} is set to be added to the '
'fstab and to be activated'.format(name))
ret.update({'comment': comt})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__opts__, {'test': False}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__salt__, {'mount.fstab': mock_emt,
'mount.set_fstab': mock}):
with patch.dict(mount.__opts__, {'test': False}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Added new entry to the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'new'}})
with patch.dict(mount.__salt__, {'mount.fstab': mock_emt,
'mount.set_fstab': mock}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Added new entry to the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'new'}})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Updated the entry in the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'update'}})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'However, the fstab was not found.')
ret.update({'comment': comt, 'result': False,
'changes': {}})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__grains__, {'os': 'AIX'}):
with patch.dict(mount.__salt__, {'mount.swaps': mock_swp,
'mount.filesystems': mock_aixfs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Updated the entry in the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'update'}})
with patch.dict(mount.__opts__, {'test': False}):
comt = ('Swap {0} already active. swap not present'
' in /etc/filesystems on AIX.'.format(name))
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'However, the fstab was not found.')
ret.update({'comment': comt, 'result': False,
'changes': {}})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__salt__, {'mount.filesystems': mock_emt,
'mount.set_filesystems': mock}):
comt = ('Swap {0} already active. swap not present'
' in /etc/filesystems on AIX.'.format(name))
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(mount.swap(name), ret)
# 'unmounted' function tests: 1
@ -257,11 +315,22 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'changes': {}}
mock_f = MagicMock(return_value=False)
mock_t = MagicMock(return_value=True)
mock_dev = MagicMock(return_value={name: {'device': device}})
mock_fs = MagicMock(return_value={name: {'device': name}})
mock_mnt = MagicMock(side_effect=[{name: {}}, {}, {}, {}])
name3 = os.path.realpath('/mnt/jfs2')
device3 = '/dev/hd1'
fstype3 = 'jfs2'
opts3 = ['']
mock_mnta = MagicMock(return_value={name3: {'device': device3, 'opts': opts3}})
mock_aixfs = MagicMock(return_value={name: {'dev': name3, 'fstype': fstype3}})
mock_delete_cache = MagicMock(return_value=True)
comt3 = ('Mount point /mnt/sdb is unmounted but needs to be purged '
'from /etc/auto_salt to be made persistent')
with patch.dict(mount.__grains__, {'os': 'Darwin'}):
with patch.dict(mount.__salt__, {'mount.active': mock_mnt,
'mount.automaster': mock_fs,
@ -273,7 +342,7 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
self.assertDictEqual(mount.unmounted(name, device), ret)
comt = ('Target was already unmounted. '
'fstab entry for device /dev/sdb5 not found')
'fstab entry for device {0} not found'.format(device))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device,
persist=True), ret)
@ -288,6 +357,37 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device), ret)
with patch.dict(mount.__grains__, {'os': 'AIX'}):
with patch.dict(mount.__salt__, {'mount.active': mock_mnta,
'mount.filesystems': mock_aixfs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Target was already unmounted')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device), ret)
comt = ('Target was already unmounted. '
'fstab entry for device /dev/sdb5 not found')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device,
persist=True), ret)
with patch.dict(mount.__salt__,
{'mount.filesystems': mock_dev}):
comt = ('Mount point {0} is mounted but should not '
'be'.format(name3))
ret.update({'comment': comt, 'result': None, 'name': name3})
self.assertDictEqual(mount.unmounted(name3, device3,
persist=True), ret)
with patch.dict(mount.__opts__, {'test': False}), \
patch.dict(mount.__salt__, {'mount.umount': mock_t,
'mount.delete_mount_cache': mock_delete_cache}):
comt = ('Target was successfully unmounted')
ret.update({'comment': comt, 'result': True,
'name': name3, 'changes': {'umount': True}})
self.assertDictEqual(mount.unmounted(name3, device3), ret)
# 'mod_watch' function tests: 1
def test_mod_watch(self):