PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/arritranco/backups/utils/tsm/tsmclient.py

https://github.com/m87carlson/arritranco
Python | 543 lines | 538 code | 3 blank | 2 comment | 0 complexity | 773a560c3b3544a49fc1e2c183d9d6a8 MD5 | raw file
  1. #! /usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import platform
  4. from subprocess import Popen
  5. from subprocess import STDOUT
  6. from subprocess import PIPE
  7. import datetime
  8. import time
  9. import calendar
  10. import getpass
  11. import os
  12. import sys
  13. import locale
  14. import urllib2
  15. import csv
  16. import simplejson as json
  17. class TSMBaseParser:
  18. def __init__(self):
  19. self.data = []
  20. self.error = []
  21. self.serverCommand = ''
  22. self.returnCode = ''
  23. def parse(self, line):
  24. print line
  25. def ANSParser(self, line):
  26. if line.startswith('ANS8000I'):
  27. self.serverCommand = line
  28. elif line.startswith('ANS8002I'):
  29. self.returnCode = line
  30. else:
  31. self.error.append(line)
  32. class TSMQueryRequestParser(TSMBaseParser):
  33. """
  34. tsm: BACKUP-SAN_SERVER1>q req
  35. ANR8352I Requests outstanding:
  36. ANR8373I 015: Fill the bulk entry/exit port of library TS3310_0 with all LTO volumes to be processed within 55 minute(s); issue 'REPLY' along with the
  37. request ID when ready.
  38. """
  39. def __init__(self):
  40. TSMBaseParser.__init__(self)
  41. self.requests = []
  42. def parse(self, line):
  43. line = line.strip()
  44. if line.startswith('ANS'):
  45. self.ANSParser(line)
  46. elif line.startswith('ANR8346I'):
  47. print "No requests are outstanding."
  48. elif line.startswith('ANR8306I'):
  49. self.requests.append(line.split(':')[0].split(' ')[1])
  50. elif line.startswith('ANR8373I'):
  51. self.requests.append(line.split(':')[0].split(' ')[1])
  52. class TSMMountParser(TSMBaseParser):
  53. MAX_DRIVES_USED = 4
  54. def __init__(self):
  55. TSMBaseParser.__init__(self)
  56. self.drivesBussy = 0
  57. def freeDrives(self):
  58. return self.drivesBussy < TSMMountParser.MAX_DRIVES_USED
  59. def parse(self, line):
  60. line = line.strip()
  61. if line.startswith('ANS'):
  62. self.ANSParser(line)
  63. elif line.startswith('ANR2034E'):
  64. self.drivesBussy = 0
  65. elif line.startswith('ANR8329I') or line.startswith('ANR8330I') or line.startswith('ANR8379I'):
  66. self.drivesBussy += 1
  67. class TSMLibvolParser(TSMBaseParser):
  68. SKIP_PHASE = 0
  69. DATA_PHASE = 1
  70. def __init__(self):
  71. TSMBaseParser.__init__(self)
  72. self.phase = TSMLibvolParser.SKIP_PHASE
  73. self.catridages = {}
  74. def parse(self, line):
  75. line = line.strip()
  76. if line.startswith('ANS'):
  77. self.ANSParser(line)
  78. if self.phase == TSMLibvolParser.SKIP_PHASE:
  79. if line.startswith('-----'):
  80. self.phase = TSMLibvolParser.DATA_PHASE
  81. elif line == '':
  82. self.phase = TSMLibvolParser.SKIP_PHASE
  83. else:
  84. # La línea debe tener esta pinta, los campos son de ancho fijo
  85. #Library Name Volume Name Status Owner Last Use Home Device Type Element Type
  86. #------------ ----------- ---------------- ---------- --------- ------- ------
  87. #L80 000062 Scratch 1,004 LTO
  88. library = line[0:12].strip()
  89. volumename = line[17:29].strip()
  90. status = line[33:50].strip()
  91. owner = line[54:65].strip()
  92. last_usage = line[69:79].strip()
  93. home = line[83:91].strip()
  94. device_type = line[95:103].strip()
  95. self.catridages["%s_%s" % (library, volumename)] = (volumename, status, owner, last_usage, home, device_type)
  96. class TSMBackupTapeParser(TSMBaseParser):
  97. SKIP_PHASE = 0
  98. DATA_PHASE = 1
  99. def __init__(self):
  100. TSMBaseParser.__init__(self)
  101. self.phase = TSMSelectParser.SKIP_PHASE
  102. def getLast(self, number):
  103. return self.data[-number:]
  104. def parse(self, line):
  105. line = line.strip()
  106. if line.startswith('ANS'):
  107. self.ANSParser(line)
  108. if self.phase == TSMSelectParser.SKIP_PHASE:
  109. if line.startswith('-----'):
  110. self.phase = TSMSelectParser.DATA_PHASE
  111. elif line == '':
  112. self.phase = TSMSelectParser.SKIP_PHASE
  113. else:
  114. # La línea debe tener esta pinta, los campos son de ancho fijo
  115. # 12/13/08 08:14:07 BACKUPFULL 1,026 0 1 LTO3CLASS A00110
  116. date = line[0:19]
  117. tape = line[97:107]
  118. self.data.append("%s (%s)" % (tape, date))
  119. class TSMActLogParser(TSMBaseParser):
  120. SKIP_PHASE = 0
  121. DATA_PHASE = 1
  122. # FILE = None
  123. def __init__(self):
  124. TSMBaseParser.__init__(self)
  125. # if not TSMActLogParser.FILE:
  126. # TSMActLogParser.FILE = open('/tmp/actlog.log', 'w')
  127. self.phase = TSMActLogParser.SKIP_PHASE
  128. self.data = []
  129. self.aux = { 'date':None, 'message':None}
  130. if platform.platform().startswith("Windows"):
  131. self.platform = "Windows"
  132. else:
  133. self.platform = "Linux"
  134. def reset(self):
  135. self.data = []
  136. def getLast(self, number):
  137. return self.data[-number:]
  138. def parse(self, line):
  139. # TSMActLogParser.FILE.write(line + '\n')
  140. if line.startswith('ANS'):
  141. self.ANSParser(line)
  142. if self.phase == TSMActLogParser.SKIP_PHASE:
  143. if line.startswith('-----'):
  144. self.phase = TSMActLogParser.DATA_PHASE
  145. elif self.phase == TSMActLogParser.DATA_PHASE:
  146. if line == '\n':
  147. self.phase = TSMActLogParser.SKIP_PHASE
  148. return
  149. #Date/Time Message
  150. #-------------------- ----------------------------------------------------------
  151. #12/18/08 12:44:34 ANR8437E CHECKOUT LIBVOLUME for volume A00033 in library
  152. # TS3310_0 failed. (SESSION: 4518, PROCESS: 50)
  153. if line.startswith(" "):
  154. self.aux['message'] += ' ' + line.strip()
  155. return
  156. if self.aux['date']:
  157. self.data.append(dict(self.aux))
  158. #print "(%s) -> %s" % (len(self.data), self.aux)
  159. self.aux['date'] = None
  160. if self.platform == "Windows":
  161. self.aux['date'] = datetime.datetime.strptime(line[0:19], '%d.%m.%y %H:%M:%S')
  162. else:
  163. self.aux['date'] = datetime.datetime.strptime(line[0:19], '%m/%d/%y %H:%M:%S')
  164. self.aux['message'] = line[25:]
  165. else:
  166. print line
  167. class TSMProcessParser(TSMBaseParser):
  168. SKIP_PHASE = 0
  169. DATA_PHASE = 1
  170. def __init__(self):
  171. TSMBaseParser.__init__(self)
  172. self.phase = TSMProcessParser.SKIP_PHASE
  173. self.procesos = {}
  174. self.last_id = None
  175. def parse(self, line):
  176. if line.startswith('ANS'):
  177. self.ANSParser(line)
  178. if line.startswith('ANS8002I'):
  179. self.phase = TSMProcessParser.SKIP_PHASE
  180. # for id in self.procesos.keys():
  181. # print "id: %s ->" % id, self.procesos[id]
  182. return
  183. if line.startswith('ANR0944E'):
  184. # ANR0944E QUERY PROCESS: No active processes found.
  185. pass
  186. if self.phase == TSMProcessParser.SKIP_PHASE:
  187. if line.startswith('-------'):
  188. self.phase = TSMProcessParser.DATA_PHASE
  189. return
  190. elif self.phase == TSMProcessParser.DATA_PHASE:
  191. # Campos de longitud fija ...
  192. #-------- -------------------- -------------------------------------------------
  193. # 48 Space Reclamation Offsite Volume(s) (storage pool LTO3POOL_COPY),
  194. # Moved Files: 23, Moved Bytes: 4,149,250,332,
  195. # Unreadable Files: 0, Unreadable Bytes: 0.
  196. # Current Physical File (bytes): 1,165,593,708
  197. # Current input volume: A00079. Current output
  198. # volume: A00068.
  199. if line.strip() == '':
  200. return
  201. if line.startswith(" "):
  202. self.procesos[self.last_id]['status'] += line.strip()
  203. return
  204. self.last_id = int(line[0:8].strip())
  205. description = line[8:32].strip()
  206. status = line[32:].strip()
  207. self.procesos[self.last_id] = {
  208. 'description':description,
  209. 'status':status}
  210. elif line == '':
  211. self.phase = TSMProcessParser.SKIP_PHASE
  212. else:
  213. print "linea: %s" % line
  214. class TSMSelectParser(TSMBaseParser):
  215. SKIP_PHASE = 0
  216. DATA_PHASE = 1
  217. def __init__(self):
  218. TSMBaseParser.__init__(self)
  219. self.phase = TSMSelectParser.SKIP_PHASE
  220. def parse(self, line):
  221. line = line.strip()
  222. if line.startswith('ANS'):
  223. self.ANSParser(line)
  224. if self.phase == TSMSelectParser.SKIP_PHASE:
  225. if line.startswith('-----'):
  226. self.phase = TSMSelectParser.DATA_PHASE
  227. elif line == '':
  228. self.phase = TSMSelectParser.SKIP_PHASE
  229. else:
  230. self.data.append(line)
  231. class TSM_CSVSelectParser(TSMBaseParser):
  232. SKIP_PHASE = 0
  233. DATA_PHASE = 1
  234. def __init__(self):
  235. TSMBaseParser.__init__(self)
  236. self.phase = TSMSelectParser.SKIP_PHASE
  237. def parse(self, line):
  238. line = line.strip()
  239. if line.startswith('ANS'):
  240. self.ANSParser(line)
  241. if self.phase == TSMSelectParser.SKIP_PHASE:
  242. if self.serverCommand != '':
  243. self.phase = TSMSelectParser.DATA_PHASE
  244. elif line == '':
  245. self.phase = TSMSelectParser.SKIP_PHASE
  246. elif line.startswith('ANR'):
  247. self.phase = TSMSelectParser.SKIP_PHASE
  248. else:
  249. for row in csv.reader([line]):
  250. self.data.append(row)
  251. class TSMClient:
  252. """
  253. Clase para interactuar con el cliente de línea de comandos de Tivoli Storage Manager
  254. """
  255. WINDOWS = False
  256. TSM_INVALID_PASSWORD=137
  257. TSM_UNEXPECTED_SQL=3
  258. def __init__(self, dsmcPath = None, log = None, username = 'admin', server = None):
  259. if dsmcPath is not None:
  260. self.dsmcPath = str(dsmcPath)
  261. self.log = log
  262. self.server = server
  263. self.username = username
  264. self.password = None
  265. if platform.platform().startswith("Windows"):
  266. self.loguea("Corriendo en Windows")
  267. TSMClient.WINDOWS = True
  268. else:
  269. self.loguea("Corriendo en Linux")
  270. def loguea(self, msg):
  271. if self.log is not None:
  272. self.log.addMSG(msg)
  273. def getDsmcPath(self):
  274. return self.dsmcPath
  275. def setDsmcPath(self, dsmcPath):
  276. self.dsmcPath = str(dsmcPath)
  277. def setUsername(self, username):
  278. self.username = str(username)
  279. def getUsername(self):
  280. return self.username
  281. def setPassword(self, password):
  282. self.password = str(password)
  283. def passwordDialog(self):
  284. self.setPassword(getpass.getpass('Introduce el password para el TSM: '))
  285. return True
  286. def run_TSMcommand(self, command, parser = None):
  287. "-id=<usuario/> -password=<password/> <command/>"
  288. if self.password is None and not self.passwordDialog():
  289. print "Pon un password cenizo"
  290. return False
  291. if self.dsmcPath is None:
  292. self.loguea("No se donde está el ejecutable del dsmcadm")
  293. return False
  294. self.loguea("\n\n\t--------- Consultando TSM (%s) -------\n\n" % datetime.datetime.now().strftime("%d:%m:%Y %H:%M"))
  295. tsm_args = ' -id=%s -password=%s ' % (self.username, self.password)
  296. if self.server:
  297. tsm_args += '-server=%s ' % self.server
  298. tsm_args += command
  299. if TSMClient.WINDOWS:
  300. executable = "dsmadmc.exe"
  301. cmd = executable + tsm_args
  302. else:
  303. os.environ['LANG'] = 'C'
  304. os.environ['LD_LIBRARY_PATH'] = os.path.join(*(['/'] + self.dsmcPath.split('/')[:-3] + ['api', 'bin']))
  305. executable = "dsmadmc"
  306. cmd = self.dsmcPath + executable + tsm_args
  307. self.run_command(cmd, self.dsmcPath, executable, parser)
  308. return True
  309. def run_TSM_CSV_Command(self, command, parser = None):
  310. return self.run_TSMcommand(' -commadelimited ' + command, parser)
  311. def run_command(self, command, cwd, executable, parser = None):
  312. self.loguea(command)
  313. try:
  314. if TSMClient.WINDOWS:
  315. os.chdir(cwd)
  316. else:
  317. executable = os.path.join(cwd, executable)
  318. cwd = None
  319. process = Popen(command.split(), executable=executable, stderr = STDOUT, stdout = PIPE, cwd = cwd)
  320. pipe = process.stdout
  321. while True:
  322. line = pipe.readline()
  323. if not line: break
  324. if TSMClient.WINDOWS:
  325. line = unicode(line.decode('cp850'))
  326. #line = line.strip()
  327. self.loguea("%s" % line[:-1])
  328. if parser is not None:
  329. parser(line)
  330. else:
  331. print line
  332. except OSError, e:
  333. print e
  334. sys.exit(0)
  335. process.wait()
  336. if process.returncode is None:
  337. print "El comando no ha terminado correctamente."
  338. elif process.returncode == TSMClient.TSM_INVALID_PASSWORD:
  339. print "Nombre de usuario o password incorrectos."
  340. self.password = None
  341. elif process.returncode == TSMClient.TSM_UNEXPECTED_SQL:
  342. print "Sentencia SQL incorrecta, consulte el log para obtener detalles."
  343. def getNodeNameFromIP(self, ip):
  344. query = "select NODE_NAME from nodes where TCP_ADDRESS='%s'" % ip
  345. parser = TSM_CSVSelectParser()
  346. self.run_TSM_CSV_Command(query, parser.parse)
  347. if len(parser.data) == 1:
  348. return parser.data[0][0]
  349. return None
  350. def getIPFromNodeName(self, node_name):
  351. query = "select TCP_ADDRESS from nodes where NODE_NAME='%s'" % node_name
  352. parser = TSM_CSVSelectParser()
  353. self.run_TSM_CSV_Command(query, parser.parse)
  354. if len(parser.data) == 1:
  355. return parser.data[0][0]
  356. return None
  357. def getNodeEvents(self, node, since):
  358. """
  359. tsm: BACKUP-SAN_SERVER1>select * from events where
  360. scheduled_start>'2010-05-27 0:0:0' and node_name='VMWSUS' and status <>
  361. 'Future'
  362. 0 SCHEDULED_START: 2010-05-27 22:00:00.000000
  363. 1 ACTUAL_START: 2010-05-27 22:09:20.000000
  364. 2 DOMAIN_NAME: DIARIA
  365. 3 SCHEDULE_NAME: DIARIA
  366. 4 NODE_NAME: VMWSUS
  367. 5 STATUS: Completed
  368. 6 RESULT: 0
  369. 7 REASON:
  370. 8 COMPLETED: 2010-05-27 22:10:24.000000
  371. """
  372. since_str = since.strftime('%Y-%m-%d %H:%M:%S')
  373. query = "select * from events where scheduled_start>'%s' and node_name='%s' and status <> 'Future'" % (since_str, node)
  374. parser = TSM_CSVSelectParser()
  375. self.run_TSM_CSV_Command(query, parser.parse)
  376. return parser.data
  377. def getBackupInformation(self, node_name, since):
  378. since_date_str = since.strftime('%m/%d/%Y')
  379. since_time_str = since.strftime('%H:%M:%S')
  380. since_time_str = '00:00:00'
  381. query = "query actlog node=%s begindate='%s' begintime='%s' originator=client msgno=4952" % (
  382. node_name,
  383. since_date_str,
  384. since_time_str
  385. )
  386. parser = TSM_CSVSelectParser()
  387. self.run_TSM_CSV_Command(query, parser.parse)
  388. backups = []
  389. for backup in parser.data:
  390. bckp = {}
  391. bckp['start time'] = backup[0]
  392. log_msg = backup[1].split()
  393. session_no = int(log_msg[-1][:-1])
  394. bckp['session'] = session_no
  395. bckp['objects inspected'] = int(log_msg[-3].replace('.', '').replace(',', ''))
  396. query = "query actlog node=%s begindate='%s' begintime='%s' originator=client SESSNUM=%d" % (
  397. node_name,
  398. since_date_str,
  399. since_time_str,
  400. session_no
  401. )
  402. session_parser = TSM_CSVSelectParser()
  403. self.run_TSM_CSV_Command(query, session_parser.parse)
  404. for info in session_parser.data:
  405. key = None
  406. if info[1].startswith('ANE4954I'):
  407. key = 'objects backed up'
  408. elif info[1].startswith('ANE4957I'):
  409. key = 'objects deleted'
  410. elif info[1].startswith('ANE4958I'):
  411. key = 'objects updated'
  412. elif info[1].startswith('ANE4959I'):
  413. key = 'objects failed'
  414. elif info[1].startswith('ANE4960I'):
  415. key = 'objects rebound'
  416. # elif info[1].startswith('ANE4961I'):
  417. # key = 'bytes transferred'
  418. elif info[1].startswith('ANE4965I'):
  419. key = 'subfile objects'
  420. elif info[1].startswith('ANE4970I'):
  421. key = 'objects expired'
  422. if key:
  423. numero = info[1].split()[-3].replace('.', '').replace(',', '')
  424. bckp[key] = int(numero)
  425. backups.append(bckp)
  426. return backups
  427. def getArchiveInformation(self, node_name, since):
  428. """
  429. NODE_NAME: VCVMWARE
  430. FILESPACE_NAME: \\vcvmware\d$
  431. FILESPACE_ID: 5
  432. TYPE: FILE
  433. HL_NAME: \VMSNAPSHOTS\APPDB.GES.CCTI.ULL.ES-FULLVM\
  434. LL_NAME: SCSI0-0-0-APPDB-S004.VMDK
  435. OBJECT_ID: 277878269
  436. ARCHIVE_DATE: 2010-05-28 21:31:57.000000
  437. OWNER:
  438. DESCRIPTION: 20100528 FullVM en VCVMWARE
  439. CLASS_NAME: ARCHIVE90D
  440. """
  441. query = "select * from archives where TYPE=FILE and NODE_NAME='%s' and YEAR(ARCHIVE_DATE)>=%d and MONTH(ARCHIVE_DATE)>=%d and DAY(ARCHIVE_DATE)>=%d order by archive_date" % (
  442. node_name,
  443. since.year,
  444. since.month,
  445. since.day
  446. )
  447. parser = TSM_CSVSelectParser()
  448. self.run_TSM_CSV_Command(query, parser.parse)
  449. backups = []
  450. for backup in parser.data:
  451. fecha = datetime.datetime(*time.strptime(backup[7].split('.')[0], '%Y-%m-%d %H:%M:%S')[0:5])
  452. b = {
  453. 'fecha': fecha,
  454. 'fichero':backup[4] + backup[5],
  455. 'filespace':backup[1],
  456. 'description':backup[9]
  457. }
  458. backups.append(b)
  459. return backups
  460. if __name__ == "__main__":
  461. # print "hola"
  462. num_dias = 90
  463. hoy = datetime.datetime.now()
  464. fecha_anterior = hoy - datetime.timedelta(days = num_dias)
  465. fecha_anterior = datetime.datetime(2009, 1, 1)
  466. tsm = TSMClient()
  467. parser = TSM_CSVSelectParser()
  468. tsm.setDsmcPath('/opt/tivoli/tsm/client/ba/bin/')
  469. tsm.setUsername('admin')
  470. backups = tsm.getArchiveInformation('VCVMWARE', fecha_anterior)
  471. for b in backups:
  472. dia_uno, max_dias = calendar.monthrange(b['fecha'].year, b['fecha'].month)
  473. max_dias -= 8
  474. if (b['fecha'].month == 2) and (b['fecha'].year == 2010):
  475. continue
  476. if b['fichero'].find('GESWIFI.COM.CCTI.ULL.ES') >= 0:
  477. # print "Geswifi!!! no se borra ... %s --> %s" % (b['fecha'], b['fichero'])
  478. continue
  479. if b['description'].find('20100430') >= 0:
  480. # print "Las copias de abril terminaron en mayo!!! no se borra ... %s --> %s" % (b['fecha'], b['fichero'])
  481. continue
  482. if (b['fecha'].year == hoy.year and b['fecha'].month == hoy.month) or (b['fecha'].day > max_dias):
  483. continue
  484. # print "Borrar: %s --> %s" % (b['fecha'], b['fichero'])
  485. print "Fecha: %s" % b['fecha']
  486. print "delete archive -noprompt -description=\"%s\" %s%s" % (b['description'], b['filespace'], b['fichero'])
  487. # print "delete archive -pick -description=\"%s\" %s%s" % (b['description'], b['filespace'], b['fichero'])