bin/pvl.backup-snapshot
changeset 57 52a4be76e85a
parent 55 95c86df4807a
child 59 b9c014c353a3
equal deleted inserted replaced
56:ae4b24ae4f70 57:52a4be76e85a
    67         help="Clean out unused snapshots (those not linked to)")
    67         help="Clean out unused snapshots (those not linked to)")
    68 
    68 
    69     parser.add_option('--clean',             action='store_true',
    69     parser.add_option('--clean',             action='store_true',
    70         help="Clean out both intervals and snapshots")
    70         help="Clean out both intervals and snapshots")
    71 
    71 
    72     parser.add_option('-n', '--dry-run',    action='store_true',
    72     parser.add_option('-n', '--noop',       action='store_true',
    73         help="Don't actually clean anything")
    73         help="Don't actually clean anything")
    74 
    74 
    75     #
    75     #
    76     parser.add_option('-c', '--config',     metavar='FILE/DIR', action='append',    # multi
    76     parser.add_option('-c', '--config',     metavar='FILE/DIR', action='append',    # multi
    77         help="Load configuration file(s)")
    77         help="Load configuration file(s)")
   341 
   341 
   342         # parse source -> rsync.RSyncServer
   342         # parse source -> rsync.RSyncServer
   343         source_path = source
   343         source_path = source
   344         source = rsync.parse_source(source, lvm_opts=lvm_options)
   344         source = rsync.parse_source(source, lvm_opts=lvm_options)
   345 
   345 
   346         log.info("parse source: %r -> %s", source_path, source)
   346         log.debug("parse source: %r -> %s", source_path, source)
   347 
   347 
   348         # global defaults
   348         # global defaults
   349         _rsync_options = dict(options.rsync_options)
   349         _rsync_options = dict(options.rsync_options)
   350 
   350 
   351         if rsync_options :
   351         if rsync_options :
   420         temp_path = os.path.join(self.snapshots_dir, 'tmp')
   420         temp_path = os.path.join(self.snapshots_dir, 'tmp')
   421 
   421 
   422         if os.path.exists(temp_path) :
   422         if os.path.exists(temp_path) :
   423             raise Exception("Old temp snapshot dir remains, please clean up: {path}".format(path=temp_path))
   423             raise Exception("Old temp snapshot dir remains, please clean up: {path}".format(path=temp_path))
   424 
   424 
   425         log.info("Perform main snapshot: %s -> %s", self.source, snapshot_path)
   425         # link-dest from current?
   426 
       
   427         # build rsync options
       
   428         opts = dict(self.rsync_options)
       
   429 
       
   430         if os.path.exists(self.current_path) :
   426         if os.path.exists(self.current_path) :
   431             # real path to target
   427             # real path to target
   432             target = os.readlink(self.current_path)
   428             target = os.readlink(self.current_path)
   433             target_path = os.path.join(os.path.dirname(self.current_path), target)
   429             target_path = os.path.join(os.path.dirname(self.current_path), target)
   434             target_abs = os.path.abspath(target_path)
   430 
   435 
   431             log.debug("%s: link-dest: %s", self, target_path)
   436             log.info("Using current -> %s as base", target_path)
       
   437 
   432 
   438             # use as link-dest base; hardlinks unchanged files; target directory must be empty
   433             # use as link-dest base; hardlinks unchanged files; target directory must be empty
   439             # rsync links absolute paths..
   434             link_dest = target_path
   440             opts['link-dest'] = target_abs
   435 
   441 
   436         else :
       
   437             link_dest = None
       
   438         
       
   439         # log
       
   440         log.info("%s: %s -> %s <- %s", self, self.source, snapshot_path, link_dest)
       
   441 
       
   442         # build rsync options
       
   443         opts = dict(self.rsync_options)
       
   444 
       
   445         # rsync links absolute paths..
       
   446         opts['link-dest'] = os.path.abspath(link_dest)
       
   447         
       
   448         # to tempdir
   442         log.debug("rsync %s -> %s", self.source, temp_path)
   449         log.debug("rsync %s -> %s", self.source, temp_path)
   443 
   450 
   444         # run the rsync.RSyncServer; None as a placeholder will get replaced with the actual source
   451         # run the rsync.RSyncServer; None as a placeholder will get replaced with the actual source
   445         self.source.execute(invoke.optargs(**opts), srcdst=(None, temp_path))
   452         self.source.execute(invoke.optargs(**opts), srcdst=(None, temp_path))
   446 
   453 
   448         log.debug("rename %s -> %s", temp_path, snapshot_path)
   455         log.debug("rename %s -> %s", temp_path, snapshot_path)
   449         os.rename(temp_path, snapshot_path)
   456         os.rename(temp_path, snapshot_path)
   450 
   457 
   451         return snapshot_name
   458         return snapshot_name
   452 
   459 
   453     def update_interval (self, options, interval, now, snapshot_name) :
   460     def interval (self, options, interval, now, snapshot_name) :
   454         """
   461         """
   455             Update given <interval>/... links for this target, using the given new snapshot
   462             Update given <interval>/... links for this target, using the given new snapshot
   456         """
   463         """
   457 
   464 
   458         dir_path = os.path.join(self.path, interval.name)
   465         dir_path = os.path.join(self.path, interval.name)
   459 
   466 
   460         if not os.path.exists(dir_path) :
   467         if not os.path.exists(dir_path) :
   461             log.warn("Creating interval dir: %s", dir_path)
   468             log.warn("%s/%s: Creating interval dir: %s", self, interval, dir_path)
   462             os.mkdir(dir_path)
   469             os.mkdir(dir_path)
   463         
   470         
   464         
   471         
   465         # name
   472         # name
   466         if interval.format is None :
   473         if interval.format is None :
   483 
   490 
   484         # already there?
   491         # already there?
   485         if os.path.exists(path) :
   492         if os.path.exists(path) :
   486             target = os.readlink(path)
   493             target = os.readlink(path)
   487 
   494 
   488             log.info("%s: Keeping existing: %s -> %s", interval, name, target)
   495             log.debug("%s: Keeping existing: %s -> %s", interval, name, target)
   489 
   496 
   490         else :
   497         else :
   491             # update
   498             # update
   492             target = os.path.join('..', 'snapshots', snapshot_name)
   499             target = os.path.join('..', 'snapshots', snapshot_name)
   493 
   500 
   494             log.info("%s: Updating: %s -> %s", interval, name, target)
   501             log.info("%s/%s: %s -> %s", self, interval, name, target)
   495             log.debug("%s -> %s", path, target)
   502             log.debug("%s -> %s", path, target)
   496 
   503 
   497             os.symlink(target, path)
   504             os.symlink(target, path)
   498 
   505 
   499 
   506 
   504 
   511 
   505         # path
   512         # path
   506         dir_path = os.path.join(self.path, interval.name)
   513         dir_path = os.path.join(self.path, interval.name)
   507 
   514 
   508         if not os.path.exists(dir_path) :
   515         if not os.path.exists(dir_path) :
   509             log.warn("%s: Skipping, no interval dir: %s", interval, dir_path)
   516             log.warn("%s/%s: Skipping, no interval dir: %s", self, interval, dir_path)
   510             return
   517             return
   511 
   518 
   512         # configured
   519         # configured
   513         keep = interval.keep
   520         keep = interval.keep
   514 
   521 
   515         if not keep :
   522         if not keep :
   516             log.info("%s: Zero keep given, not cleaning up anything", interval)
   523             log.info("%s/%s: Zero keep given, not cleaning up anything", self, interval)
   517             return
   524             return
   518 
   525 
   519         # items to clean?
   526         # items to clean?
   520         items = os.listdir(dir_path)
   527         items = os.listdir(dir_path)
   521 
   528 
   522         # sort newest -> oldest
   529         # sort newest -> oldest
   523         items.sort(reverse=True)
   530         items.sort(reverse=True)
   524 
   531 
   525         log.info("%s: Have %d / %d items", interval, len(items), keep)
   532         log.debug("%s/%s: Have %d / %d items", self, interval, len(items), keep)
   526         log.debug("%s: items: %s", interval, ' '.join(items))
   533         log.debug("%s: items: %s", interval, ' '.join(items))
   527 
   534 
   528         if len(items) > keep :
   535         if len(items) > keep :
   529             # select oldest ones
   536             # select oldest ones
   530             clean = items[keep:]
   537             clean = items[keep:]
   531 
   538 
   532             log.info("%s: Cleaning out %d items", interval, len(clean))
   539             log.debug("%s/%s: cleaning out: %s", self, interval, ' '.join(clean))
   533             log.debug("%s: cleaning out: %s", interval, ' '.join(clean))
       
   534 
   540 
   535             for item in clean :
   541             for item in clean :
   536                 path = os.path.join(dir_path, item)
   542                 path = os.path.join(dir_path, item)
   537 
   543 
   538                 log.info("%s: Clean: %s", interval, path)
   544                 log.info("%s/%s: %s", self, interval, path)
   539 
   545 
   540                 if not options.dry_run :
   546                 if not options.noop :
   541                     log.debug("rmtree: %s", path)
   547                     log.debug("rmtree: %s", path)
   542                     os.unlink(path)
   548                     os.unlink(path)
   543                 else :
   549                 else :
   544                     log.debug("dryrun: %s", path)
   550                     log.debug("dryrun: %s", path)
   545 
   551 
   580         ## compare
   586         ## compare
   581         used = snapshots & found
   587         used = snapshots & found
   582         unused = snapshots - found
   588         unused = snapshots - found
   583         broken = found - snapshots
   589         broken = found - snapshots
   584 
   590 
   585         log.info("Found used=%d, unused=%d, broken=%d snapshot symlinks", len(used), len(unused), len(broken))
   591         log.debug("%s: found used=%d, unused=%d, broken=%d snapshot symlinks", self, len(used), len(unused), len(broken))
   586         log.debug("used=%s, unused=%s", used, unused)
   592         log.debug("used=%s, unused=%s", used, unused)
   587 
   593 
   588         if broken :
   594         if broken :
   589             log.warn("Found broken symlinks to snapshots: %s", ' '.join(broken))
   595             log.warn("%s: Found broken symlinks to snapshots: %s", self, ' '.join(broken))
   590         
   596         
   591         if unused :
   597         if unused :
   592             log.info("Cleaning out %d unused snapshots:", len(unused))
   598             log.debug("%s: Cleaning out %d unused snapshots:", self, len(unused))
   593 
   599 
   594             for name in unused :
   600             for name in unused :
   595                 path = os.path.join(snapshots_path, name)
   601                 path = os.path.join(snapshots_path, name)
   596 
   602 
   597                 log.info("Clean: %s", name)
   603                 log.info("%s: %s", self, name)
   598 
   604 
   599                 if not options.dry_run :
   605                 if not options.noop :
   600                     log.debug("rmtree: %s", path)
   606                     log.debug("rmtree: %s", path)
   601 
   607 
   602                     # nuke
   608                     # nuke
   603                     shutil.rmtree(path)
   609                     shutil.rmtree(path)
   604 
   610 
   612 
   618 
   613         # initial rsync
   619         # initial rsync
   614         snapshot_name = self.snapshot(options, now)
   620         snapshot_name = self.snapshot(options, now)
   615 
   621 
   616         # update current
   622         # update current
   617         log.info("Updating current -> %s", snapshot_name)
   623         log.debug("Updating current -> %s", snapshot_name)
   618 
   624 
   619         if os.path.islink(self.current_path) :
   625         if os.path.islink(self.current_path) :
   620             # replace
   626             # replace
   621             os.unlink(self.current_path)
   627             os.unlink(self.current_path)
   622 
   628 
   628         """
   634         """
   629             Run our intervals.
   635             Run our intervals.
   630         """
   636         """
   631 
   637 
   632         if not self.intervals :
   638         if not self.intervals :
   633             log.info("No intervals given; not running any")
   639             log.warn("No intervals given")
   634 
   640 
   635         else :
   641         else :
   636             # maintain intervals
   642             # maintain intervals
   637             log.info("Updating %d intervals...", len(self.intervals))
   643             log.debug("Updating %d intervals...", len(self.intervals))
   638 
   644 
   639             for interval in self.intervals :
   645             for interval in self.intervals :
   640                 log.debug("%s", interval)
   646                 log.debug("%s", interval)
   641 
   647 
   642                 log.info("Updating interval: %s", interval)
       
   643 
       
   644                 # update
   648                 # update
   645                 self.update_interval(options, interval, now, snapshot_name)
   649                 self.interval(options, interval, now, snapshot_name)
   646 
   650 
   647     def run (self, options) :
   651     def run (self, options) :
   648         """
   652         """
   649             Execute
   653             Execute
   650         """
   654         """
   653         self.prepare(options)
   657         self.prepare(options)
   654 
   658 
   655         # clean intervals?
   659         # clean intervals?
   656         if options.clean_intervals:
   660         if options.clean_intervals:
   657             for interval in self.intervals :
   661             for interval in self.intervals :
   658                 log.info("Cleaning interval: %s...", interval)
   662                 log.debug("%s: cleaning interval: %s", self, interval)
   659 
       
   660                 self.clean_interval(options, interval)
   663                 self.clean_interval(options, interval)
   661 
   664 
   662         # clean snapshots?
   665         # clean snapshots?
   663         if options.clean_snapshots :
   666         if options.clean_snapshots :
   664             log.info("Cleaning snapshots...")
   667             log.debug("%s: cleaning snapshots...", self)
   665 
   668 
   666             self.clean_snapshots(options)
   669             self.clean_snapshots(options)
   667 
   670 
   668         # snapshot from source?
   671         # snapshot from source?
   669         if self.source :
   672         if self.source :
   670             # timestamp for run
   673             # timestamp for run
   671             now = datetime.datetime.now()
   674             now = datetime.datetime.now()
   672 
   675 
   673             log.info("Started snapshot run at: %s", now)
   676             log.debug("%s: started snapshot run at: %s", self, now)
   674 
   677 
   675             # snapshot + current
   678             # snapshot + current
   676             snapshot_name = self.run_snapshot(options, now)
   679             snapshot_name = self.run_snapshot(options, now)
   677 
   680 
   678             # intervals?
   681             # intervals?
   799     if options.run :
   802     if options.run :
   800 
   803 
   801         # given [run/...] definition..
   804         # given [run/...] definition..
   802         run_targets = list(_parse_run_targets(options, config, options.run))
   805         run_targets = list(_parse_run_targets(options, config, options.run))
   803         
   806         
   804         log.info("Running %d given [run/%s] targets", len(run_targets), options.run)
   807         log.debug("Running %d given [run/%s] targets: %s", len(run_targets), options.run, run_targets)
   805         log.debug("[run/%s]: %s", options.run, run_targets)
       
   806     
   808     
   807     # run
   809     # run
   808     if run_targets :
   810     if run_targets :
   809         log.info("Running %d given targets...", len(run_targets))
   811         log.debug("Running %d given targets...", len(run_targets))
   810 
   812 
   811         # run given ones
   813         # run given ones
   812         for name in run_targets :
   814         for name in run_targets :
   813             try :
   815             try :
   814                 # get
   816                 # get
   819                 log.info("Defined targets: %s", ' '.join(options.targets))
   821                 log.info("Defined targets: %s", ' '.join(options.targets))
   820                 return 2
   822                 return 2
   821 
   823 
   822 
   824 
   823             # run
   825             # run
   824             log.info("Target: %s", name)
   826             log.info("%s", name)
   825 
   827 
   826             target.run(options)
   828             target.run(options)
   827 
   829 
   828     else :
   830     else :
   829         # all targets
   831         # all targets
   830         log.info("Running all %d targets...", len(options.targets))
   832         log.debug("Running all %d targets...", len(options.targets))
   831 
   833 
   832         # targets
   834         # targets
   833         for name, target in options.targets.iteritems() :
   835         for name, target in options.targets.iteritems() :
   834             log.info("Target: %s", name)
   836             log.info("%s", name)
   835 
   837 
   836             # run
   838             # run
   837             target.run(options)
   839             target.run(options)
   838 
   840 
   839     # ok
   841     # ok