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

Source Code for Module Products.ZenUtils.ZenBackup

  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   
 16  __doc__='''zenbackup 
 17   
 18  Creates backup of Zope data files, Zenoss conf files and the events database. 
 19  ''' 
 20   
 21  import sys 
 22  import os 
 23  import os.path 
 24  from datetime import date 
 25  import time 
 26  import logging 
 27  import ConfigParser 
 28  import subprocess 
 29  import tarfile 
 30   
 31  import Globals 
 32  from ZCmdBase import ZCmdBase 
 33  from Products.ZenUtils.Utils import zenPath, binPath, readable_time 
 34  from ZenBackupBase import * 
 35   
 36   
 37  MAX_UNIQUE_NAME_ATTEMPTS = 1000 
 38   
 39   
40 -class ZenBackup(ZenBackupBase):
41
42 - def __init__(self, argv):
43 # Store sys.argv so we can restore it for ZCmdBase. 44 self.argv = argv 45 46 ZenBackupBase.__init__(self) 47 self.log = logging.getLogger("zenbackup") 48 logging.basicConfig() 49 self.log.setLevel(self.options.logseverity)
50
51 - def isZeoUp(self):
52 ''' 53 Returns True if Zeo appears to be running, false otherwise. 54 55 @return: whether Zeo is up or not 56 @rtype: boolean 57 ''' 58 import ZEO 59 zeohome = os.path.dirname(ZEO.__file__) 60 cmd = [ binPath('python'), 61 os.path.join(zeohome, 'scripts', 'zeoup.py')] 62 cmd += '-p 8100 -h localhost'.split() 63 self.log.debug("Can we access ZODB through Zeo?") 64 65 (output, warnings, returncode) = self.runCommand(cmd) 66 if returncode: 67 return False 68 return output.startswith('Elapsed time:')
69
70 - def readZEPSettings(self):
71 ''' 72 Read in and store the ZEP DB configuration options from 73 'zeneventserver.conf' to the 'options' object. 74 ''' 75 zepconf = os.path.join(zenPath('etc'), 'zeneventserver.conf') 76 zepsettings = {'username': 'zepdbuser', 'hostname': 'zepdbhost', 77 'dbname': 'zepdbname', 'password': 'zepdbpass', 78 'port': 'zepdbport'} 79 80 with open(zepconf) as f_zepconf: 81 for line in f_zepconf: 82 line = line.strip() 83 if line.startswith('zep.jdbc.'): 84 (key, _ignored, value) = line[9:].partition('=') 85 if key in zepsettings: 86 setattr(self.options, zepsettings[key], value)
87
88 - def saveSettings(self):
89 ''' 90 Save the database credentials to a file for use during restore. 91 ''' 92 config = ConfigParser.SafeConfigParser() 93 config.add_section(CONFIG_SECTION) 94 95 config.set(CONFIG_SECTION, 'host', self.options.host) 96 config.set(CONFIG_SECTION, 'port', self.options.port) 97 config.set(CONFIG_SECTION, 'mysqldb', self.options.mysqldb) 98 config.set(CONFIG_SECTION, 'mysqluser', self.options.mysqluser) 99 config.set(CONFIG_SECTION, 'mysqlpasswd', self.options.mysqlpasswd) 100 101 config.set(CONFIG_SECTION, 'zepdbhost', self.options.zepdbhost) 102 config.set(CONFIG_SECTION, 'zepdbport', self.options.zepdbport) 103 config.set(CONFIG_SECTION, 'zepdbname', self.options.zepdbname) 104 config.set(CONFIG_SECTION, 'zepdbuser', self.options.zepdbuser) 105 config.set(CONFIG_SECTION, 'zepdbpass', self.options.zepdbpass) 106 107 creds_file = os.path.join(self.tempDir, CONFIG_FILE) 108 self.log.debug("Writing MySQL credentials to %s", creds_file) 109 f = open(creds_file, 'w') 110 try: 111 config.write(f) 112 finally: 113 f.close()
114 115
116 - def getDefaultBackupFile(self):
117 """ 118 Return a name for the backup file or die trying. 119 120 @return: unique name for a backup 121 @rtype: string 122 """ 123 def getName(index=0): 124 """ 125 Try to create an unique backup file name. 126 127 @return: tar file name 128 @rtype: string 129 """ 130 return 'zenbackup_%s%s.tgz' % (date.today().strftime('%Y%m%d'), 131 (index and '_%s' % index) or '')
132 backupDir = zenPath('backups') 133 if not os.path.exists(backupDir): 134 os.mkdir(backupDir, 0750) 135 for i in range(MAX_UNIQUE_NAME_ATTEMPTS): 136 name = os.path.join(backupDir, getName(i)) 137 if not os.path.exists(name): 138 break 139 else: 140 self.log.critical('Cannot determine an unique file name to use' 141 ' in the backup directory (%s).' % backupDir + 142 ' Use --outfile to specify location for the backup' 143 ' file.\n') 144 sys.exit(-1) 145 return name
146 147
148 - def buildOptions(self):
149 """ 150 Basic options setup 151 """ 152 # pychecker can't handle strings made of multiple tokens 153 __pychecker__ = 'no-noeffect no-constCond' 154 ZenBackupBase.buildOptions(self) 155 self.parser.add_option('--dont-fetch-args', 156 dest='fetchArgs', 157 default=True, 158 action='store_false', 159 help='By default MySQL connection information' 160 ' is retrieved from Zenoss if not' 161 ' specified and if Zenoss is available.' 162 ' This disables fetching of these values' 163 ' from Zenoss.') 164 self.parser.add_option('--file', 165 dest="file", 166 default=None, 167 help='Name of file in which the backup will be stored.' 168 ' Backups will by default be placed' 169 ' in $ZENHOME/backups/') 170 self.parser.add_option('--no-eventsdb', 171 dest="noEventsDb", 172 default=False, 173 action='store_true', 174 help='Do not include the events database' 175 ' in the backup.') 176 self.parser.add_option('--no-zodb', 177 dest="noZopeDb", 178 default=False, 179 action='store_true', 180 help='Do not include the ZODB' 181 ' in the backup.') 182 self.parser.add_option('--no-perfdata', 183 dest="noPerfData", 184 default=False, 185 action='store_true', 186 help='Do not include performance data' 187 ' in the backup.') 188 self.parser.add_option('--stdout', 189 dest="stdout", 190 default=False, 191 action='store_true', 192 help='Send backup to stdout instead of to a file.') 193 self.parser.add_option('--no-save-mysql-access', 194 dest='saveSettings', 195 default=True, 196 action='store_false', 197 help='Do not include mysqldb, mysqluser, mysqlpasswd, zepdbname, zepdbuser, and zendbpass' 198 ' in the backup' 199 ' file for use during restore.') 200 201 self.parser.remove_option('-v') 202 self.parser.add_option('-v', '--logseverity', 203 dest='logseverity', 204 default=20, 205 type='int', 206 help='Logging severity threshold')
207 208
209 - def backupMySqlDb(self, host, port, db, user, passwdType, sqlFile):
210 cmd_p1 = ['mysqldump', '-u%s' % user] 211 cmd_p2 = ['--single-transaction', db] 212 if host and host != 'localhost': 213 cmd_p2.append('-h%s' % host) 214 if self.options.compressTransport: 215 cmd_p2.append('--compress') 216 if port and port != '3306': 217 cmd_p2.append('--port=%s' % port) 218 219 cmd = cmd_p1 + self.getPassArg(passwdType) + cmd_p2 220 obfuscated_cmd = cmd_p1 + ['*' * 8] + cmd_p2 221 222 self.log.debug(' '.join(obfuscated_cmd)) 223 224 with open(os.path.join(self.tempDir, sqlFile), 'wb') as zipfile: 225 mysqldump = subprocess.Popen(cmd, stdout=subprocess.PIPE) 226 gzip = subprocess.Popen(['gzip', '-c'], stdin=mysqldump.stdout, stdout=zipfile) 227 mysqldump.wait() 228 gzip.wait() 229 if gzip.returncode or mysqldump.returncode: 230 self.log.critical("Backup of (%s) terminated abnormally." % sqlFile) 231 return -1
232 233
234 - def backupZEP(self):
235 ''' 236 Backup ZEP 237 ''' 238 partBeginTime = time.time() 239 240 # Setup defaults for db info 241 if self.options.fetchArgs and not self.options.noEventsDb: 242 self.log.info('Getting ZEP dbname, user, password, port from configuration files.') 243 self.readZEPSettings() 244 245 self.log.info('Backing up the ZEP database.') 246 if self.options.saveSettings: 247 self.saveSettings() 248 249 self.backupMySqlDb(self.options.zepdbhost, self.options.zepdbport, 250 self.options.zepdbname, self.options.zepdbuser, 251 'zepdbpass', 'zep.sql.gz') 252 253 partEndTime = time.time() 254 subtotalTime = readable_time(partEndTime - partBeginTime) 255 self.log.info("Backup of ZEP database completed in %s.", subtotalTime) 256 257 self.log.info('Backing up ZEP indexes.') 258 zepTar = tarfile.open(os.path.join(self.tempDir, 'zep.tar'), 'w') 259 zepTar.add(os.path.join(zenPath('var'), 'zeneventserver'), 'zeneventserver') 260 zepTar.close() 261 self.log.info('Backing up ZEP indexes completed.')
262
263 - def backupZenPacks(self):
264 """ 265 Backup the zenpacks dir 266 """ 267 #can only copy zenpacks backups if ZEO is backed up 268 if not self.options.noZopeDb and os.path.isdir(zenPath('ZenPacks')): 269 # Copy /ZenPacks to backup dir 270 self.log.info('Backing up ZenPacks.') 271 etcTar = tarfile.open(os.path.join(self.tempDir, 'ZenPacks.tar'), 'w') 272 etcTar.dereference = True 273 etcTar.add(zenPath('ZenPacks'), 'ZenPacks') 274 etcTar.close() 275 self.log.info("Backup of ZenPacks completed.") 276 # add /bin dir if backing up zenpacks 277 # Copy /bin to backup dir 278 self.log.info('Backing up bin dir.') 279 etcTar = tarfile.open(os.path.join(self.tempDir, 'bin.tar'), 'w') 280 etcTar.dereference = True 281 etcTar.add(zenPath('bin'), 'bin') 282 etcTar.close() 283 self.log.info("Backup of bin completed.")
284
285 - def backupZenPackContents(self):
286 dmd = ZCmdBase(noopts=True).dmd 287 self.log.info("Backing up ZenPack contents.") 288 for pack in dmd.ZenPackManager.packs(): 289 pack.backup(self.tempDir, self.log) 290 self.log.info("Backup of ZenPack contents complete.")
291
292 - def backupZODB(self):
293 """ 294 Backup the Zope database. 295 """ 296 partBeginTime = time.time() 297 298 self.log.info('Backing up the ZODB.') 299 if self.options.saveSettings: 300 self.saveSettings() 301 302 self.backupMySqlDb(self.options.host, self.options.port, 303 self.options.mysqldb, self.options.mysqluser, 304 'mysqlpasswd', 'zodb.sql.gz') 305 306 partEndTime = time.time() 307 subtotalTime = readable_time(partEndTime - partBeginTime) 308 self.log.info("Backup of ZODB database completed in %s.", subtotalTime)
309 310
311 - def backupPerfData(self):
312 """ 313 Back up the RRD files storing performance data. 314 """ 315 perfDir = zenPath('perf') 316 if not os.path.isdir(perfDir): 317 self.log.warning('%s does not exist, skipping.', perfDir) 318 return 319 320 partBeginTime = time.time() 321 322 self.log.info('Backing up performance data (RRDs).') 323 tarFile = os.path.join(self.tempDir, 'perf.tar') 324 #will change dir to ZENHOME so that tar dir structure is relative 325 cmd = ['tar', 'chfC', tarFile, zenPath(), 'perf'] 326 (output, warnings, returncode) = self.runCommand(cmd) 327 if returncode: 328 self.log.critical("Backup terminated abnormally.") 329 return -1 330 331 partEndTime = time.time() 332 subtotalTime = readable_time(partEndTime - partBeginTime) 333 self.log.info("Backup of performance data completed in %s.", 334 subtotalTime )
335 336
337 - def packageStagingBackups(self):
338 """ 339 Gather all of the other data into one nice, neat file for easy 340 tracking. 341 """ 342 self.log.info('Packaging backup file.') 343 if self.options.file: 344 outfile = self.options.file 345 else: 346 outfile = self.getDefaultBackupFile() 347 tempHead, tempTail = os.path.split(self.tempDir) 348 tarFile = outfile 349 if self.options.stdout: 350 tarFile = '-' 351 cmd = ['tar', 'czfC', tarFile, tempHead, tempTail] 352 (output, warnings, returncode) = self.runCommand(cmd) 353 if returncode: 354 self.log.critical("Backup terminated abnormally.") 355 return -1 356 self.log.info('Backup written to %s' % outfile)
357 358
359 - def cleanupTempDir(self):
360 """ 361 Remove temporary files in staging directory. 362 """ 363 self.log.info('Cleaning up staging directory %s' % self.rootTempDir) 364 cmd = ['rm', '-r', self.rootTempDir] 365 (output, warnings, returncode) = self.runCommand(cmd) 366 if returncode: 367 self.log.critical("Backup terminated abnormally.") 368 return -1
369 370
371 - def makeBackup(self):
372 ''' 373 Create a backup of the data and configuration for a Zenoss install. 374 ''' 375 backupBeginTime = time.time() 376 377 # Create temp backup dir 378 self.rootTempDir = self.getTempDir() 379 self.tempDir = os.path.join(self.rootTempDir, BACKUP_DIR) 380 self.log.debug("Use %s as a staging directory for the backup", self.tempDir) 381 os.mkdir(self.tempDir, 0750) 382 383 if self.options.noEventsDb: 384 self.log.info('Skipping backup of events database.') 385 else: 386 self.backupZEP() 387 388 if self.options.noZopeDb: 389 self.log.info('Skipping backup of ZODB.') 390 else: 391 self.backupZODB() 392 393 # Copy /etc to backup dir (except for sockets) 394 self.log.info('Backing up config files.') 395 etcTar = tarfile.open(os.path.join(self.tempDir, 'etc.tar'), 'w') 396 etcTar.dereference = True 397 etcTar.add(zenPath('etc'), 'etc') 398 etcTar.close() 399 self.log.info("Backup of config files completed.") 400 401 self.backupZenPacks() 402 self.backupZenPackContents() 403 404 if self.options.noPerfData: 405 self.log.info('Skipping backup of performance data.') 406 else: 407 self.backupPerfData() 408 409 # tar, gzip and send to outfile 410 self.packageStagingBackups() 411 412 self.cleanupTempDir() 413 414 backupEndTime = time.time() 415 totalBackupTime = readable_time(backupEndTime - backupBeginTime) 416 self.log.info('Backup completed successfully in %s.', totalBackupTime) 417 return 0
418 419 420 if __name__ == '__main__': 421 zb = ZenBackup(sys.argv) 422 if zb.makeBackup(): 423 sys.exit(-1) 424