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 self.msg('Restoring ZEP indexes.') 158 zepTar = tarfile.open(os.path.join(self.tempDir, 'zep.tar')) 159 zepTar.extractall(zenPath('var')) 160 self.msg('ZEP indexes restored.')
161 162
163 - def hasZeoBackup(self):
164 repozoDir = os.path.join(self.tempDir, 'repozo') 165 return os.path.isdir(repozoDir)
166
167 - def hasSqlBackup(self):
168 return bool(self.getSqlFile('zodb.sql'))
169
170 - def hasZODBBackup(self):
171 return self.hasZeoBackup() or self.hasSqlBackup()
172
173 - def restoreZODB(self):
174 # Relstorage may have already loaded items into the cache in the 175 # initial connection to the database. We have to expire everything 176 # in the cache in order to prevent errors with overlapping 177 # transactions from the backup. 178 if self.options.cacheservers: 179 self.flush_memcached(self.options.cacheservers.split()) 180 if self.hasSqlBackup(): 181 self.restoreZODBSQL() 182 elif self.hasZeoBackup(): 183 self.restoreZODBZEO()
184
185 - def restoreZODBSQL(self):
186 zodbSql = self.getSqlFile('zodb.sql') 187 if not zodbSql: 188 self.msg('This archive does not contain a ZODB backup.') 189 return 190 self.msg('Restoring ZODB database.') 191 self.restoreMySqlDb(self.options.host, self.options.port, 192 self.options.mysqldb, self.options.mysqluser, 193 self.getPassArg('mysqlpasswd'), zodbSql)
194
195 - def restoreZODBZEO(self):
196 repozoDir = os.path.join(self.tempDir, 'repozo') 197 tempFilePath = os.path.join(self.tempDir, 'Data.fs') 198 tempZodbConvert = os.path.join(self.tempDir, 'convert.conf') 199 200 self.msg('Restoring ZEO backup into MySQL.') 201 202 # Create a Data.fs from the repozo backup 203 cmd = [] 204 cmd.append(binPath('repozo')) 205 cmd.append('--recover') 206 cmd.append('--repository') 207 cmd.append(repozoDir) 208 cmd.append('--output') 209 cmd.append(tempFilePath) 210 211 rc = subprocess.call(cmd, stdout=PIPE, stderr=PIPE) 212 if rc: 213 return -1 214 215 # Now we have a Data.fs, restore into MySQL with zodbconvert 216 zodbconvert_conf = open(tempZodbConvert, 'w') 217 zodbconvert_conf.write('<filestorage source>\n') 218 zodbconvert_conf.write(' path %s\n' % tempFilePath) 219 zodbconvert_conf.write('</filestorage>\n\n') 220 221 zodbconvert_conf.write('<relstorage destination>\n') 222 zodbconvert_conf.write(' <mysql>\n') 223 zodbconvert_conf.write(' host %s\n' % self.options.host) 224 zodbconvert_conf.write(' port %s\n' % self.options.port) 225 zodbconvert_conf.write(' db %s\n' % self.options.mysqldb) 226 zodbconvert_conf.write(' user %s\n' % self.options.mysqluser) 227 zodbconvert_conf.write(' passwd %s\n' % self.options.mysqlpasswd or '') 228 zodbconvert_conf.write(' </mysql>\n') 229 zodbconvert_conf.write('</relstorage>\n') 230 zodbconvert_conf.close() 231 232 rc = subprocess.call(['zodbconvert', '--clear', tempZodbConvert], 233 stdout=PIPE, stderr=PIPE) 234 if rc: 235 return -1
236 237
238 - def restoreEtcFiles(self):
239 self.msg('Restoring config files.') 240 cmd = 'cp -p %s %s' % (os.path.join(zenPath('etc'), 'global.conf'), self.tempDir) 241 if os.system(cmd): return -1 242 cmd = 'rm -rf %s' % zenPath('etc') 243 if os.system(cmd): return -1 244 cmd = 'tar Cxf %s %s' % ( 245 zenPath(), 246 os.path.join(self.tempDir, 'etc.tar') 247 ) 248 if os.system(cmd): return -1 249 if not os.path.exists(os.path.join(zenPath('etc'), 'global.conf')): 250 self.msg('Restoring default global.conf') 251 cmd = 'mv %s %s' % (os.path.join(self.tempDir, 'global.conf'), zenPath('etc')) 252 if os.system(cmd): return -1
253
254 - def restoreZenPacks(self):
255 self.msg('Restoring ZenPacks.') 256 cmd = 'rm -rf %s' % zenPath('ZenPacks') 257 if os.system(cmd): return -1 258 cmd = 'tar Cxf %s %s' % ( 259 zenPath(), 260 os.path.join(self.tempDir, 'ZenPacks.tar')) 261 if os.system(cmd): return -1 262 # restore bin dir when restoring zenpacks 263 #make sure bin dir is in tar 264 tempBin = os.path.join(self.tempDir, 'bin.tar') 265 if os.path.isfile(tempBin): 266 self.msg('Restoring bin dir.') 267 #k option prevents overwriting existing bin files 268 cmd = ['tar', 'Cxfk', zenPath(), 269 os.path.join(self.tempDir, 'bin.tar')] 270 self.runCommand(cmd)
271
272 - def restoreZenPackContents(self):
273 dmd = ZCmdBase(noopts=True).dmd 274 self.log.info("Restoring ZenPack contents.") 275 for pack in dmd.ZenPackManager.packs(): 276 pack.restore(self.tempDir, self.log) 277 self.log.info("ZenPack contents restored.")
278
279 - def restorePerfData(self):
280 cmd = 'rm -rf %s' % os.path.join(zenPath(), 'perf') 281 if os.system(cmd): return -1 282 self.msg('Restoring performance data.') 283 cmd = 'tar Cxf %s %s' % ( 284 zenPath(), 285 os.path.join(self.tempDir, 'perf.tar')) 286 if os.system(cmd): return -1
287
288 - def doRestore(self):
289 """ 290 Restore from a previous backup 291 """ 292 if self.options.file and self.options.dir: 293 sys.stderr.write('You cannot specify both --file and --dir.\n') 294 sys.exit(-1) 295 elif not self.options.file and not self.options.dir: 296 sys.stderr.write('You must specify either --file or --dir.\n') 297 sys.exit(-1) 298 299 # Maybe check to see if zeo is up and tell user to quit zenoss first 300 301 rootTempDir = '' 302 if self.options.file: 303 if not os.path.isfile(self.options.file): 304 sys.stderr.write('The specified backup file does not exist: %s\n' % 305 self.options.file) 306 sys.exit(-1) 307 # Create temp dir and untar backup into it 308 self.msg('Unpacking backup file') 309 rootTempDir = self.getTempDir() 310 cmd = 'tar xzfC %s %s' % (self.options.file, rootTempDir) 311 if os.system(cmd): return -1 312 self.tempDir = os.path.join(rootTempDir, BACKUP_DIR) 313 else: 314 self.msg('Using %s as source of restore' % self.options.dir) 315 if not os.path.isdir(self.options.dir): 316 sys.stderr.write('The specified backup directory does not exist:' 317 ' %s\n' % self.options.dir) 318 sys.exit(-1) 319 self.tempDir = self.options.dir 320 321 # Maybe use values from backup file as defaults for self.options. 322 self.getSettings() 323 324 if self.options.zenpacks and not self.hasZODBBackup(): 325 sys.stderr.write('Archive does not contain ZODB backup; cannot' 326 'restore ZenPacks') 327 sys.exit(-1) 328 329 # ZODB 330 if self.hasZODBBackup(): 331 self.restoreZODB() 332 else: 333 self.msg('Archive does not contain a ZODB backup') 334 335 # Configuration 336 self.restoreEtcFiles() 337 338 # ZenPacks 339 if self.options.zenpacks: 340 tempPacks = os.path.join(self.tempDir, 'ZenPacks.tar') 341 if os.path.isfile(tempPacks): 342 self.restoreZenPacks() 343 self.restoreZenPackContents() 344 else: 345 self.msg('Backup contains no ZenPacks.') 346 347 # Performance Data 348 tempPerf = os.path.join(self.tempDir, 'perf.tar') 349 if os.path.isfile(tempPerf): 350 self.restorePerfData() 351 else: 352 self.msg('Backup contains no perf data.') 353 354 # Events 355 if self.options.noEventsDb: 356 self.msg('Skipping the events database.') 357 else: 358 self.restoreZEP() 359 360 # clean up 361 if self.options.file: 362 self.msg('Cleaning up temporary files.') 363 cmd = 'rm -r %s' % rootTempDir 364 if os.system(cmd): return -1 365 366 self.msg('Restore complete.') 367 return 0
368
369 - def flush_memcached(self, cacheservers):
370 self.msg('Flushing memcached cache.') 371 import memcache 372 mc = memcache.Client(cacheservers, debug=0) 373 mc.flush_all() 374 mc.disconnect_all() 375 self.msg('Completed flushing memcached cache.')
376 377 if __name__ == '__main__': 378 zb = ZenRestore() 379 if zb.doRestore(): 380 sys.exit(-1) 381