report.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #!/usr/bin/python
  2. '''CONFIG_JSON is a json encoded string base64 environment variable. It is used
  3. to clone docker-ci database, generate docker-ci report and submit it by email.
  4. CONFIG_JSON data comes from the file /report/credentials.json inserted in this
  5. container by deployment.py:
  6. { "DOCKER_CI_PUB": "$(cat docker-ci_ssh_public_key.pub)",
  7. "DOCKER_CI_KEY": "$(cat docker-ci_ssh_private_key.key)",
  8. "DOCKER_CI_ADDRESS": "user@docker-ci_fqdn_server",
  9. "SMTP_USER": "SMTP_server_user",
  10. "SMTP_PWD": "SMTP_server_password",
  11. "EMAIL_SENDER": "Buildbot_mailing_sender",
  12. "EMAIL_RCP": "Buildbot_mailing_receipient" } '''
  13. import os, re, json, sqlite3, datetime, base64
  14. import smtplib
  15. from datetime import timedelta
  16. from subprocess import call
  17. from os import environ as env
  18. TODAY = datetime.date.today()
  19. # Load credentials to the environment
  20. env['CONFIG_JSON'] = base64.b64decode(open('/report/credentials.json').read())
  21. # Remove SSH private key as it needs more processing
  22. CONFIG = json.loads(re.sub(r'("DOCKER_CI_KEY".+?"(.+?)",)','',
  23. env['CONFIG_JSON'], flags=re.DOTALL))
  24. # Populate environment variables
  25. for key in CONFIG:
  26. env[key] = CONFIG[key]
  27. # Load SSH private key
  28. env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1',
  29. env['CONFIG_JSON'],flags=re.DOTALL)
  30. # Prevent rsync to validate host on first connection to docker-ci
  31. os.makedirs('/root/.ssh')
  32. open('/root/.ssh/id_rsa','w').write(env['DOCKER_CI_KEY'])
  33. os.chmod('/root/.ssh/id_rsa',0600)
  34. open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n')
  35. # Sync buildbot database from docker-ci
  36. call('rsync {}:/data/buildbot/master/state.sqlite .'.format(
  37. env['DOCKER_CI_ADDRESS']), shell=True)
  38. class SQL:
  39. def __init__(self, database_name):
  40. sql = sqlite3.connect(database_name)
  41. # Use column names as keys for fetchall rows
  42. sql.row_factory = sqlite3.Row
  43. sql = sql.cursor()
  44. self.sql = sql
  45. def query(self,query_statement):
  46. return self.sql.execute(query_statement).fetchall()
  47. sql = SQL("state.sqlite")
  48. class Report():
  49. def __init__(self,period='',date=''):
  50. self.data = []
  51. self.period = 'date' if not period else period
  52. self.date = str(TODAY) if not date else date
  53. self.compute()
  54. def compute(self):
  55. '''Compute report'''
  56. if self.period == 'week':
  57. self.week_report(self.date)
  58. else:
  59. self.date_report(self.date)
  60. def date_report(self,date):
  61. '''Create a date test report'''
  62. builds = []
  63. # Get a queryset with all builds from date
  64. rows = sql.query('SELECT * FROM builds JOIN buildrequests'
  65. ' WHERE builds.brid=buildrequests.id and'
  66. ' date(start_time, "unixepoch", "localtime") = "{0}"'
  67. ' GROUP BY number'.format(date))
  68. build_names = sorted(set([row['buildername'] for row in rows]))
  69. # Create a report build line for a given build
  70. for build_name in build_names:
  71. tried = len([row['buildername']
  72. for row in rows if row['buildername'] == build_name])
  73. fail_tests = [row['buildername'] for row in rows if (
  74. row['buildername'] == build_name and row['results'] != 0)]
  75. fail = len(fail_tests)
  76. fail_details = ''
  77. fail_pct = int(100.0*fail/tried) if tried != 0 else 100
  78. builds.append({'name': build_name, 'tried': tried, 'fail': fail,
  79. 'fail_pct': fail_pct, 'fail_details':fail_details})
  80. if builds:
  81. self.data.append({'date': date, 'builds': builds})
  82. def week_report(self,date):
  83. '''Add the week's date test reports to report.data'''
  84. date = datetime.datetime.strptime(date,'%Y-%m-%d').date()
  85. last_monday = date - datetime.timedelta(days=date.weekday())
  86. week_dates = [last_monday + timedelta(days=x) for x in range(7,-1,-1)]
  87. for date in week_dates:
  88. self.date_report(str(date))
  89. def render_text(self):
  90. '''Return rendered report in text format'''
  91. retval = ''
  92. fail_tests = {}
  93. for builds in self.data:
  94. retval += 'Test date: {0}\n'.format(builds['date'],retval)
  95. table = ''
  96. for build in builds['builds']:
  97. table += ('Build {name:15} Tried: {tried:4} '
  98. ' Failures: {fail:4} ({fail_pct}%)\n'.format(**build))
  99. if build['name'] in fail_tests:
  100. fail_tests[build['name']] += build['fail_details']
  101. else:
  102. fail_tests[build['name']] = build['fail_details']
  103. retval += '{0}\n'.format(table)
  104. retval += '\n Builds failing'
  105. for fail_name in fail_tests:
  106. retval += '\n' + fail_name + '\n'
  107. for (fail_id,fail_url,rn_tests,nr_errors,log_errors,
  108. tracelog_errors) in fail_tests[fail_name]:
  109. retval += fail_url + '\n'
  110. retval += '\n\n'
  111. return retval
  112. # Send email
  113. smtp_from = env['EMAIL_SENDER']
  114. subject = '[docker-ci] Daily report for {}'.format(str(TODAY))
  115. msg = "From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n".format(
  116. smtp_from, env['EMAIL_RCP'], subject)
  117. msg = msg + Report('week').render_text()
  118. server = smtplib.SMTP_SSL('smtp.mailgun.org')
  119. server.login(env['SMTP_USER'], env['SMTP_PWD'])
  120. server.sendmail(smtp_from, env['EMAIL_RCP'], msg)