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