1
2
3
4
5
6
7
8
9
10
11
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 from subprocess import Popen, PIPE
26 import time
27 import logging
28 import ConfigParser
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
41
43 ZenBackupBase.__init__(self)
44 self.log = logging.getLogger("zenbackup")
45 logging.basicConfig()
46 self.log.setLevel(self.options.logseverity)
47
48 - def runCommand(self, cmd=[], obfuscated_cmd=None):
49 """
50 Execute a command and return the results, displaying pre and
51 post messages.
52
53 @parameter cmd: command to run
54 @type cmd: list
55 @return: results of the command (output, warnings, returncode)
56 """
57 if obfuscated_cmd:
58 self.log.debug(' '.join(obfuscated_cmd))
59 else:
60 self.log.debug(' '.join(cmd))
61
62 proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
63 output, warnings = proc.communicate()
64 if proc.returncode:
65 self.log.error(warnings)
66 self.log.debug(output or 'No output from command')
67 return (output, warnings, proc.returncode)
68
69
71 '''
72 Returns True if Zeo appears to be running, false otherwise.
73
74 @return: whether Zeo is up or not
75 @rtype: boolean
76 '''
77
78
79 cmd = [ binPath('python'), binPath('zeoup.py') ]
80 cmd += '-p 8100 -h localhost'.split()
81 self.log.debug("Can we access ZODB through Zeo?")
82
83 (output, warnings, returncode) = self.runCommand(cmd)
84 if returncode:
85 return False
86 return output.startswith('Elapsed time:')
87
88
90 '''
91 Store the dbname, dbuser, dbpass from saved settings in the Event
92 Manager (ie ZODB) to the 'options' parsed object.
93 '''
94 zcmd = ZCmdBase(noopts=True)
95 zem = zcmd.dmd.ZenEventManager
96 for key, default, zemAttr in CONFIG_FIELDS:
97 if not getattr(self.options, key, None):
98 setattr(self.options, key,
99 getattr(zem, zemAttr, None) or default)
100
101
120
121
123 '''
124 Return string to be used as the -p (including the "-p")
125 to MySQL commands. Overrides the one in ZenBackupBase
126
127 @return: password and flag
128 @rtype: string
129 '''
130 if self.options.dbpass == None:
131 return ''
132 return '--password=%s' % self.options.dbpass
133
135 """
136 Return a name for the backup file or die trying.
137
138 @return: unique name for a backup
139 @rtype: string
140 """
141 def getName(index=0):
142 """
143 Try to create an unique backup file name.
144
145 @return: tar file name
146 @rtype: string
147 """
148 return 'zenbackup_%s%s.tgz' % (date.today().strftime('%Y%m%d'),
149 (index and '_%s' % index) or '')
150 backupDir = zenPath('backups')
151 if not os.path.exists(backupDir):
152 os.mkdir(backupDir, 0750)
153 for i in range(MAX_UNIQUE_NAME_ATTEMPTS):
154 name = os.path.join(backupDir, getName(i))
155 if not os.path.exists(name):
156 break
157 else:
158 self.log.critical('Cannot determine an unique file name to use'
159 ' in the backup directory (%s).' % backupDir +
160 ' Use --outfile to specify location for the backup'
161 ' file.\n')
162 sys.exit(-1)
163 return name
164
165
167 """
168 Basic options setup
169 """
170
171 __pychecker__ = 'no-noeffect no-constCond'
172 ZenBackupBase.buildOptions(self)
173 self.parser.add_option('--dbname',
174 dest='dbname',
175 default=None,
176 help='MySQL events database name.'
177 ' By default this will be fetched from Zenoss'
178 ' unless --dont-fetch-args is set.'),
179 self.parser.add_option('--dbuser',
180 dest='dbuser',
181 default=None,
182 help='MySQL username.'
183 ' By default this will be fetched from Zenoss'
184 ' unless --dont-fetch-args is set.'),
185 self.parser.add_option('--dbpass',
186 dest='dbpass',
187 default=None,
188 help='MySQL password.'
189 ' By default this will be fetched from Zenoss'
190 ' unless --dont-fetch-args is set.'),
191 self.parser.add_option('--dont-fetch-args',
192 dest='fetchArgs',
193 default=True,
194 action='store_false',
195 help='By default dbname, dbuser and dbpass'
196 ' are retrieved from Zenoss if not'
197 ' specified and if Zenoss is available.'
198 ' This disables fetching of these values'
199 ' from Zenoss.')
200 self.parser.add_option('--file',
201 dest="file",
202 default=None,
203 help='Name of file in which the backup will be stored.'
204 ' Backups will by default be placed'
205 ' in $ZENHOME/backups/')
206 self.parser.add_option('--no-eventsdb',
207 dest="noEventsDb",
208 default=False,
209 action='store_true',
210 help='Do not include the events database'
211 ' in the backup.')
212 self.parser.add_option('--no-zodb',
213 dest="noZopeDb",
214 default=False,
215 action='store_true',
216 help='Do not include the ZODB'
217 ' in the backup.')
218 self.parser.add_option('--no-perfdata',
219 dest="noPerfData",
220 default=False,
221 action='store_true',
222 help='Do not include performance data'
223 ' in the backup.')
224 self.parser.add_option('--stdout',
225 dest="stdout",
226 default=False,
227 action='store_true',
228 help='Send backup to stdout instead of to a file.')
229 self.parser.add_option('--save-mysql-access',
230 dest='saveSettings',
231 default=False,
232 action='store_true',
233 help='Include dbname, dbuser and dbpass'
234 ' in the backup'
235 ' file for use during restore.')
236
237 self.parser.remove_option('-v')
238 self.parser.add_option('-v', '--logseverity',
239 dest='logseverity',
240 default=20,
241 type='int',
242 help='Logging severity threshold')
243
244
246 """
247 Backup the MySQL events database
248 """
249 partBeginTime = time.time()
250
251
252 if self.options.fetchArgs and not self.options.noEventsDb:
253 if self.isZeoUp():
254 self.log.info('Getting MySQL dbname, user, password from ZODB.')
255 self.readSettingsFromZeo()
256 else:
257 self.log.error('Unable to get MySQL credentials from ZODB.'
258 ' Zeo may not be available.')
259 self.log.info("Skipping events database backup.")
260 return
261
262 if not self.options.dbname:
263 self.options.dbname = 'events'
264 if not self.options.dbuser:
265 self.options.dbuser = 'zenoss'
266
267
268
269
270 if self.options.saveSettings:
271 self.saveSettings()
272
273 self.log.info('Backing up events database.')
274 cmd_p1 = ['mysqldump', '-u%s' % self.options.dbuser]
275 cmd_p2 = ["--single-transaction", '--routines', self.options.dbname,
276 '--result-file=' + os.path.join(self.tempDir, 'events.sql') ]
277 cmd = cmd_p1 + [self.getPassArg()] + cmd_p2
278 obfuscated_cmd = cmd_p1 + ['*' * len(self.getPassArg())] + cmd_p2
279
280 (output, warnings, returncode) = self.runCommand(cmd, obfuscated_cmd)
281 if returncode:
282 self.log.info("Backup terminated abnormally.")
283 return -1
284
285 partEndTime = time.time()
286 subtotalTime = readable_time(partEndTime - partBeginTime)
287 self.log.info("Backup of events database completed in %s.",
288 subtotalTime)
289
290
292 """
293 Backup the Zope database.
294 """
295 partBeginTime = time.time()
296
297 self.log.info('Backing up the ZODB.')
298 repozoDir = os.path.join(self.tempDir, 'repozo')
299 os.mkdir(repozoDir, 0750)
300 cmd = [binPath('python'), binPath('repozo.py'),
301 '--repository', repozoDir, '--file',
302 zenPath('var', 'Data.fs'),
303 '--backup', '--full' ]
304 (output, warnings, returncode) = self.runCommand(cmd)
305 if returncode:
306 self.log.critical("Backup terminated abnormally.")
307 return -1
308
309 partEndTime = time.time()
310 subtotalTime = readable_time(partEndTime - partBeginTime)
311 self.log.info("Backup of ZODB database completed in %s.", subtotalTime)
312
313
315 """
316 Back up the RRD files storing performance data.
317 """
318 perfDir = zenPath('perf')
319 if not os.path.isdir(perfDir):
320 self.log.warning('%s does not exist, skipping.', perfDir)
321 return
322
323 partBeginTime = time.time()
324
325 self.log.info('Backing up performance data (RRDs).')
326 tarFile = os.path.join(self.tempDir, 'perf.tar')
327 cmd = ['tar', 'cf', tarFile, perfDir]
328 (output, warnings, returncode) = self.runCommand(cmd)
329 if returncode:
330 self.log.critical("Backup terminated abnormally.")
331 return -1
332
333 partEndTime = time.time()
334 subtotalTime = readable_time(partEndTime - partBeginTime)
335 self.log.info("Backup of performance data completed in %s.",
336 subtotalTime )
337
338
340 """
341 Gather all of the other data into one nice, neat file for easy
342 tracking.
343 """
344 self.log.info('Packaging backup file.')
345 if self.options.file:
346 outfile = self.options.file
347 else:
348 outfile = self.getDefaultBackupFile()
349 tempHead, tempTail = os.path.split(self.tempDir)
350 tarFile = outfile
351 if self.options.stdout:
352 tarFile = '-'
353 cmd = ['tar', 'czfC', tarFile, tempHead, tempTail]
354 (output, warnings, returncode) = self.runCommand(cmd)
355 if returncode:
356 self.log.critical("Backup terminated abnormally.")
357 return -1
358 self.log.info('Backup written to %s' % outfile)
359
360
362 """
363 Remove temporary files in staging directory.
364 """
365 self.log.info('Cleaning up staging directory %s' % self.rootTempDir)
366 cmd = ['rm', '-r', self.rootTempDir]
367 (output, warnings, returncode) = self.runCommand(cmd)
368 if returncode:
369 self.log.critical("Backup terminated abnormally.")
370 return -1
371
372
374 '''
375 Create a backup of the data and configuration for a Zenoss install.
376 '''
377 backupBeginTime = time.time()
378
379
380 self.rootTempDir = self.getTempDir()
381 self.tempDir = os.path.join(self.rootTempDir, BACKUP_DIR)
382 self.log.debug("Use %s as a staging directory for the backup", self.tempDir)
383 os.mkdir(self.tempDir, 0750)
384
385 if self.options.noEventsDb:
386 self.log.info('Skipping backup of events database.')
387 else:
388 self.backupEventsDatabase()
389
390 if self.options.noZopeDb:
391 self.log.info('Skipping backup of ZODB.')
392 else:
393 self.backupZODB()
394
395
396 self.log.info('Backing up config files.')
397 etcTar = tarfile.open(os.path.join(self.tempDir, 'etc.tar'), 'w')
398 etcTar.add(zenPath('etc'), 'etc')
399 etcTar.close()
400 self.log.info("Backup of config files completed.")
401
402
403 self.log.info('Backing up ZenPacks.')
404 etcTar = tarfile.open(os.path.join(self.tempDir, 'ZenPacks.tar'), 'w')
405 etcTar.add(zenPath('ZenPacks'), 'ZenPacks')
406 etcTar.close()
407 self.log.info("Backup of ZenPacks completed.")
408
409 if self.options.noPerfData:
410 self.log.info('Skipping backup of performance data.')
411 else:
412 self.backupPerfData()
413
414
415 self.packageStagingBackups()
416
417 self.cleanupTempDir()
418
419 backupEndTime = time.time()
420 totalBackupTime = readable_time(backupEndTime - backupBeginTime)
421 self.log.info('Backup completed successfully in %s.', totalBackupTime)
422 return 0
423
424
425 if __name__ == '__main__':
426 zb = ZenBackup()
427 if zb.makeBackup():
428 sys.exit(-1)
429