Back to index

obnam  1.1
app.py
Go to the documentation of this file.
00001 # Copyright (C) 2009, 2011  Lars Wirzenius
00002 #
00003 # This program is free software: you can redistribute it and/or modify
00004 # it under the terms of the GNU General Public License as published by
00005 # the Free Software Foundation, either version 3 of the License, or
00006 # (at your option) any later version.
00007 #
00008 # This program is distributed in the hope that it will be useful,
00009 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 # GNU General Public License for more details.
00012 #
00013 # You should have received a copy of the GNU General Public License
00014 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
00015 
00016 
00017 import cliapp
00018 import larch
00019 import logging
00020 import os
00021 import socket
00022 import StringIO
00023 import sys
00024 import time
00025 import tracing
00026 import ttystatus
00027 
00028 import obnamlib
00029 
00030 
00031 class App(cliapp.Application):
00032 
00033     '''Main program for backup program.'''
00034     
00035     def add_settings(self):
00036         devel_group = obnamlib.option_group['devel']
00037         perf_group = obnamlib.option_group['perf']
00038     
00039         self.settings.string(['repository', 'r'], 'name of backup repository')
00040 
00041         self.settings.string(['client-name'], 'name of client (%default)',
00042                            default=self.deduce_client_name())
00043 
00044         self.settings.bytesize(['node-size'],
00045                              'size of B-tree nodes on disk '
00046                                  '(default: %default)',
00047                               default=obnamlib.DEFAULT_NODE_SIZE,
00048                               group=perf_group)
00049 
00050         self.settings.bytesize(['chunk-size'],
00051                             'size of chunks of file data backed up '
00052                                  '(default: %default)',
00053                              default=obnamlib.DEFAULT_CHUNK_SIZE,
00054                               group=perf_group)
00055 
00056         self.settings.bytesize(['upload-queue-size'],
00057                             'length of upload queue for B-tree nodes '
00058                                  '(default: %default)',
00059                             default=obnamlib.DEFAULT_UPLOAD_QUEUE_SIZE,
00060                             group=perf_group)
00061 
00062         self.settings.bytesize(['lru-size'],
00063                              'size of LRU cache for B-tree nodes '
00064                                  '(default: %default)',
00065                              default=obnamlib.DEFAULT_LRU_SIZE,
00066                              group=perf_group)
00067 
00068         self.settings.string_list(['trace'],
00069                                 'add to filename patters for which trace '
00070                                 'debugging logging happens')
00071 
00072         
00073         self.settings.integer(['idpath-depth'],
00074                               'depth of chunk id mapping',
00075                               default=obnamlib.IDPATH_DEPTH,
00076                               group=perf_group)
00077         self.settings.integer(['idpath-bits'],
00078                               'chunk id level size',
00079                               default=obnamlib.IDPATH_BITS,
00080                               group=perf_group)
00081         self.settings.integer(['idpath-skip'],
00082                               'chunk id mapping lowest bits skip',
00083                               default=obnamlib.IDPATH_SKIP,
00084                               group=perf_group)
00085 
00086         self.settings.boolean(['quiet'], 'be silent')
00087 
00088         self.settings.boolean(['pretend', 'dry-run', 'no-act'],
00089                            'do not actually change anything (works with '
00090                            'backup, forget and restore only, and may only '
00091                            'simulate approximately real behavior)')
00092                            
00093         self.settings.string(['pretend-time'],
00094                              'pretend it is TIMESTAMP (YYYY-MM-DD HH:MM:SS); '
00095                                 'this is only useful for testing purposes',
00096                              metavar='TIMESTAMP',
00097                              group=devel_group)
00098 
00099         self.settings.integer(['lock-timeout'],
00100                               'when locking in the backup repository, '
00101                                 'wait TIMEOUT seconds for an existing lock '
00102                                 'to go away before giving up',
00103                               metavar='TIMEOUT',
00104                               default=60)
00105 
00106         self.settings.integer(['crash-limit'],
00107                               'artificially crash the program after COUNTER '
00108                                 'files written to the repository; this is '
00109                                 'useful for crash testing the application, '
00110                                 'and should not be enabled for real use; '
00111                                 'set to 0 to disable (disabled by default)',
00112                               metavar='COUNTER',
00113                               group=devel_group)
00114 
00115         # The following needs to be done here, because it needs
00116         # to be done before option processing. This is a bit ugly,
00117         # but the best we can do with the current cliapp structure.
00118         # Possibly cliapp will provide a better hook for us to use
00119         # later on, but this is reality now.
00120 
00121         self.setup_ttystatus()
00122 
00123         self.pm = obnamlib.PluginManager()
00124         self.pm.locations = [self.plugins_dir()]
00125         self.pm.plugin_arguments = (self,)
00126         
00127         self.setup_hooks()
00128 
00129         self.fsf = obnamlib.VfsFactory()
00130 
00131         self.pm.load_plugins()
00132         self.pm.enable_plugins()
00133         self.hooks.call('plugins-loaded')
00134 
00135     def deduce_client_name(self):
00136         return socket.gethostname()
00137 
00138     def setup_hooks(self):
00139         self.hooks = obnamlib.HookManager()
00140         self.hooks.new('plugins-loaded')
00141         self.hooks.new('config-loaded')
00142         self.hooks.new('shutdown')
00143 
00144         # The Repository class defines some hooks, but the class
00145         # won't be instantiated until much after plugins are enabled,
00146         # and since all hooks must be defined when plugins are enabled,
00147         # we create one instance here, which will immediately be destroyed.
00148         # FIXME: This is fugly.
00149         obnamlib.Repository(None, 1000, 1000, 100, self.hooks, 10, 10, 10,
00150                             self.time, 0, '')
00151 
00152     def plugins_dir(self):
00153         return os.path.join(os.path.dirname(obnamlib.__file__), 'plugins')
00154 
00155     def setup_logging(self):
00156         log = self.settings['log']
00157         if log and log != 'syslog' and not os.path.exists(log):
00158             fd = os.open(log, os.O_WRONLY | os.O_CREAT, 0600)
00159             os.close(fd)
00160         cliapp.Application.setup_logging(self)
00161 
00162     def process_args(self, args):
00163         try:
00164             if self.settings['quiet']:
00165                 self.ts.disable()
00166             self.log_config()
00167             for pattern in self.settings['trace']:
00168                 tracing.trace_add_pattern(pattern)
00169             self.hooks.call('config-loaded')
00170             cliapp.Application.process_args(self, args)
00171             self.hooks.call('shutdown')
00172             logging.info('Obnam ends')
00173         except larch.Error, e:
00174             logging.critical(str(e))
00175             sys.stderr.write('ERROR: %s\n' % str(e))
00176             sys.exit(1)
00177 
00178     def log_config(self):
00179         '''Log current configuration into the log file.'''
00180         f = StringIO.StringIO()
00181         self.settings.dump_config(f)
00182         logging.debug('Current configuration:\n%s' % f.getvalue())
00183 
00184     def setup_ttystatus(self):
00185         self.ts = ttystatus.TerminalStatus(period=0.25)
00186         if self.settings['quiet']:
00187             self.ts.disable()
00188 
00189     def open_repository(self, create=False, repofs=None): # pragma: no cover
00190         logging.debug('opening repository (create=%s)' % create)
00191         tracing.trace('repofs=%s' % repr(repofs))
00192         repopath = self.settings['repository']
00193         if repofs is None:
00194             repofs = self.fsf.new(repopath, create=create)
00195             if self.settings['crash-limit'] > 0:
00196                 repofs.crash_limit = self.settings['crash-limit']
00197             repofs.connect()
00198         else:
00199             repofs.reinit(repopath)
00200         return obnamlib.Repository(repofs, 
00201                                     self.settings['node-size'],
00202                                     self.settings['upload-queue-size'],
00203                                     self.settings['lru-size'],
00204                                     self.hooks,
00205                                     self.settings['idpath-depth'],
00206                                     self.settings['idpath-bits'],
00207                                     self.settings['idpath-skip'],
00208                                     self.time,
00209                                     self.settings['lock-timeout'],
00210                                     self.settings['client-name'])
00211 
00212     def time(self):
00213         '''Return current time in seconds since epoch.
00214         
00215         This is a wrapper around time.time() so that it can be overridden
00216         with the --pretend-time setting.
00217         
00218         '''
00219 
00220         s = self.settings['pretend-time']
00221         if s:
00222             t = time.strptime(s, '%Y-%m-%d %H:%M:%S')
00223             return time.mktime(t)
00224         else:
00225             return time.time()
00226