diff --git a/README.md b/README.md index 1e6213e..9880a24 100644 --- a/README.md +++ b/README.md @@ -45,28 +45,69 @@ This variable is required. It supports one of the following values: The snapshot role supports sets of volumes. Sets may contain any number of volumes. Sets are defined in the following format: -```text - snapshot_lvm_set: +```yaml + snapshot_test_set: name: snapset1 volumes: - name: snapshot VG1 LV1 vg: test_vg1 lv: lv1 - percent_space_required: 20 - - name: snapshot VG1 LV1 + percent_space_required: 15 + mountpoint: /mnt/lv1_mp + - name: snapshot VG2 LV3 vg: test_vg2 + lv: lv3 + mountpoint: /mnt/lv3_mp + percent_space_required: 15 + - name: snapshot VG2 LV4 + vg: test_vg2 + lv: lv4 + mountpoint: /mnt/lv4_mp + percent_space_required: 15 + - name: snapshot VG3 LV7 + vg: test_vg3 + lv: lv7 + mountpoint: /mnt/lv7_mp + percent_space_required: 15 +``` + +If the user is indicating the mount action and wants the origin of the snapshot to be mounted rather +than the snapshot, the set would be defined with the additional field "mount_origin" for any entry +that it applies, for example: + +```yaml + snapshot_test_set: + name: snapset1 + volumes: + - name: snapshot VG1 LV1 + vg: test_vg1 lv: lv1 - percent_space_required: 25 + percent_space_required: 15 + mountpoint: /mnt/lv1_mp + mount_origin: false - name: snapshot VG2 LV3 vg: test_vg2 lv: lv3 + mountpoint: /mnt/lv3_mp percent_space_required: 15 + mount_origin: true + - name: snapshot VG2 LV4 + vg: test_vg2 + lv: lv4 + mountpoint: /mnt/lv4_mp + percent_space_required: 15 + mount_origin: false - name: snapshot VG3 LV7 vg: test_vg3 lv: lv7 + mountpoint: /mnt/lv7_mp percent_space_required: 15 + mount_origin: true ``` +The mount_origin flag defaults to false, so it is not necessary when the user is mounting the +snapshot rather than the origin. + If before running the role, with : ### snapshot_lvm_snapset_name @@ -154,6 +195,25 @@ be removed by the remove command without snapshot_lvm_verify_only. snapshot_lvm_verify_only is intended to be used to double check that the snapshot or remove command have completed the operation correctly. +### snapshot_lvm_mountpoint_create + +If the mount point specified doesn't currently exist, create the mount point and any +parent directories necessary for the mount point. + +### snapshot_lvm_mountpoint + +The provide mount target for the block device + +### snapshot_lvm_mount_origin + +If set to true, mount the origin of the snapshot rather than the snapshot. + +### snapshot_lvm_unmount_multiple + +If set to true, unmount all mountpoint for the resulting blockdevice. +Linux allows filesytems to be mounted in multiple locations. Setting +this flag will unmount all locations. + ### Variables Exported by the Role #### snapshot_facts diff --git a/defaults/main.yml b/defaults/main.yml index 9f00763..c54cd24 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -5,8 +5,13 @@ snapshot_lvm_all_vgs: false snapshot_lvm_verify_only: false +snapshot_lvm_mount_origin: false +snapshot_lvm_mountpoint_create: false +snapshot_lvm_unmount_multiple: false snapshot_lvm_vg: '' snapshot_lvm_lv: '' snapshot_lvm_snapset_name: '' snapshot_lvm_percent_space_required: '' snapshot_lvm_set: '' +snapshot_lvm_mountpoint: '' +snapshot_lvm_mount_options: '' diff --git a/tasks/files/snapshot.py b/tasks/files/snapshot.py index 54bf557..d5d26d9 100644 --- a/tasks/files/snapshot.py +++ b/tasks/files/snapshot.py @@ -5,14 +5,20 @@ import logging import math import os +import stat import subprocess import sys +from os.path import join as path_join logger = logging.getLogger("snapshot-role") LVM_NOTFOUND_RC = 5 MAX_LVM_NAME = 127 CHUNK_SIZE = 65536 +DEV_PREFIX = "/dev" + +# Minimum LVM snapshot size (512MiB) +LVM_MIN_SNAPSHOT_SIZE = 512 * 1024**2 class LvmBug(RuntimeError): @@ -46,6 +52,8 @@ class SnapshotCommand: REVERT = "revert" EXTEND = "extend" LIST = "list" + MOUNT = "mount" + UMOUNT = "umount" class SnapshotStatus: @@ -80,6 +88,74 @@ class SnapshotStatus: ERROR_EXTEND_NOT_FOUND = 28 ERROR_EXTEND_FAILED = 29 ERROR_EXTEND_VERIFY_FAILED = 29 + ERROR_UMOUNT_FAILED = 30 + ERROR_MOUNT_FAILED = 31 + ERROR_MOUNT_POINT_NOT_EXISTING = 32 + ERROR_MOUNT_NOT_BLOCKDEV = 33 + ERROR_MOUNT_INVALID_PARAMS = 34 + ERROR_MOUNT_INUSE = 35 + ERROR_MOUNT_VERIFY_FAILED = 36 + ERROR_UMOUNT_VERIFY_FAILED = 37 + + +def makedirs(path): + if not os.path.isdir(path): + os.makedirs(path, 0o755) + + +def mount(blockdev, mountpoint, fstype=None, options=None, create_mountpoint=False): + + mount_command = [] + mountpoint = os.path.normpath(mountpoint) + if options is None: + options = "defaults" + + if not os.path.isdir(mountpoint): + if not create_mountpoint: + return ( + SnapshotStatus.ERROR_MOUNT_POINT_NOT_EXISTING, + "mount point does not exist", + ) + makedirs(mountpoint) + + mountpoint = os.path.normpath(mountpoint) + mount_command.append("mount") + + if fstype: + mount_command.append("-t") + mount_command.append(fstype) + + if options: + mount_command.append("-o") + mount_command.append(options) + + mount_command.append(blockdev) + mount_command.append(mountpoint) + + rc, output = run_command(mount_command) + + if rc != 0: + logger.info("failed to mount: ".join(mount_command)) + logger.info(output) + return (SnapshotStatus.ERROR_UMOUNT_FAILED, output) + + return SnapshotStatus.SNAPSHOT_OK, "" + + +def umount(umount_target, all_targets): + umount_command = [] + + umount_command.append("umount") + umount_command.append(umount_target) + if all_targets: + umount_command.append("-A") + + rc, output = run_command(umount_command) + + if rc != 0: + logger.info("failed to unmount %s: %s", umount_target, output) + return (SnapshotStatus.ERROR_UMOUNT_FAILED, output) + return SnapshotStatus.SNAPSHOT_OK, "" # what percentage is part of whole @@ -127,15 +203,19 @@ def run_command(argv, stdin=None): logger.info("Running... %s", " ".join(argv)) try: proc = subprocess.Popen( - argv, stdin=stdin, stdout=subprocess.PIPE, close_fds=True + argv, + stdin=stdin, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, ) out, err = proc.communicate() - - if err is not None: - logger.info("Error running %s: %s", argv[0], err) - - out = out.decode("utf-8") + if err: + logger.info(err.decode().strip()) + out = err.decode("utf-8") + else: + out = out.decode("utf-8") except OSError as e: logger.info("Error running %s: %s", argv[0], e.strerror) raise @@ -223,6 +303,52 @@ def lvm_get_fs_mount_points(block_path): return mount_list +def lvm_list_json(vg_name, lv_name): + lvm_json = lvm_full_report_json() + report = lvm_json["report"] + vg_dict = dict() + fs_dict = dict() + lv_list = list() + fs_list = list() + top_level = dict() + + # Revert snapshots + for list_item in report: + # The list contains items that are not VGs + try: + list_item["vg"] + except KeyError: + continue + vg = list_item["vg"][0]["vg_name"] + if vg_name and vg != vg_name: + continue + + for lv_item in list_item["lv"]: + lv_item_name = lv_item["lv_name"] + if lv_name and lv_name != lv_item_name: + continue + + lv_list.append(lv_item) + + if len(lv_list) > 0: + vg_dict[vg] = lv_list + lv_list = [] + + for lv_list in vg_dict.items(): + for lv_item in lv_list[1]: + block_path = lv_item["lv_path"] + fs_mount_points = lvm_get_fs_mount_points(block_path) + if fs_mount_points: + fs_list.append(fs_mount_points) + fs_dict[block_path] = fs_mount_points + fs_list = list() + + top_level["volumes"] = vg_dict + top_level["mounts"] = fs_dict + print(json.dumps(top_level, indent=4)) + return SnapshotStatus.SNAPSHOT_OK, "" + + def lvm_list_snapset_json(all_lvs, vg_name, lv_name, suffix): lvm_json = lvm_full_report_json() report = lvm_json["report"] @@ -732,20 +858,6 @@ def extend_lvs(vg_name, lv_name, suffix, required_space): return SnapshotStatus.SNAPSHOT_OK, "" -def list_set(all_lvs, snapset_json): - snapset_name = snapset_json["name"] - lvm_json = lvm_list_snapset_json(all_lvs, None, None, snapset_name) - print(lvm_json) - - return SnapshotStatus.SNAPSHOT_OK, "" - - -def list_lvs(all_lvs, vg_name, lv_name, suffix): - lvm_json = lvm_list_snapset_json(all_lvs, vg_name, lv_name, suffix) - print(lvm_json) - return SnapshotStatus.SNAPSHOT_OK, "" - - def snapshot_lv(vg_name, lv_name, suffix, snap_size): snapshot_name = get_snapshot_name(lv_name, suffix) @@ -1006,6 +1118,272 @@ def revert_snapshot_set(snapset_json): return SnapshotStatus.SNAPSHOT_OK, "" +def umount_verify(mountpoint, vg_name, lv_to_check): + + blockdev = path_join(DEV_PREFIX, vg_name, lv_to_check) + + mount_list = lvm_get_fs_mount_points(mountpoint) + + if mount_list: + for mount_point_json in mount_list: + if mount_point_json["SOURCE"] == blockdev: + return ( + SnapshotStatus.ERROR_MOUNT_VERIFY_FAILED, + "device is mounted on mountpoint: " + blockdev, + ) + + if mount_point_json["TARGET"] == mountpoint: + return ( + SnapshotStatus.ERROR_MOUNT_VERIFY_FAILED, + "device is mounted on mountpoint: " + mountpoint, + ) + + return SnapshotStatus.SNAPSHOT_OK, "" + + +def umount_lv(umount_target, vg_name, lv_name, all_targets): + logger.info("umount_lv : %s", umount_target) + + if vg_name and lv_name: + # Check to make sure all the source vgs/lvs exist + rc, message = verify_source_lvs_exist(vg_name, lv_name) + if rc != SnapshotStatus.SNAPSHOT_OK: + return rc, message + + return umount(umount_target, all_targets) + + +def umount_snapshot_set(snapset_json, verify_only): + snapset_name = snapset_json["name"] + volume_list = snapset_json["volumes"] + + logger.info("mount verify snapsset : %s", snapset_name) + + for list_item in volume_list: + vg_name = list_item["vg"] + lv_name = list_item["lv"] + if "mountpoint" in list_item: + mountpoint = list_item["mountpoint"] + else: + return ( + SnapshotStatus.ERROR_UMOUNT_VERIFY_FAILED, + "set item must provide a mountpoint for : " + vg_name + "/" + lv_name, + ) + + if "all_targets" in list_item: + all_targets = bool(list_item["all_targets"]) + else: + all_targets = False + + if "mount_origin" in list_item: + origin = bool(list_item["mount_origin"]) + + else: + origin = False + + if origin: + lv_to_check = lv_name + else: + lv_to_check = get_snapshot_name(lv_name, snapset_name) + + if verify_only: + rc, message = umount_verify(mountpoint, vg_name, lv_to_check) + else: + rc, message = umount_lv(mountpoint, vg_name, lv_to_check, all_targets) + + if rc != SnapshotStatus.SNAPSHOT_OK: + return rc, message + + return SnapshotStatus.SNAPSHOT_OK, "" + + +def mount_snapshot_set(snapset_json, verify_only, cmdline_create_mountpoint): + snapset_name = snapset_json["name"] + volume_list = snapset_json["volumes"] + + logger.info("mount verify snapsset : %s", snapset_name) + + for list_item in volume_list: + vg_name = list_item["vg"] + lv_name = list_item["lv"] + + if not cmdline_create_mountpoint: + if "create_mountpoint" in list_item: + create_mountpoint = bool(list_item["create_mountpoint"]) + else: + create_mountpoint = False + else: + create_mountpoint = bool(cmdline_create_mountpoint) + + if "mount_origin" in list_item: + origin = bool(list_item["mount_origin"]) + else: + origin = False + + if "fstype" in list_item: + fstype = list_item["fstype"] + else: + fstype = None + + if "options" in list_item: + options = list_item["options"] + else: + options = None + + if "mountpoint" in list_item: + mountpoint = list_item["mountpoint"] + else: + return ( + SnapshotStatus.ERROR_MOUNT_INVALID_PARAMS, + "set item must provide a mountpoint for : " + vg_name + "/" + lv_name, + ) + + if origin: + lv_to_check = lv_name + else: + lv_to_check = get_snapshot_name(lv_name, snapset_name) + + blockdev = path_join(DEV_PREFIX, vg_name, lv_to_check) + + if verify_only: + rc, message = mount_verify( + origin, mountpoint, blockdev, vg_name, lv_name, snapset_name + ) + else: + rc, message = mount_lv( + create_mountpoint, + origin, + mountpoint, + fstype, + blockdev, + options, + vg_name, + lv_name, + snapset_name, + ) + if rc != SnapshotStatus.SNAPSHOT_OK: + return rc, message + + return SnapshotStatus.SNAPSHOT_OK, "" + + +def mount_verify(origin, mountpoint, blockdev, vg_name, lv_name, snapset_name): + logger.info( + "mount_verify_lv : %d %s %s %s %s %s", + origin, + mountpoint, + blockdev, + vg_name, + lv_name, + snapset_name, + ) + + if not mountpoint: + return ( + SnapshotStatus.ERROR_MOUNT_INVALID_PARAMS, + "must provide mountpoint", + ) + + if not blockdev and (not vg_name or not lv_name): + return ( + SnapshotStatus.ERROR_MOUNT_INVALID_PARAMS, + "must provide blockdev or vg/lv for mount source", + ) + + if vg_name and lv_name: + if origin: + lv_to_check = lv_name + else: + lv_to_check = get_snapshot_name(lv_name, snapset_name) + + # Check to make sure all the source vgs/lvs exist + rc, message = verify_source_lvs_exist(vg_name, lv_to_check) + if rc != SnapshotStatus.SNAPSHOT_OK: + return rc, message + + blockdev = path_join(DEV_PREFIX, vg_name, lv_to_check) + else: + mode = os.stat(blockdev).st_mode + if not stat.S_ISBLK(mode): + return ( + SnapshotStatus.ERROR_MOUNT_NOT_BLOCKDEV, + "blockdev parameter is not a block device", + ) + + if not blockdev: + return ( + SnapshotStatus.ERROR_MOUNT_NOT_BLOCKDEV, + "blockdev or vg/lv is a required", + ) + + mount_list = lvm_get_fs_mount_points(blockdev) + + if not mount_list: + return ( + SnapshotStatus.ERROR_MOUNT_VERIFY_FAILED, + "blockdev not mounted on any mountpoint: " + blockdev, + ) + + for mount_point_json in mount_list: + if mount_point_json["TARGET"] == mountpoint: + return SnapshotStatus.SNAPSHOT_OK, "" + + return ( + SnapshotStatus.ERROR_MOUNT_VERIFY_FAILED, + "blockdev not mounted on specified mountpoint: " + blockdev + " " + mountpoint, + ) + + +def mount_lv( + create, + origin, + mountpoint, + fstype, + blockdev, + options, + vg_name, + lv_name, + snapset_name, +): + logger.info("mount_lv : %s", mountpoint) + + if not blockdev and (not vg_name or not lv_name): + return ( + SnapshotStatus.ERROR_MOUNT_INVALID_PARAMS, + "must provide blockdev or vg/lv for mount source", + ) + + if vg_name and lv_name: + if origin: + lv_to_mount = lv_name + else: + lv_to_mount = get_snapshot_name(lv_name, snapset_name) + + # Check to make sure all the source vgs/lvs exist + rc, message = verify_source_lvs_exist(vg_name, lv_to_mount) + if rc != SnapshotStatus.SNAPSHOT_OK: + return rc, message + + blockdev = path_join(DEV_PREFIX, vg_name, lv_to_mount) + else: + mode = os.stat(blockdev).st_mode + if not stat.S_ISBLK(mode): + return ( + SnapshotStatus.ERROR_MOUNT_NOT_BLOCKDEV, + "blockdev parameter is not a block device", + ) + + if not blockdev: + return ( + SnapshotStatus.ERROR_MOUNT_NOT_BLOCKDEV, + "blockdev or vg/lv is a required", + ) + + rc, message = mount(blockdev, mountpoint, fstype, options, create) + + return rc, message + + def remove_snapshot_set(snapset_json): snapset_name = snapset_json["name"] volume_list = snapset_json["volumes"] @@ -1032,7 +1410,10 @@ def remove_snapshot_set(snapset_json): return rc, "failed to lvm_is_inuse status" if in_use: - return (rc, "volume is in use: " + vg + "/" + snapshot_name) + return ( + SnapshotStatus.ERROR_REMOVE_FAILED_INUSE, + "volume is in use: " + vg + "/" + snapshot_name, + ) for list_item in volume_list: vg = list_item["vg"] @@ -1551,6 +1932,10 @@ def validate_args(args): print("Snapset name must be provided : ", args.operation) sys.exit(SnapshotStatus.ERROR_CMD_INVALID) + if len(args.suffix) == 0: + print("Snapset name must be provided : ", args.operation) + sys.exit(SnapshotStatus.ERROR_CMD_INVALID) + # not all commands include required_space if hasattr(args, "required_space"): rc, message, _required_space = get_required_space(args.required_space) @@ -1562,6 +1947,24 @@ def validate_args(args): return True +def validate_umount_args(args): + if not args.mountpoint and not args.blockdev: + print("--mountpoint or --blockdev is required : ", args.operation) + sys.exit(SnapshotStatus.ERROR_MOUNT_INVALID_PARAMS) + + +def validate_mount_args(args): + if not args.blockdev and (not args.volume_group or not args.logical_volume): + print("must provide blockdev or vg/lv for mount source : ", args.operation) + sys.exit(SnapshotStatus.ERROR_MOUNT_INVALID_PARAMS) + + if not args.mountpoint: + print("mountpoint is required : ", args.operation) + sys.exit(SnapshotStatus.ERROR_MOUNT_INVALID_PARAMS) + + return SnapshotStatus.SNAPSHOT_OK, "" + + def get_required_space(required_space_str): try: percent_space_required = int(required_space_str) @@ -1637,6 +2040,14 @@ def validate_json_request(snapset_json, check_percent_space_required): return SnapshotStatus.SNAPSHOT_OK, "" +def validate_json_mount_request(snapset_json): + return SnapshotStatus.SNAPSHOT_OK, "" + + +def validate_json_umount_request(snapset_json): + return SnapshotStatus.SNAPSHOT_OK, "" + + def validate_snapset_json(cmd, snapset, verify_only): try: snapset_json = json.loads(snapset) @@ -1650,11 +2061,15 @@ def validate_snapset_json(cmd, snapset, verify_only): elif cmd == SnapshotCommand.CHECK and not verify_only: rc, message = validate_json_request(snapset_json, True) elif cmd == SnapshotCommand.CHECK and verify_only: - rc, message = validate_json_request(snapset_json, False) + rc, message = validate_json_request(snapset_json, not verify_only) elif cmd == SnapshotCommand.REMOVE: rc, message = validate_json_request(snapset_json, False) elif cmd == SnapshotCommand.LIST: rc, message = validate_json_request(snapset_json, False) + elif cmd == SnapshotCommand.MOUNT: + rc, message = validate_json_mount_request(snapset_json) + elif cmd == SnapshotCommand.UMOUNT: + rc, message = validate_json_umount_request(snapset_json) else: rc = SnapshotStatus.ERROR_UNKNOWN_FAILURE message = "validate_snapset_json" @@ -1877,39 +2292,114 @@ def list_cmd(args): if args.set_json is None: validate_args(args) - rc, message = list_lvs( - args.all, + rc, message = lvm_list_json( args.volume_group, args.logical_volume, - args.suffix, ) + else: + # TODO filter the set based on the JSON + rc, message = lvm_list_json(None, None) + + return rc, message + + +def mount_cmd(args): + logger.info( + "mount_cmd: %d %d %d %s %s %s %s %s %s %s %s", + args.create, + args.origin, + args.verify, + args.mountpoint, + args.fstype, + args.blockdev, + args.options, + args.suffix, + args.logical_volume, + args.volume_group, + args.set_json, + ) + + if args.set_json is None: + validate_mount_args(args) + + if args.verify: + rc, message = mount_verify( + args.origin, + args.mountpoint, + args.blockdev, + args.volume_group, + args.logical_volume, + args.suffix, + ) + else: + rc, message = mount_lv( + args.create, + args.origin, + args.mountpoint, + args.fstype, + args.blockdev, + args.options, + args.volume_group, + args.logical_volume, + args.suffix, + ) else: rc, message, snapset_json = validate_snapset_json( - SnapshotCommand.LIST, args.set_json, False + SnapshotCommand.MOUNT, args.set_json, args.verify ) - rc, message = list_set(args.all, snapset_json) + if rc != SnapshotStatus.SNAPSHOT_OK: + return rc, message + + rc, message = mount_snapshot_set(snapset_json, args.verify, args.create) + + return rc, message + + +def umount_cmd(args): + logger.info( + "umount_cmd: %d %s %s %s %s", + args.all_targets, + args.mountpoint, + args.logical_volume, + args.volume_group, + args.set_json, + ) + if args.set_json is None: + validate_umount_args(args) + + if args.verify: + rc, message = umount_verify( + args.mountpoint, + args.volume_group, + args.logical_volume, + ) + else: + if args.mountpoint: + umount_target = args.mountpoint + else: + umount_target = args.blockdev + rc, message = umount_lv( + umount_target, args.volume_group, args.logical_volume, args.all_targets + ) + else: + rc, message, snapset_json = validate_snapset_json( + SnapshotCommand.UMOUNT, args.set_json, False + ) + rc, message = umount_snapshot_set(snapset_json, args.verify) return rc, message if __name__ == "__main__": set_up_logging() + # Ensure that we get consistent output for parsing stdout/stderr and that we # are using the lvmdbusd profile. os.environ["LC_ALL"] = "C" os.environ["LVM_COMMAND_PROFILE"] = "lvmdbusd" - # arguments common to all operations common_parser = argparse.ArgumentParser(add_help=False) - common_parser.add_argument( - "-g", - "--group", - nargs="?", - action="store", - required=False, - default=None, - dest="set_json", - ) + # arguments common to most operations common_parser.add_argument( "-a", "--all", @@ -1919,6 +2409,35 @@ def list_cmd(args): help="snapshot all VGs and LVs", ) common_parser.add_argument( + "-s", + "--snapset", + dest="suffix", + type=str, + help="name for snapshot set", + ) + common_parser.add_argument( + "-p", + "--prefix", + dest="prefix", + type=str, + help="prefix to add to volume name for snapshot", + ) + + # Group parser + group_parser = argparse.ArgumentParser(add_help=False) + group_parser.add_argument( + "-g", + "--group", + nargs="?", + action="store", + required=False, + default=None, + dest="set_json", + ) + + # LVM VG/LV parser + lvm_parser = argparse.ArgumentParser(add_help=False) + lvm_parser.add_argument( "-vg", "--volumegroup", nargs="?", @@ -1927,7 +2446,7 @@ def list_cmd(args): dest="volume_group", help="volume group to snapshot", ) - common_parser.add_argument( + lvm_parser.add_argument( "-lv", "--logicalvolume", nargs="?", @@ -1936,13 +2455,6 @@ def list_cmd(args): dest="logical_volume", help="logical volume to snapshot", ) - common_parser.add_argument( - "-s", - "--snapset", - dest="suffix", - type=str, - help="name for snapshot set", - ) # arguments for operations that do verify verify_parser = argparse.ArgumentParser(add_help=False) @@ -1968,6 +2480,60 @@ def list_cmd(args): help="percent of required space in the volume group to be reserved for snapshot", ) + # arguments for operations that deal with mount of filesytems + mountpoint_parser = argparse.ArgumentParser(add_help=False) + mountpoint_parser.add_argument( + "-m", + "--mountpoint", + dest="mountpoint", + required=False, + type=str, + help="mount point for block device", + ) + + # arguments for operations that deal with mount of filesytems + mount_parser = argparse.ArgumentParser(add_help=False) + mount_parser.add_argument( + "-b", + "--blockdev", + dest="blockdev", + required=False, + type=str, + help="mount point for block device", + ) + mount_parser.add_argument( + "-t", + "--type", + dest="fstype", + required=False, + default="", + type=str, + help="filesystem type", + ) + mount_parser.add_argument( + "-o", + "--options", + dest="options", + required=False, + type=str, + help="mount options", + ) + mount_parser.add_argument( + "-c", + "--create", + action="store_true", + default=False, + dest="create", + help="create the directory for the mount point if it doesn't already exist", + ) + mount_parser.add_argument( + "-O", + "--origin", + action="store_true", + default=False, + dest="origin", + help="mount the origin", + ) parser = argparse.ArgumentParser(description="Snapshot Operations") # sub-parsers @@ -1977,7 +2543,7 @@ def list_cmd(args): snapshot_parser = subparsers.add_parser( SnapshotCommand.SNAPSHOT, help="Snapshot given VG/LVs", - parents=[common_parser, req_space_parser], + parents=[common_parser, lvm_parser, group_parser, req_space_parser], ) snapshot_parser.set_defaults(func=snapshot_cmd) @@ -1985,7 +2551,13 @@ def list_cmd(args): check_parser = subparsers.add_parser( SnapshotCommand.CHECK, help="Check space for given VG/LV", - parents=[common_parser, req_space_parser, verify_parser], + parents=[ + common_parser, + lvm_parser, + group_parser, + req_space_parser, + verify_parser, + ], ) check_parser.set_defaults(func=check_cmd) @@ -1993,7 +2565,7 @@ def list_cmd(args): remove_parser = subparsers.add_parser( SnapshotCommand.REMOVE, help="Remove snapshots", - parents=[common_parser, verify_parser], + parents=[common_parser, group_parser, lvm_parser, verify_parser], ) remove_parser.set_defaults(func=remove_cmd) @@ -2001,7 +2573,7 @@ def list_cmd(args): revert_parser = subparsers.add_parser( SnapshotCommand.REVERT, help="Revert to snapshots", - parents=[common_parser, verify_parser], + parents=[common_parser, group_parser, lvm_parser, verify_parser], ) revert_parser.set_defaults(func=revert_cmd) @@ -2009,7 +2581,13 @@ def list_cmd(args): extend_parser = subparsers.add_parser( SnapshotCommand.EXTEND, help="Extend given LVs", - parents=[common_parser, verify_parser, req_space_parser], + parents=[ + common_parser, + group_parser, + lvm_parser, + verify_parser, + req_space_parser, + ], ) extend_parser.set_defaults(func=extend_cmd) @@ -2017,10 +2595,47 @@ def list_cmd(args): list_parser = subparsers.add_parser( SnapshotCommand.LIST, help="List snapshots", - parents=[common_parser], + parents=[common_parser, group_parser, lvm_parser], ) list_parser.set_defaults(func=list_cmd) + # sub-parser for 'mount' + mount_parser = subparsers.add_parser( + SnapshotCommand.MOUNT, + help="mount filesystems", + parents=[ + common_parser, + mountpoint_parser, + mount_parser, + group_parser, + lvm_parser, + verify_parser, + ], + ) + mount_parser.set_defaults(func=mount_cmd) + + # sub-parser for 'umount' + umount_parser = subparsers.add_parser( + SnapshotCommand.UMOUNT, + help="umount filesystems", + parents=[ + common_parser, + mountpoint_parser, + group_parser, + lvm_parser, + verify_parser, + ], + ) + umount_parser.add_argument( + "-A", + "--all-targets", + action="store_true", + default=True, + dest="all_targets", + help="unmount all mountpoints for the given device", + ) + umount_parser.set_defaults(func=umount_cmd) + args = parser.parse_args() return_code, display_message = args.func(args) print_result(return_code, display_message) diff --git a/tasks/main.yml b/tasks/main.yml index c260e00..8e59757 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -41,3 +41,11 @@ - name: Extend Snapshots ansible.builtin.include_tasks: extend.yml when: snapshot_lvm_action == "extend" + +- name: Mount Snapshots or Origin + ansible.builtin.include_tasks: mount.yml + when: snapshot_lvm_action == "mount" + +- name: Unmount Snapshots or Origin + ansible.builtin.include_tasks: umount.yml + when: snapshot_lvm_action == "umount" diff --git a/tasks/mount.yml b/tasks/mount.yml new file mode 100644 index 0000000..0fbdf7e --- /dev/null +++ b/tasks/mount.yml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT +--- +- name: Mount Snapshot or Origin + ansible.builtin.script: "{{ __snapshot_cmd }}" + args: + executable: "{{ __snapshot_python }}" + register: snapshot_cmd diff --git a/tasks/umount.yml b/tasks/umount.yml new file mode 100644 index 0000000..3d4c5e2 --- /dev/null +++ b/tasks/umount.yml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT +--- +- name: Umount Snapshot or Origin + ansible.builtin.script: "{{ __snapshot_cmd }}" + args: + executable: "{{ __snapshot_python }}" + register: snapshot_cmd diff --git a/tests/tests_list.yml b/tests/tests_list.yml index 2d913da..99270dc 100644 --- a/tests/tests_list.yml +++ b/tests/tests_list.yml @@ -1,5 +1,5 @@ --- -- name: Basic snapshot test +- name: Basic list snapshot test hosts: all tasks: - name: Run tests diff --git a/tests/tests_mount.yml b/tests/tests_mount.yml new file mode 100644 index 0000000..c9c6aaf --- /dev/null +++ b/tests/tests_mount.yml @@ -0,0 +1,214 @@ +--- +- name: Basic mount snapshot test + hosts: all + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk lists + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + disk_list_2: "{{ range(3, 6) | map('extract', unused_disks) | + list }}" + disk_list_3: "{{ range(6, 10) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "15%" + fs_type: xfs + - name: lv2 + size: "50%" + fs_type: xfs + - name: test_vg2 + disks: "{{ disk_list_2 }}" + volumes: + - name: lv3 + size: "10%" + fs_type: xfs + - name: lv4 + size: "20%" + fs_type: xfs + - name: test_vg3 + disks: "{{ disk_list_3 }}" + volumes: + - name: lv5 + size: "30%" + fs_type: xfs + - name: lv6 + size: "25%" + fs_type: xfs + - name: lv7 + size: "10%" + fs_type: xfs + - name: lv8 + size: "10%" + fs_type: xfs + + - name: Run the snapshot role to create snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_percent_space_required: 15 + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: snapshot + + - name: Verify the snapshot LVs are created + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: check + + - name: Mount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the snapshot for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the snapshot for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the origin for lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv6 + snapshot_lvm_mountpoint: /mnt/lv6_mp + snapshot_lvm_mountpoint_create: true + snapshot_lvm_mount_origin: true + + - name: Umount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + + - name: Umount the snapshot for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + + - name: Umount the snapshot for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + + - name: Umount the origin for lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: umount + snapshot_lvm_mountpoint: /mnt/lv6_mp + + + - name: Run the snapshot role remove the snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: remove + + - name: Use the snapshot_lvm_verify option to make sure remove is done + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: remove + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent + - name: lv2 + state: absent + - name: test_vg2 + disks: "{{ disk_list_2 }}" + state: absent + volumes: + - name: lv3 + state: absent + - name: lv4 + state: absent + - name: test_vg3 + disks: "{{ disk_list_3 }}" + state: absent + volumes: + - name: lv5 + state: absent + - name: lv6 + state: absent + - name: lv7 + state: absent + - name: lv8 + state: absent diff --git a/tests/tests_mount_no_vg_fail.yml b/tests/tests_mount_no_vg_fail.yml new file mode 100644 index 0000000..25d7c92 --- /dev/null +++ b/tests/tests_mount_no_vg_fail.yml @@ -0,0 +1,84 @@ +--- +- name: Verify snapshot mount action fails VG doesn't exist + hosts: all + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk list + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "50%" + fs_type: xfs + + - name: Create snapshot for LV + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_percent_space_required: 15 + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: snapshot + + - name: Test failure of verifying wrong mount + include_tasks: verify-role-failed.yml + vars: + __snapshot_failed_regex: source volume group does not exist:* + __snapshot_failed_msg: Role did not fail no VG error + __snapshot_failed_params: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: wrong_vg + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_mountpoint_create: true + + - name: Remove the snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_all_vgs: true + snapshot_lvm_action: remove + + - name: Use the snapshot_lvm_verify option to make sure remove is done + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: remove + + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent diff --git a/tests/tests_mount_verify.yml b/tests/tests_mount_verify.yml new file mode 100644 index 0000000..67c598a --- /dev/null +++ b/tests/tests_mount_verify.yml @@ -0,0 +1,305 @@ +--- +- name: Basic mount verify snapshot test + hosts: all + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk lists + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + disk_list_2: "{{ range(3, 6) | map('extract', unused_disks) | + list }}" + disk_list_3: "{{ range(6, 10) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "15%" + fs_type: xfs + - name: lv2 + size: "50%" + fs_type: xfs + - name: test_vg2 + disks: "{{ disk_list_2 }}" + volumes: + - name: lv3 + size: "10%" + fs_type: xfs + - name: lv4 + size: "20%" + fs_type: xfs + - name: test_vg3 + disks: "{{ disk_list_3 }}" + volumes: + - name: lv5 + size: "30%" + fs_type: xfs + - name: lv6 + size: "25%" + fs_type: xfs + - name: lv7 + size: "10%" + fs_type: xfs + - name: lv8 + size: "10%" + fs_type: xfs + + - name: Run the snapshot role to create snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_percent_space_required: 15 + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: snapshot + + - name: Verify the snapshot LVs are created + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: check + + - name: Mount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the snapshot for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the snapshot for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the origin for lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv6 + snapshot_lvm_mountpoint: /mnt/lv6_mp + snapshot_lvm_mountpoint_create: true + snapshot_lvm_mount_origin: true + + - name: Verify snapshot is mounted for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_verify_only: true + + + - name: Verify snapshot is mounted for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: ummountount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + snapshot_lvm_verify_only: true + + + - name: Verify snapshot is mounted for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + snapshot_lvm_verify_only: true + + - name: Verify origin is mounted lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv6 + snapshot_lvm_mountpoint: /mnt/lv6_mp + snapshot_lvm_mountpoint_create: true + snapshot_lvm_mount_origin: true + snapshot_lvm_verify_only: true + + - name: Umount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + + - name: Umount the snapshot for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + + - name: Umount the snapshot for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + + - name: Umount the origin for lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: umount + snapshot_lvm_mountpoint: /mnt/lv6_mp + + - name: Verify umount of the for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_verify_only: true + + - name: Verify umount of the for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + snapshot_lvm_verify_only: true + + - name: Verify umount of the for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + snapshot_lvm_verify_only: true + + - name: Verify umount of the origin for lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv6 + snapshot_lvm_mountpoint: /mnt/lv6_mp + snapshot_lvm_verify_only: true + + - name: Run the snapshot role remove the snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: remove + + - name: Use the snapshot_lvm_verify option to make sure remove is done + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: remove + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent + - name: lv2 + state: absent + - name: test_vg2 + disks: "{{ disk_list_2 }}" + state: absent + volumes: + - name: lv3 + state: absent + - name: lv4 + state: absent + - name: test_vg3 + disks: "{{ disk_list_3 }}" + state: absent + volumes: + - name: lv5 + state: absent + - name: lv6 + state: absent + - name: lv7 + state: absent + - name: lv8 + state: absent diff --git a/tests/tests_mount_verify_fail.yml b/tests/tests_mount_verify_fail.yml new file mode 100644 index 0000000..2a6c946 --- /dev/null +++ b/tests/tests_mount_verify_fail.yml @@ -0,0 +1,115 @@ +--- +- name: Verify mount action fails with wrong mount point + hosts: all + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk list + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "50%" + fs_type: xfs + + - name: Create snapshot for LV + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_percent_space_required: 15 + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: snapshot + + - name: Mount the snapshot for LV + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_mountpoint_create: true + + - name: Verify snapshot is mounted for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_verify_only: true + + - name: Test failure of verifying wrong mount + include_tasks: verify-role-failed.yml + vars: + __snapshot_failed_regex: blockdev not mounted on specified* + __snapshot_failed_msg: Role did not fail with not mounted error + __snapshot_failed_params: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/wrong_mountpoint + snapshot_lvm_verify_only: true + + - name: Umount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + + - name: Remove the snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: remove + + - name: Use the snapshot_lvm_verify option to make sure remove is done + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: remove + + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent diff --git a/tests/tests_set_mount.yml b/tests/tests_set_mount.yml new file mode 100644 index 0000000..b559cfc --- /dev/null +++ b/tests/tests_set_mount.yml @@ -0,0 +1,151 @@ +--- +- name: Mount snapshots of logical volumes across different volume groups + hosts: all + vars: + snapshot_test_set: + name: snapset1 + volumes: + - name: snapshot VG1 LV1 + vg: test_vg1 + lv: lv1 + mountpoint: /mnt/lv1_mp + percent_space_required: 15 + create_mountpoint: true + - name: snapshot VG2 LV3 + vg: test_vg2 + lv: lv3 + mountpoint: /mnt/lv3_mp + percent_space_required: 15 + create_mountpoint: true + - name: snapshot VG2 LV4 + vg: test_vg2 + lv: lv4 + mountpoint: /mnt/lv4_mp + percent_space_required: 15 + create_mountpoint: true + - name: snapshot VG3 LV7 + vg: test_vg3 + lv: lv7 + mountpoint: /mnt/lv7_mp + percent_space_required: 15 + create_mountpoint: true + + + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk lists + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + disk_list_2: "{{ range(3, 6) | map('extract', unused_disks) | + list }}" + disk_list_3: "{{ range(6, 10) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "15%" + - name: lv2 + size: "50%" + - name: test_vg2 + disks: "{{ disk_list_2 }}" + volumes: + - name: lv3 + size: "10%" + - name: lv4 + size: "20%" + - name: test_vg3 + disks: "{{ disk_list_3 }}" + volumes: + - name: lv5 + size: "30%" + - name: lv6 + size: "25%" + - name: lv7 + size: "10%" + - name: lv8 + size: "10%" + + - name: Run the snapshot role to create a snapshot set of LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: snapshot + snapshot_lvm_set: "{{ snapshot_test_set }}" + + - name: Verify the set of snapshots for the LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: check + snapshot_lvm_set: "{{ snapshot_test_set }}" + snapshot_lvm_verify_only: true + + - name: Mount the set + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: mount + snapshot_lvm_set: "{{ snapshot_test_set }}" + + - name: Verify the mount is done + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_verify_only: true + snapshot_lvm_action: mount + snapshot_lvm_set: "{{ snapshot_test_set }}" + + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent + - name: lv2 + state: absent + - name: test_vg2 + disks: "{{ disk_list_2 }}" + state: absent + volumes: + - name: lv3 + state: absent + - name: lv4 + state: absent + - name: test_vg3 + disks: "{{ disk_list_3 }}" + state: absent + volumes: + - name: lv5 + state: absent + - name: lv6 + state: absent + - name: lv7 + state: absent + - name: lv8 + state: absent diff --git a/tests/tests_set_mount_verify_fail.yml b/tests/tests_set_mount_verify_fail.yml new file mode 100644 index 0000000..5096e1a --- /dev/null +++ b/tests/tests_set_mount_verify_fail.yml @@ -0,0 +1,86 @@ +--- +- name: Verify the extend verify commmand fails when space too low + hosts: all + vars: + snapshot_test_set: + name: snapset1 + volumes: + - name: snapshot VG1 LV1 + vg: test_vg1 + lv: lv1 + percent_space_required: 20 + snapshot_test_verify_set: + name: snapset1 + volumes: + - name: snapshot VG1 LV1 + vg: test_vg1 + lv: lv1 + percent_space_required: 50 + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk list + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "50%" + + - name: Run the snapshot role to create snapshot set of LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: snapshot + snapshot_lvm_set: "{{ snapshot_test_set }}" + + - name: Run the snapshot role to verify the set of snapshots for the LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: check + snapshot_lvm_set: "{{ snapshot_test_set }}" + snapshot_lvm_verify_only: true + + - name: Test failure extend verify + include_tasks: verify-role-failed.yml + vars: + __snapshot_failed_regex: + "verify failed due to insufficient space for:*" + __snapshot_failed_msg: Role did not fail with extend verify + __snapshot_failed_params: + snapshot_lvm_action: extend + snapshot_lvm_verify_only: true + __snapshot_lvm_set: "{{ snapshot_test_verify_set }}" + + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent diff --git a/tests/tests_umount_verify.yml b/tests/tests_umount_verify.yml new file mode 100644 index 0000000..96445b2 --- /dev/null +++ b/tests/tests_umount_verify.yml @@ -0,0 +1,262 @@ +--- +- name: Basic umount verify snapshot test + hosts: all + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk lists + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + disk_list_2: "{{ range(3, 6) | map('extract', unused_disks) | + list }}" + disk_list_3: "{{ range(6, 10) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "15%" + fs_type: xfs + - name: lv2 + size: "50%" + fs_type: xfs + - name: test_vg2 + disks: "{{ disk_list_2 }}" + volumes: + - name: lv3 + size: "10%" + fs_type: xfs + - name: lv4 + size: "20%" + fs_type: xfs + - name: test_vg3 + disks: "{{ disk_list_3 }}" + volumes: + - name: lv5 + size: "30%" + fs_type: xfs + - name: lv6 + size: "25%" + fs_type: xfs + - name: lv7 + size: "10%" + fs_type: xfs + - name: lv8 + size: "10%" + fs_type: xfs + + - name: Run the snapshot role to create snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_percent_space_required: 15 + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: snapshot + + - name: Verify the snapshot LVs are created + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: check + + - name: Mount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the snapshot for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the snapshot for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + snapshot_lvm_mountpoint_create: true + + - name: Mount the origin for lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv6 + snapshot_lvm_mountpoint: /mnt/lv6_mp + snapshot_lvm_mountpoint_create: true + snapshot_lvm_mount_origin: true + + - name: Verify snapshot is mounted for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_verify_only: true + + + - name: Verify snapshot is mounted for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: ummountount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + snapshot_lvm_verify_only: true + + + - name: Verify snapshot is mounted for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + snapshot_lvm_verify_only: true + + - name: Verify origin is mounted lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv6 + snapshot_lvm_mountpoint: /mnt/lv6_mp + snapshot_lvm_mountpoint_create: true + snapshot_lvm_mount_origin: true + snapshot_lvm_verify_only: true + + - name: Umount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + + - name: Umount the snapshot for lv2 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv2 + snapshot_lvm_mountpoint: /mnt/lv2_mp + + - name: Umount the snapshot for lv7 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg3 + snapshot_lvm_lv: lv7 + snapshot_lvm_mountpoint: /mnt/lv7_mp + + - name: Umount the origin for lv6 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_action: umount + snapshot_lvm_mountpoint: /mnt/lv6_mp + + + - name: Run the snapshot role remove the snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: remove + + - name: Use the snapshot_lvm_verify option to make sure remove is done + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: remove + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent + - name: lv2 + state: absent + - name: test_vg2 + disks: "{{ disk_list_2 }}" + state: absent + volumes: + - name: lv3 + state: absent + - name: lv4 + state: absent + - name: test_vg3 + disks: "{{ disk_list_3 }}" + state: absent + volumes: + - name: lv5 + state: absent + - name: lv6 + state: absent + - name: lv7 + state: absent + - name: lv8 + state: absent diff --git a/tests/tests_umount_verify_fail.yml b/tests/tests_umount_verify_fail.yml new file mode 100644 index 0000000..8092a9d --- /dev/null +++ b/tests/tests_umount_verify_fail.yml @@ -0,0 +1,115 @@ +--- +- name: Verify umount action when fs still mounted + hosts: all + tasks: + - name: Run tests + block: + - name: Run the storage role to create test LVs + include_role: + name: fedora.linux_system_roles.storage + + - name: Get unused disks + include_tasks: get_unused_disk.yml + vars: + min_size: "1g" + min_return: 10 + + - name: Set disk list + set_fact: + disk_list_1: "{{ range(0, 3) | map('extract', unused_disks) | + list }}" + + - name: Create LVM logical volumes under volume groups + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + volumes: + - name: lv1 + size: "50%" + fs_type: xfs + + - name: Create snapshot for LV + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_percent_space_required: 15 + snapshot_lvm_all_vgs: true + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: snapshot + + - name: Mount the snapshot for LV + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_mountpoint_create: true + + - name: Verify snapshot is mounted for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: mount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_verify_only: true + + - name: Test failure of verifying umount when fs mounted + include_tasks: verify-role-failed.yml + vars: + __snapshot_failed_regex: device is mounted on mountpoint* + __snapshot_failed_msg: Role did not fail with fs still mounted error + __snapshot_failed_params: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + snapshot_lvm_verify_only: true + + - name: Umount the snapshot for lv1 + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: umount + snapshot_lvm_vg: test_vg1 + snapshot_lvm_lv: lv1 + snapshot_lvm_mountpoint: /mnt/lv1_mp + + - name: Remove the snapshot LVs + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_action: remove + + - name: Use the snapshot_lvm_verify option to make sure remove is done + include_role: + name: linux-system-roles.snapshot + vars: + snapshot_lvm_snapset_name: snapset1 + snapshot_lvm_verify_only: true + snapshot_lvm_action: remove + + always: + - name: Remove storage volumes + include_role: + name: fedora.linux_system_roles.storage + vars: + storage_safe_mode: false + storage_pools: + - name: test_vg1 + disks: "{{ disk_list_1 }}" + state: absent + volumes: + - name: lv1 + state: absent diff --git a/tests/verify-role-failed.yml b/tests/verify-role-failed.yml index c772c9c..8844891 100644 --- a/tests/verify-role-failed.yml +++ b/tests/verify-role-failed.yml @@ -19,6 +19,9 @@ snapshot_lvm_snapset_name: "{{ __snapshot_failed_params.get('snapshot_lvm_snapset_name') }}" + snapshot_lvm_set: "{{ + __snapshot_failed_params.get('__snapshot_lvm_set') + }}" snapshot_lvm_action: "{{ __snapshot_failed_params.get('snapshot_lvm_action') }}" @@ -31,8 +34,20 @@ snapshot_lvm_lv: "{{ __snapshot_failed_params.get('snapshot_lvm_lv') }}" - snapshot_lvm_set: "{{ - __snapshot_failed_params.get('__snapshot_lvm_set') + snapshot_lvm_mount_origin: "{{ + __snapshot_failed_params.get('snapshot_lvm_mount_origin') + }}" + snapshot_lvm_mountpoint_create: "{{ + __snapshot_failed_params.get('snapshot_lvm_mountpoint_create') + }}" + snapshot_lvm_unmount_multiple: "{{ + __snapshot_failed_params.get('snapshot_lvm_unmount_multiple') + }}" + snapshot_lvm_mountpoint: "{{ + __snapshot_failed_params.get('snapshot_lvm_mountpoint') + }}" + snapshot_lvm_mount_options: "{{ + __snapshot_failed_params.get('snapshot_lvm_mount_options') }}" - name: Unreachable task fail: diff --git a/vars/main.yml b/vars/main.yml index f88b54a..3b87a2c 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -20,6 +20,9 @@ __snapshot_required_facts_subsets: "{{ ['!all', '!min'] + __snapshot_cmd: "{{ 'snapshot.py ' ~ snapshot_lvm_action ~ ' ' ~ ('-a ' if snapshot_lvm_all_vgs else '') ~ ('-v ' if snapshot_lvm_verify_only else '') ~ + ('-O ' if snapshot_lvm_mount_origin else '') ~ + ('-c ' if snapshot_lvm_mountpoint_create else '') ~ + ('-A ' if snapshot_lvm_unmount_multiple else '') ~ ('-r ' if snapshot_lvm_percent_space_required else '') ~ ' ' ~ (snapshot_lvm_percent_space_required | quote if snapshot_lvm_percent_space_required else '') ~ ' ' ~ @@ -32,6 +35,12 @@ __snapshot_cmd: "{{ 'snapshot.py ' ~ snapshot_lvm_action ~ ' ' ~ ('-s ' if snapshot_lvm_snapset_name else '') ~ ' ' ~ (snapshot_lvm_snapset_name | quote if snapshot_lvm_snapset_name else '') ~ ' ' ~ + ('-s ' if snapshot_lvm_mount_options else '') ~ ' ' ~ + (snapshot_lvm_mount_options | quote + if snapshot_lvm_mount_options else '') ~ ' ' ~ + ('-m ' if snapshot_lvm_mountpoint else '') ~ ' ' ~ + (snapshot_lvm_mountpoint | quote + if snapshot_lvm_mountpoint else '') ~ ' ' ~ ('-g ' if snapshot_lvm_set else '') ~ ' ' ~ (snapshot_lvm_set | to_json | quote if snapshot_lvm_set else '') }}"