diff options
Diffstat (limited to 'extras/snap_scheduler/snap_scheduler.py')
| -rwxr-xr-x | extras/snap_scheduler/snap_scheduler.py | 525 | 
1 files changed, 525 insertions, 0 deletions
diff --git a/extras/snap_scheduler/snap_scheduler.py b/extras/snap_scheduler/snap_scheduler.py new file mode 100755 index 00000000000..fda0fd3310f --- /dev/null +++ b/extras/snap_scheduler/snap_scheduler.py @@ -0,0 +1,525 @@ +#!/usr/bin/env python +# +# Copyright (c) 2015 Red Hat, Inc. <http://www.redhat.com> +# This file is part of GlusterFS. +# +# This file is licensed to you under your choice of the GNU Lesser +# General Public License, version 3 or any later version (LGPLv3 or +# later), or the GNU General Public License, version 2 (GPLv2), in all +# cases as published by the Free Software Foundation. + +from __future__ import print_function +import subprocess +import os +import os.path +import logging +import argparse +import fcntl +import logging.handlers +from errno import EEXIST + + +SCRIPT_NAME = "snap_scheduler" +scheduler_enabled = False +log = logging.getLogger(SCRIPT_NAME) +GCRON_DISABLED = "/var/run/gluster/snaps/shared_storage/gcron_disabled" +GCRON_ENABLED = "/var/run/gluster/snaps/shared_storage/gcron_enabled" +GCRON_TASKS = "/var/run/gluster/snaps/shared_storage/glusterfs_snap_cron_tasks" +GCRON_CROND_TASK = "/etc/cron.d/glusterfs_snap_cron_tasks" +LOCK_FILE_DIR = "/var/run/gluster/snaps/shared_storage/lock_files/" +LOCK_FILE = "/var/run/gluster/snaps/shared_storage/lock_files/lock_file" +TMP_FILE = "/var/run/gluster/snaps/shared_storage/tmp_file" +GCRON_UPDATE_TASK = "/etc/cron.d/gcron_update_task" +tasks = {} +longest_field = 12 + + +def output(msg): +    print("%s: %s" % (SCRIPT_NAME, msg)) + + +def initLogger(): +    log.setLevel(logging.DEBUG) +    logFormat = "[%(asctime)s %(filename)s:%(lineno)s %(funcName)s] "\ +        "%(levelname)s %(message)s" +    formatter = logging.Formatter(logFormat) + +    sh = logging.handlers.SysLogHandler() +    sh.setLevel(logging.ERROR) +    sh.setFormatter(formatter) + +    process = subprocess.Popen(["gluster", "--print-logdir"], +                               stdout=subprocess.PIPE) +    logfile = os.path.join(process.stdout.read()[:-1], SCRIPT_NAME + ".log") + +    fh = logging.FileHandler(logfile) +    fh.setLevel(logging.DEBUG) +    fh.setFormatter(formatter) + +    log.addHandler(sh) +    log.addHandler(fh) + + +def scheduler_status(): +    success = False +    global scheduler_enabled +    try: +        f = os.path.realpath(GCRON_TASKS) +        if f != GCRON_ENABLED or not os.path.exists(GCRON_ENABLED): +            log.info("Snapshot scheduler is currently disabled.") +            scheduler_enabled = False +        else: +            log.info("Snapshot scheduler is currently enabled.") +            scheduler_enabled = True +        success = True +    except: +        log.error("Failed to enable snapshot scheduling. Error: " +                  "Failed to check the status of %s.", GCRON_DISABLED) + +    return success + + +def enable_scheduler(): +    ret = scheduler_status() +    if ret: +        if not scheduler_enabled: +            log.info("Enabling snapshot scheduler.") +            try: +                if os.path.exists(GCRON_DISABLED): +                    os.remove(GCRON_DISABLED) +                if os.path.lexists(GCRON_TASKS): +                    os.remove(GCRON_TASKS) +                try: +                    f = os.open(GCRON_ENABLED, os.O_CREAT | os.O_NONBLOCK, +                                0644) +                    os.close(f) +                except IOError as (errno, strerror): +                    log.error("Failed to open %s. Error: %s.", +                              GCRON_ENABLED, strerror) +                    ret = False +                    return ret +                os.symlink(GCRON_ENABLED, GCRON_TASKS) +                log.info("Snapshot scheduling is enabled") +                output("Snapshot scheduling is enabled") +            except IOError as (errno, strerror): +                log.error("Failed to enable snapshot scheduling. Error: %s.", +                          strerror) +                ret = False +        else: +            print_str = "Failed to enable snapshot scheduling. " \ +                        "Error: Snapshot scheduling is already enabled." +            log.error(print_str) +            output(print_str) + +    return ret + + +def disable_scheduler(): +    ret = scheduler_status() +    if ret: +        if scheduler_enabled: +            log.info("Disabling snapshot scheduler.") +            try: +                if os.path.exists(GCRON_DISABLED): +                    os.remove(GCRON_DISABLED) +                if os.path.lexists(GCRON_TASKS): +                    os.remove(GCRON_TASKS) +                f = os.open(GCRON_DISABLED, os.O_CREAT, 0644) +                os.close(f) +                os.symlink(GCRON_DISABLED, GCRON_TASKS) +                log.info("Snapshot scheduling is disabled") +                output("Snapshot scheduling is disabled") +            except IOError as (errno, strerror): +                log.error("Failed to disable snapshot scheduling. Error: %s.", +                          strerror) +                ret = False +        else: +            print_str = "Failed to disable scheduling. " \ +                        "Error: Snapshot scheduling is already disabled." +            log.error(print_str) +            output(print_str) + +    return ret + + +def load_tasks_from_file(): +    global tasks +    global longest_field +    try: +        with open(GCRON_ENABLED, 'r') as f: +            for line in f: +                line = line.rstrip('\n') +                if not line: +                    break +                line = line.split("gcron.py") +                schedule = line[0].split("root")[0].rstrip(' ') +                line = line[1].split(" ") +                volname = line[1] +                jobname = line[2] +                longest_field = max(longest_field, len(jobname), len(volname), +                                    len(schedule)) +                tasks[jobname] = schedule+":"+volname +        ret = True +    except IOError as (errno, strerror): +        log.error("Failed to open %s. Error: %s.", GCRON_ENABLED, strerror) +        ret = False + +    return ret + + +def list_schedules(): +    ret = scheduler_status() +    if ret: +        if scheduler_enabled: +            log.info("Listing snapshot schedules.") +            ret = load_tasks_from_file() +            if ret: +                if len(tasks) == 0: +                    output("No snapshots scheduled") +                else: +                    jobname = "JOB_NAME".ljust(longest_field+5) +                    schedule = "SCHEDULE".ljust(longest_field+5) +                    operation = "OPERATION".ljust(longest_field+5) +                    volname = "VOLUME NAME".ljust(longest_field+5) +                    hyphens = "".ljust((longest_field+5) * 4, '-') +                    print(jobname+schedule+operation+volname) +                    print(hyphens) +                    for key in sorted(tasks): +                        jobname = key.ljust(longest_field+5) +                        schedule = tasks[key].split(":")[0].ljust( +                            longest_field + 5) +                        volname = tasks[key].split(":")[1].ljust( +                            longest_field + 5) +                        operation = "Snapshot Create".ljust(longest_field+5) +                        print(jobname+schedule+operation+volname) +            else: +                log.error("Failed to load tasks from %s.", GCRON_ENABLED) +        else: +            print_str = "Failed to list snapshot schedules. " \ +                        "Error: Snapshot scheduling is currently disabled." +            log.error(print_str) +            output(print_str) + +    return ret + + +def write_tasks_to_file(): +    ret = False +    try: +        with open(TMP_FILE, "w", 0644) as f: +            # If tasks is empty, just create an empty tmp file +            if len(tasks) != 0: +                for key in sorted(tasks): +                    jobname = key +                    schedule = tasks[key].split(":")[0] +                    volname = tasks[key].split(":")[1] +                    f.write("%s root PATH=$PATH:/usr/local/sbin:/usr/sbin " +                            "gcron.py %s %s\n" % (schedule, volname, jobname)) +                f.write("\n") +                f.flush() +                os.fsync(f.fileno()) +    except IOError as (errno, strerror): +        log.error("Failed to open %s. Error: %s.", TMP_FILE, strerror) +        ret = False +        return ret + +    os.rename(TMP_FILE, GCRON_ENABLED) +    ret = True + +    return ret + + +def add_schedules(jobname, schedule, volname): +    ret = scheduler_status() +    if ret: +        if scheduler_enabled: +            log.info("Adding snapshot schedules.") +            ret = load_tasks_from_file() +            if ret: +                if jobname in tasks: +                    print_str = ("%s already exists in schedule. Use " +                                 "'edit' to modify %s" % (jobname, jobname)) +                    log.error(print_str) +                    output(print_str) +                else: +                    tasks[jobname] = schedule + ":" + volname +                    ret = write_tasks_to_file() +                    if ret: +                        # Create a LOCK_FILE for the job +                        job_lockfile = LOCK_FILE_DIR + jobname +                        try: +                            f = os.open(job_lockfile, +                                        os.O_CREAT | os.O_NONBLOCK, 0644) +                            os.close(f) +                        except IOError as (errno, strerror): +                            log.error("Failed to open %s. Error: %s.", +                                      job_lockfile, strerror) +                            ret = False +                            return ret +                        log.info("Successfully added snapshot schedule %s" +                                 % jobname) +                        output("Successfully added snapshot schedule") +            else: +                log.error("Failed to load tasks from %s.", GCRON_ENABLED) +        else: +            print_str = ("Failed to add snapshot schedule. " +                         "Error: Snapshot scheduling is currently disabled.") +            log.error(print_str) +            output(print_str) + +    return ret + + +def delete_schedules(jobname): +    ret = scheduler_status() +    if ret: +        if scheduler_enabled: +            log.info("Delete snapshot schedules.") +            ret = load_tasks_from_file() +            if ret: +                if jobname in tasks: +                    del tasks[jobname] +                    ret = write_tasks_to_file() +                    if ret: +                        # Delete the LOCK_FILE for the job +                        job_lockfile = LOCK_FILE_DIR+jobname +                        try: +                            os.remove(job_lockfile) +                        except IOError as (errno, strerror): +                            log.error("Failed to open %s. Error: %s.", +                                      job_lockfile, strerror) +                        log.info("Successfully deleted snapshot schedule %s" +                                 % jobname) +                        output("Successfully deleted snapshot schedule") +                else: +                    print_str = ("Failed to delete %s. Error: No such " +                                 "job scheduled" % jobname) +                    log.error(print_str) +                    output(print_str) +            else: +                log.error("Failed to load tasks from %s.", GCRON_ENABLED) +        else: +            print_str = ("Failed to delete snapshot schedule. " +                         "Error: Snapshot scheduling is currently disabled.") +            log.error(print_str) +            output(print_str) + +    return ret + + +def edit_schedules(jobname, schedule, volname): +    ret = scheduler_status() +    if ret: +        if scheduler_enabled: +            log.info("Editing snapshot schedules.") +            ret = load_tasks_from_file() +            if ret: +                if jobname in tasks: +                    tasks[jobname] = schedule+":"+volname +                    ret = write_tasks_to_file() +                    if ret: +                        log.info("Successfully edited snapshot schedule %s" +                                 % jobname) +                        output("Successfully edited snapshot schedule") +                else: +                    print_str = ("Failed to edit %s. Error: No such " +                                 "job scheduled" % jobname) +                    log.error(print_str) +                    output(print_str) +            else: +                log.error("Failed to load tasks from %s.", GCRON_ENABLED) +        else: +            print_str = ("Failed to edit snapshot schedule. " +                         "Error: Snapshot scheduling is currently disabled.") +            log.error(print_str) +            output(print_str) + +    return ret + + +def initialise_scheduler(): +    try: +        with open("/tmp/crontab", "w+", 0644) as f: +            updater = ("* * * * * root PATH=$PATH:/usr/local/sbin:" +                       "/usr/sbin gcron.py --update\n") +            f.write("%s\n" % updater) +            f.flush() +            os.fsync(f.fileno()) +    except IOError as (errno, strerror): +        log.error("Failed to open /tmp/crontab. Error: %s.", strerror) +        ret = False +        return ret + +    os.rename("/tmp/crontab", GCRON_UPDATE_TASK) + +    if not os.path.lexists(GCRON_TASKS): +        try: +            f = open(GCRON_TASKS, "w", 0644) +            f.close() +        except IOError as (errno, strerror): +            log.error("Failed to open %s. Error: %s.", GCRON_TASKS, strerror) +            ret = False +            return ret + +    if os.path.lexists(GCRON_CROND_TASK): +        os.remove(GCRON_CROND_TASK) + +    os.symlink(GCRON_TASKS, GCRON_CROND_TASK) + +    log.info("Successfully inited snapshot scheduler for this node") +    output("Successfully inited snapshot scheduler for this node") + +    ret = True +    return ret + + +def perform_operation(args): +    ret = False + +    # Initialise snapshot scheduler on local node +    if args.action == "init": +        ret = initialise_scheduler() +        if not ret: +            output("Failed to initialise snapshot scheduling") +        else: +            return ret + +    # Check if the symlink to GCRON_TASKS is properly set in the shared storage +    if (not os.path.lexists(GCRON_UPDATE_TASK) or +       not os.path.lexists(GCRON_CROND_TASK) or +       os.readlink(GCRON_CROND_TASK) != GCRON_TASKS): +        print_str = ("Please run 'snap_scheduler.py' init to initialise " +                     "the snap scheduler for the local node.") +        log.error(print_str) +        output(print_str) +        return + +    # Check status of snapshot scheduler. +    if args.action == "status": +        ret = scheduler_status() +        if ret: +            if scheduler_enabled: +                output("Snapshot scheduling status: Enabled") +            else: +                output("Snapshot scheduling status: Disabled") +        else: +            output("Failed to check status of snapshot scheduler") +        return ret + +    # Enable snapshot scheduler +    if args.action == "enable": +        ret = enable_scheduler() +        if not ret: +            output("Failed to enable snapshot scheduling") +        else: +            subprocess.Popen(["touch", "-h", GCRON_TASKS]) +        return ret + +    # Disable snapshot scheduler +    if args.action == "disable": +        ret = disable_scheduler() +        if not ret: +            output("Failed to disable snapshot scheduling") +        else: +            subprocess.Popen(["touch", "-h", GCRON_TASKS]) +        return ret + +    # List snapshot schedules +    if args.action == "list": +        ret = list_schedules() +        if not ret: +            output("Failed to list snapshot schedules") +        return ret + +    # Add snapshot schedules +    if args.action == "add": +        ret = add_schedules(args.jobname, args.schedule, args.volname) +        if not ret: +            output("Failed to add snapshot schedule") +        else: +            subprocess.Popen(["touch", "-h", GCRON_TASKS]) +        return ret + +    # Delete snapshot schedules +    if args.action == "delete": +        ret = delete_schedules(args.jobname) +        if not ret: +            output("Failed to delete snapshot schedule") +        else: +            subprocess.Popen(["touch", "-h", GCRON_TASKS]) +        return ret + +    # Edit snapshot schedules +    if args.action == "edit": +        ret = edit_schedules(args.jobname, args.schedule, args.volname) +        if not ret: +            output("Failed to edit snapshot schedule") +        else: +            subprocess.Popen(["touch", "-h", GCRON_TASKS]) +        return ret + +    return ret + + +def main(): +    initLogger() +    parser = argparse.ArgumentParser() +    subparsers = parser.add_subparsers(dest="action") +    subparsers.add_parser('init', +                          help="Initialise the node for snapshot scheduling") + +    subparsers.add_parser("status", +                          help="Check if snapshot scheduling is " +                          "enabled or disabled") +    subparsers.add_parser("enable", +                          help="Enable snapshot scheduling") +    subparsers.add_parser("disable", +                          help="Disable snapshot scheduling") +    subparsers.add_parser("list", +                          help="List snapshot schedules") +    parser_add = subparsers.add_parser("add", +                                       help="Add snapshot schedules") +    parser_add.add_argument("jobname", help="Job Name") +    parser_add.add_argument("schedule", help="Schedule") +    parser_add.add_argument("volname", help="Volume Name") + +    parser_delete = subparsers.add_parser("delete", +                                          help="Delete snapshot schedules") +    parser_delete.add_argument("jobname", help="Job Name") +    parser_edit = subparsers.add_parser("edit", +                                        help="Edit snapshot schedules") +    parser_edit.add_argument("jobname", help="Job Name") +    parser_edit.add_argument("schedule", help="Schedule") +    parser_edit.add_argument("volname", help="Volume Name") + +    args = parser.parse_args() + +    if not os.path.exists(LOCK_FILE_DIR): +        try: +            os.makedirs(LOCK_FILE_DIR) +        except IOError as (errno, strerror): +            if errno != EEXIST: +                log.error("Failed to create %s : %s", LOCK_FILE_DIR, strerror) +                output("Failed to create %s. Error: %s" +                       % (LOCK_FILE_DIR, strerror)) + +    try: +        f = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR | os.O_NONBLOCK, 0644) +        try: +            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) +            perform_operation(args) +            fcntl.flock(f, fcntl.LOCK_UN) +        except IOError as (errno, strerror): +            log.info("%s is being processed by another agent.", LOCK_FILE) +            output("Another snap_scheduler command is running. " +                   "Please try again after some time.") +        os.close(f) +    except IOError as (errno, strerror): +        log.error("Failed to open %s : %s", LOCK_FILE, strerror) +        output("Failed to open %s. Error: %s" % (LOCK_FILE, strerror)) + +    return + + +if __name__ == "__main__": +    main()  | 
