CedarBackup2.actions.stage

Package CedarBackup2 :: Package actions :: Module stage |
[hide private]
[frames] | no frames]

# Source Code for Module CedarBackup2.actions.stage

      1  # -*- coding: iso-8859-1 -*-
      2  # vim: set ft=python ts=3 sw=3 expandtab:
      3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      4  #
      5  #              C E D A R
      6  #          S O L U T I O N S       "Software done right."
      7  #           S O F T W A R E
      8  #
      9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
     10  #
     11  # Copyright (c) 2004-2008,2010 Kenneth J. Pronovici.
     12  # All rights reserved.
     13  #
     14  # This program is free software; you can redistribute it and/or
     15  # modify it under the terms of the GNU General Public License,
     16  # Version 2, as published by the Free Software Foundation.
     17  #
     18  # This program is distributed in the hope that it will be useful,
     19  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     21  #
     22  # Copies of the GNU General Public License are available from
     23  # the Free Software Foundation website, http://www.gnu.org/.
     24  #
     25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
     26  #
     27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org>
     28  # Language : Python 2 (>= 2.7)
     29  # Project  : Cedar Backup, release 2
     30  # Purpose  : Implements the standard 'stage' action.
     31  #
     32  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
     33
     34  ########################################################################
     35  # Module documentation
     36  ########################################################################
     37
     38  """
     39  Implements the standard 'stage' action.
     40  @sort: executeStage
     41  @author: Kenneth J. Pronovici <pronovic@ieee.org>
     42  """
     43
     44
     45  ########################################################################
     46  # Imported modules
     47  ########################################################################
     48
     49  # System modules
     50  import os
     51  import time
     52  import logging
     53
     54  # Cedar Backup modules
     55  from CedarBackup2.peer import RemotePeer, LocalPeer
     56  from CedarBackup2.util import getUidGid, changeOwnership, isStartOfWeek, isRunningAsRoot
     57  from CedarBackup2.actions.constants import DIR_TIME_FORMAT, STAGE_INDICATOR
     58  from CedarBackup2.actions.util import writeIndicatorFile
     59
     60
     61  ########################################################################
     62  # Module-wide constants and variables
     63  ########################################################################
     64
     65  logger = logging.getLogger("CedarBackup2.log.actions.stage")
     66
     67
     68  ########################################################################
     69  # Public functions
     70  ########################################################################
     71
     72  ##########################
     73  # executeStage() function
     74  ##########################
     75
     76 -def executeStage(configPath, options, config):
     77     """
     78     Executes the stage backup action.
     79
     80     @note: The daily directory is derived once and then we stick with it, just
     81     in case a backup happens to span midnite.
     82
     83     @note: As portions of the stage action is complete, we will write various
     84     indicator files so that it's obvious what actions have been completed.  Each
     85     peer gets a stage indicator in its collect directory, and then the master
     86     gets a stage indicator in its daily staging directory.  The store process
     87     uses the master's stage indicator to decide whether a directory is ready to
     88     be stored.  Currently, nothing uses the indicator at each peer, and it
     89     exists for reference only.
     90
     91     @param configPath: Path to configuration file on disk.
     92     @type configPath: String representing a path on disk.
     93
     94     @param options: Program command-line options.
     95     @type options: Options object.
     96
     97     @param config: Program configuration.
     98     @type config: Config object.
     99
    100     @raise ValueError: Under many generic error conditions
    101     @raise IOError: If there are problems reading or writing files.
    102     """
    103     logger.debug("Executing the 'stage' action.")
    104     if config.options is None or config.stage is None:
    105        raise ValueError("Stage configuration is not properly filled in.")
    106     dailyDir = _getDailyDir(config)
    107     localPeers = _getLocalPeers(config)
    108     remotePeers = _getRemotePeers(config)
    109     allPeers = localPeers + remotePeers
    110     stagingDirs = _createStagingDirs(config, dailyDir, allPeers)
    111     for peer in allPeers:
    112        logger.info("Staging peer [%s].", peer.name)
    113        ignoreFailures = _getIgnoreFailuresFlag(options, config, peer)
    114        if not peer.checkCollectIndicator():
    115           if not ignoreFailures:
    116              logger.error("Peer [%s] was not ready to be staged.", peer.name)
    117           else:
    118              logger.info("Peer [%s] was not ready to be staged.", peer.name)
    119           continue
    120        logger.debug("Found collect indicator.")
    121        targetDir = stagingDirs[peer.name]
    122        if isRunningAsRoot():
    123           # Since we're running as root, we can change ownership
    124           ownership = getUidGid(config.options.backupUser,  config.options.backupGroup)
    125           logger.debug("Using target dir [%s], ownership [%d:%d].", targetDir, ownership[0], ownership[1])
    126        else:
    127           # Non-root cannot change ownership, so don't set it
    128           ownership = None
    129           logger.debug("Using target dir [%s], ownership [None].", targetDir)
    130        try:
    131           count = peer.stagePeer(targetDir=targetDir, ownership=ownership)  # note: utilize effective user's default umask
    132           logger.info("Staged %d files for peer [%s].", count, peer.name)
    133           peer.writeStageIndicator()
    134        except (ValueError, IOError, OSError), e:
    135           logger.error("Error staging [%s]: %s", peer.name, e)
    136     writeIndicatorFile(dailyDir, STAGE_INDICATOR, config.options.backupUser, config.options.backupGroup)
    137     logger.info("Executed the 'stage' action successfully.")
    138
    139
    140  ########################################################################
    141  # Private utility functions
    142  ########################################################################
    143
    144  ################################
    145  # _createStagingDirs() function
    146  ################################
    147
    148 -def _createStagingDirs(config, dailyDir, peers):
    149     """
    150     Creates staging directories as required.
    151
    152     The main staging directory is the passed in daily directory, something like
    153     C{staging/2002/05/23}.  Then, individual peers get their own directories,
    154     i.e. C{staging/2002/05/23/host}.
    155
    156     @param config: Config object.
    157     @param dailyDir: Daily staging directory.
    158     @param peers: List of all configured peers.
    159
    160     @return: Dictionary mapping peer name to staging directory.
    161     """
    162     mapping = {}
    163     if os.path.isdir(dailyDir):
    164        logger.warn("Staging directory [%s] already existed.", dailyDir)
    165     else:
    166        try:
    167           logger.debug("Creating staging directory [%s].", dailyDir)
    168           os.makedirs(dailyDir)
    169           for path in [ dailyDir, os.path.join(dailyDir, ".."), os.path.join(dailyDir, "..", ".."), ]:
    170              changeOwnership(path, config.options.backupUser, config.options.backupGroup)
    171        except Exception, e:
    172           raise Exception("Unable to create staging directory: %s" % e)
    173     for peer in peers:
    174        peerDir = os.path.join(dailyDir, peer.name)
    175        mapping[peer.name] = peerDir
    176        if os.path.isdir(peerDir):
    177           logger.warn("Peer staging directory [%s] already existed.", peerDir)
    178        else:
    179           try:
    180              logger.debug("Creating peer staging directory [%s].", peerDir)
    181              os.makedirs(peerDir)
    182              changeOwnership(peerDir, config.options.backupUser, config.options.backupGroup)
    183           except Exception, e:
    184              raise Exception("Unable to create staging directory: %s" % e)
    185     return mapping
    186
    187
    188  ########################################################################
    189  # Private attribute "getter" functions
    190  ########################################################################
    191
    192  ####################################
    193  # _getIgnoreFailuresFlag() function
    194  ####################################
    195
    196 -def _getIgnoreFailuresFlag(options, config, peer):
    197     """
    198     Gets the ignore failures flag based on options, configuration, and peer.
    199     @param options: Options object
    200     @param config: Configuration object
    201     @param peer: Peer to check
    202     @return: Whether to ignore stage failures for this peer
    203     """
    204     logger.debug("Ignore failure mode for this peer: %s", peer.ignoreFailureMode)
    205     if peer.ignoreFailureMode is None or peer.ignoreFailureMode == "none":
    206        return False
    207     elif peer.ignoreFailureMode == "all":
    208        return True
    209     else:
    210        if options.full or isStartOfWeek(config.options.startingDay):
    211           return peer.ignoreFailureMode == "weekly"
    212        else:
    213           return peer.ignoreFailureMode == "daily"
    214
    215
    216  ##########################
    217  # _getDailyDir() function
    218  ##########################
    219
    220 -def _getDailyDir(config):
    221     """
    222     Gets the daily staging directory.
    223
    224     This is just a directory in the form C{staging/YYYY/MM/DD}, i.e.
    225     C{staging/2000/10/07}, except it will be an absolute path based on
    226     C{config.stage.targetDir}.
    227
    228     @param config: Config object
    229
    230     @return: Path of daily staging directory.
    231     """
    232     dailyDir = os.path.join(config.stage.targetDir, time.strftime(DIR_TIME_FORMAT))
    233     logger.debug("Daily staging directory is [%s].", dailyDir)
    234     return dailyDir
    235
    236
    237  ############################
    238  # _getLocalPeers() function
    239  ############################
    240
    241 -def _getLocalPeers(config):
    242     """
    243     Return a list of L{LocalPeer} objects based on configuration.
    244     @param config: Config object.
    245     @return: List of L{LocalPeer} objects.
    246     """
    247     localPeers = []
    248     configPeers = None
    249     if config.stage.hasPeers():
    250        logger.debug("Using list of local peers from stage configuration.")
    251        configPeers = config.stage.localPeers
    252     elif config.peers is not None and config.peers.hasPeers():
    253        logger.debug("Using list of local peers from peers configuration.")
    254        configPeers = config.peers.localPeers
    255     if configPeers is not None:
    256        for peer in configPeers:
    257           localPeer = LocalPeer(peer.name, peer.collectDir, peer.ignoreFailureMode)
    258           localPeers.append(localPeer)
    259           logger.debug("Found local peer: [%s]", localPeer.name)
    260     return localPeers
    261
    262
    263  #############################
    264  # _getRemotePeers() function
    265  #############################
    266
    267 -def _getRemotePeers(config):
    268     """
    269     Return a list of L{RemotePeer} objects based on configuration.
    270     @param config: Config object.
    271     @return: List of L{RemotePeer} objects.
    272     """
    273     remotePeers = []
    274     configPeers = None
    275     if config.stage.hasPeers():
    276        logger.debug("Using list of remote peers from stage configuration.")
    277        configPeers = config.stage.remotePeers
    278     elif config.peers is not None and config.peers.hasPeers():
    279        logger.debug("Using list of remote peers from peers configuration.")
    280        configPeers = config.peers.remotePeers
    281     if configPeers is not None:
    282        for peer in configPeers:
    283           remoteUser = _getRemoteUser(config, peer)
    284           localUser = _getLocalUser(config)
    285           rcpCommand = _getRcpCommand(config, peer)
    286           remotePeer = RemotePeer(peer.name, peer.collectDir, config.options.workingDir,
    287                                   remoteUser, rcpCommand, localUser,
    288                                   ignoreFailureMode=peer.ignoreFailureMode)
    289           remotePeers.append(remotePeer)
    290           logger.debug("Found remote peer: [%s]", remotePeer.name)
    291     return remotePeers
    292
    293
    294  ############################
    295  # _getRemoteUser() function
    296  ############################
    297
    298 -def _getRemoteUser(config, remotePeer):
    299     """
    300     Gets the remote user associated with a remote peer.
    301     Use peer's if possible, otherwise take from options section.
    302     @param config: Config object.
    303     @param remotePeer: Configuration-style remote peer object.
    304     @return: Name of remote user associated with remote peer.
    305     """
    306     if remotePeer.remoteUser is None:
    307        return config.options.backupUser
    308     return remotePeer.remoteUser
    309
    310
    311  ###########################
    312  # _getLocalUser() function
    313  ###########################
    314
    315 -def _getLocalUser(config):
    316     """
    317     Gets the remote user associated with a remote peer.
    318     @param config: Config object.
    319     @return: Name of local user that should be used
    320     """
    321     if not isRunningAsRoot():
    322        return None
    323     return config.options.backupUser
    324
    325
    326  ############################
    327  # _getRcpCommand() function
    328  ############################
    329
    330 -def _getRcpCommand(config, remotePeer):
    331     """
    332     Gets the RCP command associated with a remote peer.
    333     Use peer's if possible, otherwise take from options section.
    334     @param config: Config object.
    335     @param remotePeer: Configuration-style remote peer object.
    336     @return: RCP command associated with remote peer.
    337     """
    338     if remotePeer.rcpCommand is None:
    339        return config.options.rcpCommand
    340     return remotePeer.rcpCommand
    341