Пример #1
0
class ListBackups(Command):
    """${cmd_usage}

    ${cmd_option_list}

    Lists available backups
    """
    name = 'list-backups'
    aliases = ['lb']
    description = 'List available backups'
    options = [
        option('-v', '--verbose', action='store_true', help="Verbose output")
    ]

    def print_table(self, table):
        header = table[0]
        rest = table[1:]
        fmt = "%-28s %-9s %-16s %s"
        print fmt % tuple(header)
        print "-" * 80
        for row in rest:
            print fmt % tuple(row)

    def run(self, cmd, opts):
        backup_list = [x for x in spool.list_backups()]
        if not backup_list:
            print "No backups"
            return 0

        backupsets_seen = []
        for backup in backup_list:
            if backup.backupset not in backupsets_seen:
                backupsets_seen.append(backup.backupset)
                print "Backupset[%s]:" % (backup.backupset)
            # Read the backup.conf
            backup.load_config()
            plugin_name = backup.config.get('holland:backup', {})['plugin']
            if not plugin_name:
                print "Skipping broken backup: %s" % backup.name
                continue
            print "\t%s" % backup.name
            if opts.verbose:
                print "\t", backup.info()
                plugin = load_backup_plugin(plugin_name)
                plugin = plugin(backup.backupset, backup.config, backup.path)
                if hasattr(plugin, 'info'):
                    plugin_info = plugin.info()
                    import re
                    rec = re.compile(r'^', re.M)
                    print rec.sub('\t\t', plugin_info)

        return 0
Пример #2
0
class Restore(Command):
    """${cmd_usage}

    Restore data from an existing Holland backup

    The actual restore is delegated to the backup plugin that
    created the backup. 

    Example:
    holland ${cmd_name} some-backup --help

    # Example restore for a mysqldump based backup
    holland ${cmd_name} some-backup --table mysql.proc 

    ${cmd_option_list}
        
    """

    name = 'restore'

    aliases = [
        're'
    ]

    options = [
        option('--dry-run', '-n', action='store_true',
                help="Print what restore actually would do without actually running the restore")
    ]

    description = 'Restore data from an existing Holland Backup'
    def __init__(self):
        Command.__init__(self)
        self.optparser.disable_interspersed_args()

    def run(self, cmd, opts, backup_name, *restore_options):
        backup = spool.find_backup(backup_name)
        if not backup:
            logging.error("No backup found named %s", backup_name)
            return 1
        config = backup.config
        plugin_name = config.get('holland:backup', {}).get('plugin')
        plugin = load_first_entrypoint('holland.restore', plugin_name)(backup)
        plugin.dispatch([plugin_name]  + list(restore_options))
        return 1
Пример #3
0
class Purge(Command):
    """${cmd_usage}

    Purge the requested job runs 

    ${cmd_option_list}
        
    """

    name = 'purge'

    aliases = ['pg']

    options = [
        option('--dry-run',
               '-n',
               action='store_true',
               dest='force',
               default=False,
               help="Print what would be purged without actually purging"),
        option('--all',
               '-a',
               action='store_true',
               default=False,
               help="When purging a backupset purge everything rather than "
               "using the retention count from the active configuration"),
        option(
            '--force',
            '-f',
            action='store_true',
            default=False,
            help="Execute the purge (disable dry-run). Alias for --execute"),
        option('--execute',
               action='store_true',
               dest='force',
               help="Execute the purge (disable dry-run)")
    ]

    description = 'Purge the requested job runs'

    def run(self, cmd, opts, *backups):
        error = 0

        if not backups:
            LOG.info("No backupsets specified - using backupsets from %s",
                     hollandcfg.filename)
            backups = hollandcfg.lookup('holland.backupsets')

        if not backups:
            LOG.warn("Nothing to purge")
            return 0

        if not opts.force:
            LOG.warn(
                "Running in dry-run mode.  Use --execute to do a real purge.")

        for name in backups:
            if '/' not in name:
                backupset = spool.find_backupset(name)
                if not backupset:
                    LOG.error("Failed to find backupset '%s'", name)
                    error = 1
                    continue
                purge_backupset(backupset, opts.force, opts.all)
            else:
                backup = spool.find_backup(name)
                if not backup:
                    LOG.error("Failed to find single backup '%s'", name)
                    error = 1
                    continue
                purge_backup(backup, opts.force)
                if opts.force:
                    spool.find_backupset(backup.backupset).update_symlinks()
        return error
Пример #4
0
class MkConfig(Command):
    """${cmd_usage}

    Generate a config file for a backup
    plugin.

    ${cmd_option_list}

    """

    name = 'mk-config'

    aliases = [
        'mc'
    ]

    options = [
        option('--name',
                help='Name of the backupset'),
        option('--edit', action='store_true',
                help='Edit the generated config'),
        option('--provider', action='store_true',
                help='Generate a provider config'),
        option('--file', '-f',
                help='Save the final config to the specified file'),
        option('--minimal', '-m', action='store_true', default=False,
               help="Do not include comment from a backup "
                    "plugin's configspec"),
    ]

    description = 'Generate a config file for a backup plugin'


    # After initial validation:
    #   run through and flag required parameters with a 'REQUIRED' comment
    #   run through and comment out default=None parameters
    def _cleanup_config(self, config, skip_comments=False):
        errors = config.validate(validator, preserve_errors=True,copy=True)
        # First flag any required parameters
        for entry in flatten_errors(config, errors):
            section_list, key, error = entry
            section_name, = section_list
            if error is False:
                config[section_name][key] = ''
                config[section_name].comments[key].append('REQUIRED')
            elif error:
                print >>sys.stderr, "Bad configspec generated error", error

        pending_comments = []
        for section in list(config):
            if pending_comments:
                if skip_comments:
                    comments = []
                else:
                    comments = config.comments.get(section, [])
                comments = pending_comments + comments
                config.comments[section] = comments
                del pending_comments[:]
            for idx, (key, value) in enumerate(config[section].items()):
                if value is None:
                    if not skip_comments:
                        pending_comments.extend(config[section].comments.get(key, []))
                    pending_comments.append('%s = "" # no default' % key)
                    del config[section][key]
                else:
                    if skip_comments:
                        del config[section].comments[key][:]
                    if pending_comments:
                        if skip_comments:
                            comments = []
                        else:
                            comments = config[section].comments.get(key, [])
                        comments = pending_comments + comments
                        config[section].comments[key] = comments
                        del pending_comments[:]
                    if value is True or value is False:
                        config[section][key] = ['no','yes'][value]

        if pending_comments:
            if skip_comments:
                config.final_comment = pending_comments
            else:
                config.final_comment = pending_comments + config.final_comment

        # drop initial whitespace
        config.initial_comment = []
        # insert a blank between [holland:backup] and first section
        try:
            config.comments[config.sections[1]].insert(0, '')
        except IndexError:
            pass

    def run(self, cmd, opts, plugin_type):
        if opts.name and opts.provider:
            print >>sys.stderr, "Can't specify a name for a global provider config"
            return 1

        try:
            plugin_cls = load_first_entrypoint('holland.backup', plugin_type)
        except PluginLoadError, exc:
            logging.info("Failed to load backup plugin %r: %s",
                         plugin_type, exc)
            return 1

        try:
            cfgspec = sys.modules[plugin_cls.__module__].CONFIGSPEC
        except:
            print >>sys.stderr, "Could not load config-spec from plugin %r" % plugin_type
            return 1

        base_config = """
        [holland:backup]
        plugin                  = ""
        backups-to-keep         = 1
        auto-purge-failures     = yes
        purge-policy            = after-backup
        """.lstrip().splitlines()
        cfg = ConfigObj(base_config, configspec=cfgspec, list_values=True,stringify=True)
        cfg['holland:backup']['plugin'] = plugin_type
        self._cleanup_config(cfg, skip_comments=opts.minimal)

        if opts.edit:
            done = False
            editor = _find_editor()
            if not editor:
                print >>sys.stderr, "Could not find a valid editor"
                return 1

            tmpfileobj = tempfile.NamedTemporaryFile()
            cfg.filename = tmpfileobj.name
            cfg.write()
            while not done:
                status = subprocess.call([editor, cfg.filename])
                if status != 0:
                    if not confirm("Editor exited with non-zero status[%d]. "
                                   "Would you like to retry?" % status):
                        print >>sys.stderr, "Aborting"
                        return 1
                    else:
                        continue
                try:
                    cfg.reload()
                except ParseError, exc:
                    print >>sys.stderr, "%s : %s" % \
                    (exc.msg, exc.line)
                else:
                    errors = cfg.validate(validator,preserve_errors=True)
                    if errors is True:
                        done = True
                        continue
                    else:
                        _report_errors(cfg, errors)

                if not confirm('There were configuration errors. Continue?'):
                    print >>sys.stderr, "Aborting"
                    return 1
            tmpfileobj.close()
Пример #5
0
class Backup(Command):
    """${cmd_usage}

    Backup the specified backupsets or all
    active backupsets specified in holland.conf

    ${cmd_option_list}

    """

    name = 'backup'

    aliases = [
        'bk'
    ]

    options = [
        option('--abort-immediately', action='store_true',
                help="Abort on the first backupset that fails."),
        option('--dry-run', '-n', action='store_true',
                help="Print backup commands without executing them."),
        option('--no-lock', '-f', action='store_true', default=False,
                help="Run even if another copy of Holland is running.")
    ]

    description = 'Run backups for active backupsets'

    def run(self, cmd, opts, *backupsets):
        if not backupsets:
            backupsets = hollandcfg.lookup('holland.backupsets')

        # strip empty items from backupsets list
        backupsets = [name for name in backupsets if name]

        if not backupsets:
            LOG.info("Nothing to backup")
            return 1

        runner = BackupRunner(spool)

        # dry-run implies no-lock
        if opts.dry_run:
            opts.no_lock = True

        # don't purge if doing a dry-run, or when simultaneous backups may be running
        if not opts.no_lock:
            purge_mgr = PurgeManager()

            runner.register_cb('before-backup', purge_mgr)
            runner.register_cb('after-backup', purge_mgr)
            runner.register_cb('failed-backup', purge_backup)

        runner.register_cb('after-backup', report_low_space)

        if not opts.dry_run:
            runner.register_cb('before-backup', call_hooks)
            runner.register_cb('after-backup', call_hooks)
            runner.register_cb('failed-backup', call_hooks)

        error = 1
        LOG.info("--- Starting %s run ---", opts.dry_run and 'dry' or 'backup')
        for name in backupsets:
            try:
                config = hollandcfg.backupset(name)
                # ensure we have at least an empty holland:backup section
                config.setdefault('holland:backup', {})
            except (SyntaxError, IOError), exc:
                LOG.error("Could not load backupset '%s': %s", name, exc)
                break

            if not opts.no_lock:
                lock = Lock(config.filename)
                try:
                    lock.acquire()
                    LOG.debug("Set advisory lock on %s", lock.path)
                except LockError:
                    LOG.debug("Unable to acquire advisory lock on %s",
                              lock.path)
                    LOG.error("Another holland backup process is already "
                              "running backupset '%s'. Aborting.", name)
                    break

            try:
                try:
                    runner.backup(name, config, opts.dry_run)
                except BackupError, exc:
                    LOG.error("Backup failed: %s", exc.args[0])
                    break
                except ConfigError, exc:
                    break