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 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
41
43
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
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
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
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
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
149 """
150 Basic options setup
151 """
152
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
235 '''
236 Backup ZEP
237 '''
238 partBeginTime = time.time()
239
240
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
264 """
265 Backup the zenpacks dir
266 """
267
268 if not self.options.noZopeDb and os.path.isdir(zenPath('ZenPacks')):
269
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
277
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
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
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
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
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
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
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
372 '''
373 Create a backup of the data and configuration for a Zenoss install.
374 '''
375 backupBeginTime = time.time()
376
377
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
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
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