Skip to content

Commit

Permalink
refactor integration tests
Browse files Browse the repository at this point in the history
* for better readability/understanding of what is tested, and what OS is
  targetted
* extend tests to FreeBSD when possible

and:

* convert swap source if it is an UUID or a LABEL (linux), or a memory
  disk (freebsd)
* override default opts and boot when fstype=swap
* do not honor 'fstab' when fstype=swap (fail instead)
* also fail when fstype=swap and 'path' is not 'none' ('-' for Solaris)
  • Loading branch information
quidame committed Feb 15, 2021
1 parent bb5134e commit 8399ca8
Show file tree
Hide file tree
Showing 9 changed files with 631 additions and 479 deletions.
101 changes: 71 additions & 30 deletions plugins/modules/mount.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
options:
path:
description:
- Path to the mount point (e.g. C(/mnt/files)). Must be C(none) for swap spaces.
- Path to the mount point (e.g. C(/mnt/files)). Must be C(none) (or C(-)
on Solaris) for swap spaces.
- Before Ansible 2.3 this option was only usable as I(dest), I(destfile) and I(name).
type: path
required: true
Expand Down Expand Up @@ -69,8 +70,7 @@
description:
- If C(mounted), the device will be actively mounted and appropriately
configured in I(fstab). If the mount point is not present, the mount
point will be created (unless I(path=none), as expected for C(swap)
filesystems).
point will be created (unless I(fstype=swap)).
- If C(unmounted), the device will be unmounted without changing I(fstab).
- C(present) only specifies that the device is to be configured in
I(fstab) and does not trigger or require a mount.
Expand Down Expand Up @@ -102,7 +102,7 @@
boot:
description:
- Determines if the filesystem should be mounted on boot.
- Only applies to Solaris systems.
- Only applies to Solaris systems. Defaults to C(true) unless I(fstype=swap).
type: bool
default: yes
backup:
Expand Down Expand Up @@ -184,20 +184,28 @@
state: mounted
fstype: nfs
- name: Enable swap device with priority=1
- name: Enable swap device with priority=1 (Linux)
ansible.posix.mount:
src: /dev/mapper/vg0-swap
fstype: swap
path: none
opts: pri=1
state: mounted
- name: Disable swap file and keep its record in fstab
- name: Enable a swapfile (Linux, Solaris)
ansible.posix.mount:
src: /var/swapfile
fstype: swap
path: none
state: unmounted
state: mounted
- name: Enable a swapfile (FreeBSD)
ansible.posix.mount:
src: md99
fstype: swap
path: none
opts: file=/var/swapfile
state: mounted
'''


Expand Down Expand Up @@ -317,8 +325,6 @@ def _set_mount_save_old(module, args):
if (
ld['name'] != escaped_args['name'] or (
# In the case of swap, check the src instead
'src' in args and
ld['name'] == 'none' and
ld['fstype'] == 'swap' and
ld['src'] != args['src'])):
to_write.append(line)
Expand Down Expand Up @@ -404,8 +410,6 @@ def unset_mount(module, args):
if (
ld['name'] != escaped_name or (
# In the case of swap, check the src instead
'src' in args and
ld['name'] == 'none' and
ld['fstype'] == 'swap' and
ld['src'] != args['src'])):
to_write.append(line)
Expand Down Expand Up @@ -674,7 +678,7 @@ def get_linux_mounts(module, mntinfo_file="/proc/self/mountinfo"):
def is_swap(module, args):
"""Return True if the device/file is an active swap space, False otherwise."""

if module.params['fstype'] != 'swap' or args['name'] != 'none':
if module.params['fstype'] != 'swap':
return False

if SYSTEM == 'sunos':
Expand All @@ -688,20 +692,26 @@ def is_swap(module, args):
swapon_bin = module.get_bin_path('swapon', required=True)
cmd = [swapon_bin]

if SYSTEM == 'linux':
cmd += ['--noheadings', '--show=name']

rc, out, err = module.run_command(cmd)

if rc != 0:
if rc:
module.fail_json(msg="Error while querying active swaps: %s" % err)

# Get the first field of each line but the header
devices = [x.split()[0] for x in out.splitlines()[1:]]
dev = os.path.realpath(args['src'])

if SYSTEM == 'linux':
devices = out.splitlines()
else:
devices = [x.split()[0] for x in out.splitlines()]
if args['src'].startswith('UUID='):
uuid_path = os.path.join('/dev/disk/by-uuid', args['src'].split('=')[1])
dev = os.path.realpath(uuid_path)
elif args['src'].startswith('LABEL='):
label_path = os.path.join('/dev/disk/by-label', args['src'].split('=')[1])
dev = os.path.realpath(label_path)
elif SYSTEM == 'freebsd':
if args['src'].startswith('md'):
dev = os.path.join('/dev', args['src'])

dev = os.path.realpath(args['src'])
return bool(dev in devices)


Expand All @@ -723,15 +733,17 @@ def swapon(module, args):
# unless provided on command line with '-o opts'. But not all versions of
# swapon accept -o or --options. So we don't use it here, but at least we
# keep the 'priority' and 'discard' flags available on Linux.
if SYSTEM == 'linux' and args['opts'] is not None:
if SYSTEM == 'linux':
for opt in args['opts'].split(','):
if opt.startswith('pri='):
cmd += ['-p', opt.split('=')[1]]
elif opt.startswith('discard'):
cmd += ['--%s' % opt]

# src such as UUID=some_uuid and LABEL=some_label work as is (Linux).
cmd += [args['src']]
if SYSTEM == 'freebsd' and args['src'].startswith('md'):
cmd += [os.path.join('/dev', args['src'])]
else:
cmd += [args['src']]

rc, out, err = module.run_command(cmd)

Expand All @@ -754,7 +766,10 @@ def swapoff(module, args):
swapoff_bin = module.get_bin_path('swapoff', required=True)
cmd = [swapoff_bin]

cmd += [args['src']]
if SYSTEM == 'freebsd' and args['src'].startswith('md'):
cmd += [os.path.join('/dev', args['src'])]
else:
cmd += [args['src']]

rc, out, err = module.run_command(cmd)

Expand Down Expand Up @@ -800,6 +815,25 @@ def main():
),
)

fstype = module.params['fstype']

# swapon/swapoff (and the likes) don't honor alternative fstab locations
# the same way the mount command does, that could make things very, very
# complicated...
if fstype == 'swap':
if SYSTEM == 'sunos':
swap_fstab_file = '/etc/vfstab'
swap_mountpoint = '-'
else:
swap_fstab_file = '/etc/fstab'
swap_mountpoint = 'none'

if module.params['fstab'] not in (None, swap_fstab_file):
module.fail_json(msg="option 'fstype=swap' does not support alternative fstab locations")
if module.params['path'] != swap_mountpoint:
module.fail_json(msg="swap filesystems can't be mounted, please set path to '%s'" %
swap_mountpoint)

# solaris args:
# name, src, fstype, opts, boot, passno, state, fstab=/etc/vfstab
# linux args:
Expand All @@ -816,6 +850,10 @@ def main():
)
if args['fstab'] is None:
args['fstab'] = '/etc/vfstab'

# swap spaces are used internally by kernels and have no mountpoint
if fstype == 'swap':
args['boot'] = 'no'
else:
args = dict(
name=module.params['path'],
Expand All @@ -827,8 +865,11 @@ def main():
if args['fstab'] is None:
args['fstab'] = '/etc/fstab'

# Override default value of options field for swap filesystems
if fstype == 'swap':
args['opts'] = 'sw'
# FreeBSD doesn't have any 'default' so set 'rw' instead
if SYSTEM == 'freebsd':
elif SYSTEM == 'freebsd':
args['opts'] = 'rw'

linux_mounts = []
Expand Down Expand Up @@ -871,7 +912,7 @@ def main():
# changed in fstab then remount it.

state = module.params['state']
name = module.params['path']
name = args['name']
changed = False

if state == 'absent':
Expand Down Expand Up @@ -917,7 +958,7 @@ def main():
changed = True
elif state == 'mounted':
dirs_created = []
if name != 'none' and not os.path.exists(name) and not module.check_mode:
if fstype != 'swap' and not os.path.exists(name) and not module.check_mode:
try:
# Something like mkdir -p but with the possibility to undo.
# Based on some copy-paste from the "file" module.
Expand Down Expand Up @@ -961,7 +1002,7 @@ def main():
changed = True

if not module.check_mode:
if args['fstype'] == 'swap' and name == 'none':
if fstype == 'swap':
res, msg = swapon(module, args)
else:
res, msg = mount(module, args)
Expand All @@ -982,7 +1023,7 @@ def main():
except Exception:
pass

if args['fstype'] == 'swap' and name == 'none':
if fstype == 'swap':
error_msg = "Error enabling swap space %s: %s" % (args['src'], msg)
else:
error_msg = "Error mounting %s: %s" % (name, msg)
Expand All @@ -992,7 +1033,7 @@ def main():
name, changed = set_mount(module, args)
elif state == 'remounted':
if not module.check_mode:
if module.params['fstype'] == 'swap' and name == 'none':
if fstype == 'swap':
res, msg = reswap(module, args)

if res:
Expand Down
94 changes: 94 additions & 0 deletions tests/integration/targets/mount/tasks/bind_mount.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
# Tasks to validate bind mounts (i.e. mount of a directory to another one).
# Linux and FreeBSD only.

- name: Create the mount point (the target of the mount)
file:
state: directory
path: '{{ output_dir }}/mount_dest'

- name: Create a directory to bind mount (the source of the mount)
file:
state: directory
path: '{{ output_dir }}/mount_source'

- name: Put something in the directory so we see that it worked
copy:
content: 'Testing
'
dest: '{{ output_dir }}/mount_source/test_file'
register: orig_info

- name: Bind mount a directory
mount:
src: '{{ output_dir }}/mount_source'
name: '{{ output_dir }}/mount_dest'
state: mounted
fstype: "{{ 'none' if ansible_system == 'Linux' else 'nullfs' }}"
opts: "{{ 'bind' if ansible_system == 'Linux' else omit }}"
register: bind_result

- name: get checksum for bind mounted file
stat:
path: '{{ output_dir }}/mount_dest/test_file'
when: ansible_system in ('FreeBSD', 'Linux')
register: dest_stat

- name: assert the bind mount was successful
assert:
that:
- bind_result is changed
- dest_stat.stat.exists
- orig_info.checksum == dest_stat.stat.checksum

- name: Bind mount a directory again
mount:
src: '{{ output_dir }}/mount_source'
name: '{{ output_dir }}/mount_dest'
state: mounted
fstype: "{{ 'none' if ansible_system == 'Linux' else 'nullfs' }}"
opts: "{{ 'bind' if ansible_system == 'Linux' else omit }}"
register: bind_result

- name: Make sure we didn't mount a second time
assert:
that:
- bind_result is not changed

- name: Remount directory with different options
mount:
src: '{{ output_dir }}/mount_source'
name: '{{ output_dir }}/mount_dest'
state: mounted
fstype: "{{ 'none' if ansible_system == 'Linux' else 'nullfs' }}"
opts: "{{ 'bind,' if ansible_system == 'Linux' else '' }}ro"
register: bind_result

- name: Get mount options
shell: mount | grep mount_dest | grep -E -w '(ro|read-only)' | wc -l
register: remount_options

- name: Make sure the filesystem now has the new opts
assert:
that:
- bind_result is changed
- '''1'' in remount_options.stdout'
- 1 == remount_options.stdout_lines | length

- name: Unmount the bind mount
mount:
name: '{{ output_dir }}/mount_dest'
state: absent
register: unmount_result

- name: Check if the file still exists in dest
stat:
path: '{{ output_dir }}/mount_dest/test_file'
register: dest_stat

- name: Assert that we unmounted
assert:
that:
- unmount_result is changed
- not dest_stat.stat.exists
30 changes: 30 additions & 0 deletions tests/integration/targets/mount/tasks/fstab_last_fields.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
# Tasks to validate the two last fields in fstab, that are optional on Linux,
# are added if missing.

- name: Create fstab record with missing last two fields
copy:
dest: /etc/fstab
content: '//nas/photo /home/jik/pictures cifs defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev
'

- name: Try to change the fstab record with the missing last two fields
mount:
src: //nas/photo
path: /home/jik/pictures
fstype: cifs
opts: defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev,x-systemd.mount-timeout=0
state: present
register: optional_fields_update

- name: Get the content of the fstab file
shell: cat /etc/fstab
register: optional_fields_content

- name: Check if the line containing the missing last two fields was changed
assert:
that:
- optional_fields_update is changed
- ''' 0 0'' in optional_fields_content.stdout'
- 1 == optional_fields_content.stdout_lines | length
Loading

0 comments on commit 8399ca8

Please sign in to comment.