Package Products :: Package ZenUtils :: Module ZenRestore |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenUtils.ZenRestore

      1  #! /usr/bin/env python
      2  ###########################################################################
      3  #
      4  # This program is part of Zenoss Core, an open source monitoring platform.
      5  # Copyright (C) 2007, 2009 Zenoss Inc.
      6  #
      7  # This program is free software; you can redistribute it and/or modify it
      8  # under the terms of the GNU General Public License version 2 or (at your
      9  # option) any later version as published by the Free Software Foundation.
     10  #
     11  # For complete information please visit: http://www.zenoss.com/oss/
     12  #
     13  ###########################################################################
     14
     15  __doc__='''zenrestore
     16
     17  Restores a zenoss backup created by zenbackup.
     18  '''
     19
     20  import logging
     21  import sys
     22  import os
     23  import os.path
     24  import subprocess
     25  import tarfile
     26  import ConfigParser
     27
     28  import Globals
     29  from ZCmdBase import ZCmdBase
     30  from Products.ZenUtils.Utils import zenPath, binPath
     31
     32  from ZenBackupBase import *
     33
     34
     35 -class ZenRestore(ZenBackupBase):
     36
     37 -    def __init__(self):
     38          ZenBackupBase.__init__(self)
     39          self.log = logging.getLogger("zenrestore")
     40          logging.basicConfig()
     41          if self.options.verbose:
     42              self.log.setLevel(10)
     43          else:
     44              self.log.setLevel(40)
     45
     46 -    def buildOptions(self):
     47          """basic options setup sub classes can add more options here"""
     48          ZenBackupBase.buildOptions(self)
     49
     50          self.parser.add_option('--file',
     51                                 dest="file",
     52                                 default=None,
     53                                 help='File from which to restore.')
     54          self.parser.add_option('--dir',
     55                                 dest="dir",
     56                                 default=None,
     57                                 help='Path to an untarred backup file'
     58                                          ' from which to restore.')
     59          self.parser.add_option('--no-zodb',
     60                                 dest="noZODB",
     61                                 default=False,
     62                                 action='store_true',
     63                                 help='Do not restore the ZODB.')
     64          self.parser.add_option('--no-eventsdb',
     65                                 dest="noEventsDb",
     66                                 default=False,
     67                                 action='store_true',
     68                                 help='Do not restore the events database.')
     69          self.parser.add_option('--no-perfdata',
     70                                 dest="noPerfdata",
     71                                 default=False,
     72                                 action='store_true',
     73                                 help='Do not restore performance data.')
     74          self.parser.add_option('--deletePreviousPerfData',
     75                                 dest="deletePreviousPerfData",
     76                                 default=False,
     77                                 action='store_true',
     78                                 help='Delete ALL existing performance data before restoring?')
     79          self.parser.add_option('--zenpacks',
     80                                 dest='zenpacks',
     81                                 default=False,
     82                                 action='store_true',
     83                                 help=('Experimental: Restore any ZenPacks in '
     84                                       'the backup. Some ZenPacks may not work '
     85                                       'properly. Reinstall ZenPacks if possible'))
     86
     87 -    def getSettings(self):
     88          ''' Retrieve some options from settings file
     89          '''
     90          try:
     91              f = open(os.path.join(self.tempDir, CONFIG_FILE), 'r')
     92          except:
     93              return
     94          try:
     95              config = ConfigParser.SafeConfigParser()
     96              config.readfp(f)
     97              for name, value in config.items(CONFIG_SECTION):
     98                  setattr(self.options, name, value)
     99          finally:
    100              f.close()
    101
    102 -    def getSqlFile(self, filename):
    103          """
    104          Find path to sql file in backup, trying gzipped versions
    105          Returns path to real file, or none if nothing found
    106          """
    107          pathFile = os.path.join(self.tempDir, filename)
    108          for path in (pathFile, pathFile + '.gz'):
    109              if os.path.isfile(path):
    110                  return path
    111          return None
    112
    113 -    def restoreMySqlDb(self, host, port, db, user, passwd, sqlFile):
    114          """
    115          Create MySQL database if it doesn't exist.
    116          """
    117          mysql_cmd = ['mysql', '-u%s' % user]
    118          mysql_cmd.extend(passwd)
    119          if host and host != 'localhost':
    120              mysql_cmd.extend(['--host', host])
    121              if self.options.compressTransport:
    122                  mysql_cmd.append('--compress')
    123          if port and str(port) != '3306':
    124              mysql_cmd.extend(['--port', str(port)])
    125
    126          mysql_cmd = subprocess.list2cmdline(mysql_cmd)
    127
    128          cmd = 'echo "create database if not exists %s" | %s' % (db, mysql_cmd)
    129          os.system(cmd)
    130
    131          if sqlFile.endswith('.gz'):
    132              cmd = 'gzip -dc %s | %s %s' % (
    133                  os.path.join(self.tempDir, sqlFile), mysql_cmd, db
    134              )
    135          else:
    136              cmd = '%s %s < %s' % (
    137                  mysql_cmd, db, os.path.join(self.tempDir, sqlFile)
    138              )
    139
    140          os.system(cmd)
    141
    142
    143 -    def restoreZEP(self):
    144          '''
    145          Restore ZEP DB and indexes
    146          '''
    147          zepSql = self.getSqlFile('zep.sql')
    148          if not zepSql:
    149              self.msg('This backup does not contain a ZEP database backup.')
    150              return
    151
    152          self.msg('Restoring ZEP database.')
    153          self.restoreMySqlDb(self.options.zepdbhost, self.options.zepdbport,
    154                              self.options.zepdbname, self.options.zepdbuser,
    155                              self.getPassArg('zepdbpass'), zepSql)
    156          self.msg('ZEP database restored.')
    157
    158          # Remove any current indexes on the system
    159          index_dir = zenPath('var', 'zeneventserver', 'index')
    160          if os.path.isdir(index_dir):
    161              import shutil
    162              self.msg('Removing existing ZEP indexes.')
    163              shutil.rmtree(index_dir)
    164
    165          index_tar = os.path.join(self.tempDir, 'zep.tar')
    166          if os.path.isfile(index_tar):
    167              self.msg('Restoring ZEP indexes.')
    168              zepTar = tarfile.open(os.path.join(self.tempDir, 'zep.tar'))
    169              zepTar.extractall(zenPath('var'))
    170              self.msg('ZEP indexes restored.')
    171          else:
    172              self.msg('ZEP indexes not found in backup file - will be recreated from database.')
    173
    174
    175
    176 -    def hasZeoBackup(self):
    177          repozoDir = os.path.join(self.tempDir, 'repozo')
    178          return os.path.isdir(repozoDir)
    179
    180 -    def hasSqlBackup(self):
    181          return bool(self.getSqlFile('zodb.sql'))
    182
    183 -    def hasZODBBackup(self):
    184          return self.hasZeoBackup() or self.hasSqlBackup()
    185
    186 -    def restoreZODB(self):
    187          # Relstorage may have already loaded items into the cache in the
    188          # initial connection to the database. We have to expire everything
    189          # in the cache in order to prevent errors with overlapping
    190          # transactions from the backup.
    191          if self.options.cacheservers:
    192              self.flush_memcached(self.options.cacheservers.split())
    193          if self.hasSqlBackup():
    194              self.restoreZODBSQL()
    195          elif self.hasZeoBackup():
    196              self.restoreZODBZEO()
    197
    198 -    def restoreZODBSQL(self):
    199          zodbSql = self.getSqlFile('zodb.sql')
    200          if not zodbSql:
    201              self.msg('This archive does not contain a ZODB backup.')
    202              return
    203          self.msg('Restoring ZODB database.')
    204          self.restoreMySqlDb(self.options.host, self.options.port,
    205                              self.options.mysqldb, self.options.mysqluser,
    206                              self.getPassArg('mysqlpasswd'), zodbSql)
    207
    208 -    def restoreZODBZEO(self):
    209          repozoDir = os.path.join(self.tempDir, 'repozo')
    210          tempFilePath = os.path.join(self.tempDir, 'Data.fs')
    211          tempZodbConvert = os.path.join(self.tempDir, 'convert.conf')
    212
    213          self.msg('Restoring ZEO backup into MySQL.')
    214
    215          # Create a Data.fs from the repozo backup
    216          cmd = []
    217          cmd.append(binPath('repozo'))
    218          cmd.append('--recover')
    219          cmd.append('--repository')
    220          cmd.append(repozoDir)
    221          cmd.append('--output')
    222          cmd.append(tempFilePath)
    223
    224          rc = subprocess.call(cmd, stdout=PIPE, stderr=PIPE)
    225          if rc:
    226              return -1
    227
    228          # Now we have a Data.fs, restore into MySQL with zodbconvert
    229          zodbconvert_conf = open(tempZodbConvert, 'w')
    230          zodbconvert_conf.write('<filestorage source>\n')
    231          zodbconvert_conf.write('  path %s\n' % tempFilePath)
    232          zodbconvert_conf.write('</filestorage>\n\n')
    233
    234          zodbconvert_conf.write('<relstorage destination>\n')
    235          zodbconvert_conf.write('  <mysql>\n')
    236          zodbconvert_conf.write('    host %s\n' % self.options.host)
    237          zodbconvert_conf.write('    port %s\n' % self.options.port)
    238          zodbconvert_conf.write('    db %s\n' % self.options.mysqldb)
    239          zodbconvert_conf.write('    user %s\n' % self.options.mysqluser)
    240          zodbconvert_conf.write('    passwd %s\n' % self.options.mysqlpasswd or '')
    241          zodbconvert_conf.write('  </mysql>\n')
    242          zodbconvert_conf.write('</relstorage>\n')
    243          zodbconvert_conf.close()
    244
    245          rc = subprocess.call(['zodbconvert', '--clear', tempZodbConvert],
    246                               stdout=PIPE, stderr=PIPE)
    247          if rc:
    248              return -1
    249
    250
    251 -    def restoreEtcFiles(self):
    252          self.msg('Restoring config files.')
    253          cmd = 'cp -p %s %s' % (os.path.join(zenPath('etc'), 'global.conf'), self.tempDir)
    254          if os.system(cmd): return -1
    255          cmd = 'rm -rf %s' % zenPath('etc')
    256          if os.system(cmd): return -1
    257          cmd = 'tar Cxf %s %s' % (
    258              zenPath(),
    259              os.path.join(self.tempDir, 'etc.tar')
    260          )
    261          if os.system(cmd): return -1
    262          if not os.path.exists(os.path.join(zenPath('etc'), 'global.conf')):
    263              self.msg('Restoring default global.conf')
    264              cmd = 'mv %s %s' % (os.path.join(self.tempDir, 'global.conf'), zenPath('etc'))
    265              if os.system(cmd): return -1
    266
    267 -    def restoreZenPacks(self):
    268          self.msg('Restoring ZenPacks.')
    269          cmd = 'rm -rf %s' % zenPath('ZenPacks')
    270          if os.system(cmd): return -1
    271          cmd = 'tar Cxf %s %s' % (
    272                          zenPath(),
    273                          os.path.join(self.tempDir, 'ZenPacks.tar'))
    274          if os.system(cmd): return -1
    275          # restore bin dir when restoring zenpacks
    276          #make sure bin dir is in tar
    277          tempBin = os.path.join(self.tempDir, 'bin.tar')
    278          if os.path.isfile(tempBin):
    279              self.msg('Restoring bin dir.')
    280              #k option prevents overwriting existing bin files
    281              cmd = ['tar', 'Cxfk', zenPath(),
    282                     os.path.join(self.tempDir, 'bin.tar')]
    283              self.runCommand(cmd)
    284
    285 -    def restoreZenPackContents(self):
    286          dmd = ZCmdBase(noopts=True).dmd
    287          self.log.info("Restoring ZenPack contents.")
    288          for pack in dmd.ZenPackManager.packs():
    289              pack.restore(self.tempDir, self.log)
    290          self.log.info("ZenPack contents restored.")
    291
    292 -    def restorePerfData(self):
    293          cmd = 'rm -rf %s' % os.path.join(zenPath(), 'perf')
    294          if os.system(cmd): return -1
    295          self.msg('Restoring performance data.')
    296          cmd = 'tar Cxf %s %s' % (
    297                          zenPath(),
    298                          os.path.join(self.tempDir, 'perf.tar'))
    299          if os.system(cmd): return -1
    300
    301 -    def doRestore(self):
    302          """
    303          Restore from a previous backup
    304          """
    305          if self.options.file and self.options.dir:
    306              sys.stderr.write('You cannot specify both --file and --dir.\n')
    307              sys.exit(-1)
    308          elif not self.options.file and not self.options.dir:
    309              sys.stderr.write('You must specify either --file or --dir.\n')
    310              sys.exit(-1)
    311
    312          # Maybe check to see if zeo is up and tell user to quit zenoss first
    313
    314          rootTempDir = ''
    315          if self.options.file:
    316              if not os.path.isfile(self.options.file):
    317                  sys.stderr.write('The specified backup file does not exist: %s\n' %
    318                        self.options.file)
    319                  sys.exit(-1)
    320              # Create temp dir and untar backup into it
    321              self.msg('Unpacking backup file')
    322              rootTempDir = self.getTempDir()
    323              cmd = 'tar xzfC %s %s' % (self.options.file, rootTempDir)
    324              if os.system(cmd): return -1
    325              self.tempDir = os.path.join(rootTempDir, BACKUP_DIR)
    326          else:
    327              self.msg('Using %s as source of restore' % self.options.dir)
    328              if not os.path.isdir(self.options.dir):
    329                  sys.stderr.write('The specified backup directory does not exist:'
    330                                  ' %s\n' % self.options.dir)
    331                  sys.exit(-1)
    332              self.tempDir = self.options.dir
    333
    334          # Maybe use values from backup file as defaults for self.options.
    335          self.getSettings()
    336
    337          if self.options.zenpacks and not self.hasZODBBackup():
    338              sys.stderr.write('Archive does not contain ZODB backup; cannot'
    339                               'restore ZenPacks')
    340              sys.exit(-1)
    341
    342          # ZODB
    343          if self.hasZODBBackup():
    344              self.restoreZODB()
    345          else:
    346              self.msg('Archive does not contain a ZODB backup')
    347
    348          # Configuration
    349          self.restoreEtcFiles()
    350
    351          # ZenPacks
    352          if self.options.zenpacks:
    353              tempPacks = os.path.join(self.tempDir, 'ZenPacks.tar')
    354              if os.path.isfile(tempPacks):
    355                  self.restoreZenPacks()
    356                  self.restoreZenPackContents()
    357              else:
    358                  self.msg('Backup contains no ZenPacks.')
    359
    360          # Performance Data
    361          tempPerf = os.path.join(self.tempDir, 'perf.tar')
    362          if os.path.isfile(tempPerf):
    363              self.restorePerfData()
    364          else:
    365              self.msg('Backup contains no perf data.')
    366
    367          # Events
    368          if self.options.noEventsDb:
    369              self.msg('Skipping the events database.')
    370          else:
    371              self.restoreZEP()
    372
    373          # clean up
    374          if self.options.file:
    375              self.msg('Cleaning up temporary files.')
    376              cmd = 'rm -r %s' % rootTempDir
    377              if os.system(cmd): return -1
    378
    379          self.msg('Restore complete.')
    380          return 0
    381
    382 -    def flush_memcached(self, cacheservers):
    383          self.msg('Flushing memcached cache.')
    384          import memcache
    385          mc = memcache.Client(cacheservers, debug=0)
    386          mc.flush_all()
    387          mc.disconnect_all()
    388          self.msg('Completed flushing memcached cache.')
    389
    390  if __name__ == '__main__':
    391      zb = ZenRestore()
    392      if zb.doRestore():
    393          sys.exit(-1)
    394