run-ci.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. #!/usr/bin/env python
  2. import subprocess
  3. import os
  4. import sys
  5. from benchmark import framework_test
  6. from benchmark.utils import gather_tests
  7. import glob
  8. import json
  9. import traceback
  10. import re
  11. import logging
  12. log = logging.getLogger('run-ci')
  13. import time
  14. import threading
  15. # Needed for various imports
  16. sys.path.append('.')
  17. sys.path.append('toolset/setup/linux')
  18. sys.path.append('toolset/benchmark')
  19. class CIRunnner:
  20. '''
  21. Manages running TFB on the Travis Continuous Integration system.
  22. Makes a best effort to avoid wasting time and resources by running
  23. useless jobs.
  24. Only verifies the first test in each directory
  25. '''
  26. def __init__(self, mode, testdir=None):
  27. '''
  28. mode = [cisetup|prereq|install|verify] for what we want to do
  29. testdir = framework directory we are running
  30. '''
  31. logging.basicConfig(level=logging.INFO)
  32. self.directory = testdir
  33. self.mode = mode
  34. try:
  35. is_pull_req = (os.environ['TRAVIS_PULL_REQUEST'] != "false")
  36. if is_pull_req:
  37. # See add_commit_range in setup/travis-ci
  38. self.commit_range = "prbase..prhead"
  39. else:
  40. self.commit_range = os.environ['TRAVIS_COMMIT_RANGE']
  41. except KeyError:
  42. log.warning("I should only be used for automated integration tests e.g. Travis-CI")
  43. log.warning("Were you looking for run-tests.py?")
  44. last_commit = subprocess.check_output("git rev-parse HEAD^", shell=True).rstrip('\n')
  45. self.commit_range = "%s...HEAD" % last_commit
  46. #
  47. # Find the one test from benchmark_config that we are going to run
  48. #
  49. tests = gather_tests()
  50. dirtests = [t for t in tests if t.directory == testdir]
  51. # Travis-CI is linux only
  52. osvalidtests = [t for t in dirtests if t.os.lower() == "linux"
  53. and (t.database_os.lower() == "linux" or t.database_os.lower() == "none")]
  54. # Travis-CI only has some supported databases
  55. validtests = [t for t in osvalidtests if t.database.lower() == "mysql"
  56. or t.database.lower() == "postgres"
  57. or t.database.lower() == "mongodb"
  58. or t.database.lower() == "none"]
  59. log.info("Found %s tests (%s for linux, %s for linux and mysql) in directory '%s'",
  60. len(dirtests), len(osvalidtests), len(validtests), testdir)
  61. if len(validtests) == 0:
  62. log.critical("Found no test that is possible to run in Travis-CI! Aborting!")
  63. if len(osvalidtests) != 0:
  64. log.critical("Note: Found these tests that could run in Travis-CI if more databases were supported")
  65. log.critical("Note: %s", osvalidtests)
  66. databases_needed = [t.database for t in osvalidtests]
  67. databases_needed = list(set(databases_needed))
  68. log.critical("Note: Here are the needed databases:")
  69. log.critical("Note: %s", databases_needed)
  70. sys.exit(1)
  71. self.names = [t.name for t in validtests]
  72. log.info("Choosing to use test %s to verify directory %s", self.names, testdir)
  73. def _should_run(self):
  74. '''
  75. Decides if the current framework test should be tested.
  76. Examines git commits included in the latest push to see if any files relevant to
  77. this framework were changed.
  78. If you do rewrite history (e.g. rebase) then it's up to you to ensure that both
  79. old and new (e.g. old...new) are available in the public repository. For simple
  80. rebase onto the public master this is not a problem, only more complex rebases
  81. may have issues
  82. '''
  83. # Don't use git diff multiple times, it's mega slow sometimes\
  84. # Put flag on filesystem so that future calls to run-ci see it too
  85. if os.path.isfile('.run-ci.should_run'):
  86. return True
  87. if os.path.isfile('.run-ci.should_not_run'):
  88. return False
  89. def touch(fname):
  90. open(fname, 'a').close()
  91. log.info("Using commit range %s", self.commit_range)
  92. log.info("Running `git diff --name-only %s`" % self.commit_range)
  93. changes = subprocess.check_output("git diff --name-only %s" % self.commit_range, shell=True)
  94. log.info(changes)
  95. # Look for changes to core TFB framework code
  96. if re.search(r'^toolset/', changes, re.M) is not None:
  97. log.info("Found changes to core framework code")
  98. touch('.run-ci.should_run')
  99. return True
  100. # Look for changes relevant to this test
  101. if re.search("^%s/" % self.directory, changes, re.M) is None:
  102. log.info("No changes found for %s", self.directory)
  103. touch('.run-ci.should_not_run')
  104. return False
  105. log.info("Changes found for %s", self.directory)
  106. touch('.run-ci.should_run')
  107. return True
  108. def run(self):
  109. ''' Do the requested command using TFB '''
  110. if not self._should_run():
  111. log.info("Not running %s", self.directory)
  112. return 0
  113. if self.mode == 'cisetup':
  114. self.run_travis_setup()
  115. return 0
  116. names = ' '.join(self.names)
  117. command = 'toolset/run-tests.py '
  118. if self.mode == 'prereq':
  119. command = command + "--install server --install-only --test ''"
  120. elif self.mode == 'install':
  121. command = command + "--install server --install-only --test %s" % names
  122. elif self.mode == 'verify':
  123. command = command + "--mode verify --test %s" % names
  124. else:
  125. log.critical('Unknown mode passed')
  126. return 1
  127. # Run the command
  128. log.info("Running mode %s with commmand %s", self.mode, command)
  129. try:
  130. p = subprocess.Popen(command, shell=True)
  131. p.wait()
  132. return p.returncode
  133. except subprocess.CalledProcessError:
  134. log.critical("Subprocess Error")
  135. print traceback.format_exc()
  136. return 1
  137. except Exception as err:
  138. log.critical("Exception from running+wait on subprocess")
  139. log.error(err.child_traceback)
  140. return 1
  141. def run_travis_setup(self):
  142. log.info("Setting up Travis-CI")
  143. script = '''
  144. # Needed to download latest MongoDB (use two different approaches)
  145. sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 || gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 7F0CEB10
  146. echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
  147. sudo apt-get update
  148. # MongoDB takes a good 30-45 seconds to turn on, so install it first
  149. sudo apt-get install mongodb-org
  150. sudo apt-get install openssh-server
  151. # Run as travis user (who already has passwordless sudo)
  152. ssh-keygen -f /home/travis/.ssh/id_rsa -N '' -t rsa
  153. cat /home/travis/.ssh/id_rsa.pub > /home/travis/.ssh/authorized_keys
  154. chmod 600 /home/travis/.ssh/authorized_keys
  155. # =============Setup Databases===========================
  156. # NOTE: Do not run `--install database` in travis-ci!
  157. # It changes DB configuration files and will break everything
  158. # =======================================================
  159. # Add data to mysql
  160. mysql -uroot < config/create.sql
  161. # Setup Postgres
  162. psql --version
  163. sudo useradd benchmarkdbuser -p benchmarkdbpass
  164. sudo -u postgres psql template1 < config/create-postgres-database.sql
  165. sudo -u benchmarkdbuser psql hello_world < config/create-postgres.sql
  166. # Setup MongoDB (see install above)
  167. mongod --version
  168. until nc -z localhost 27017 ; do echo Waiting for MongoDB; sleep 1; done
  169. mongo < config/create.js
  170. '''
  171. def sh(command):
  172. log.info("Running `%s`", command)
  173. subprocess.check_call(command, shell=True)
  174. for command in script.split('\n'):
  175. command = command.lstrip()
  176. if command != "" and command[0] != '#':
  177. sh(command.lstrip())
  178. if __name__ == "__main__":
  179. args = sys.argv[1:]
  180. usage = '''Usage: toolset/run-ci.py [cisetup|prereq|install|verify] <framework-directory>
  181. run-ci.py selects one test from <framework-directory>/benchark_config, and
  182. automates a number of calls into run-tests.py specific to the selected test.
  183. It is guaranteed to always select the same test from the benchark_config, so
  184. multiple runs with the same <framework-directory> reference the same test.
  185. The name of the selected test will be printed to standard output.
  186. cisetup - configure the Travis-CI environment for our test suite
  187. prereq - trigger standard prerequisite installation
  188. install - trigger server installation for the selected test_directory
  189. verify - run a verification on the selected test using `--mode verify`
  190. run-ci.py expects to be run inside the Travis-CI build environment, and
  191. will expect environment variables such as $TRAVIS_BUILD'''
  192. if len(args) != 2:
  193. print usage
  194. sys.exit(1)
  195. mode = args[0]
  196. testdir = args[1]
  197. if len(args) == 2 and (mode == "install"
  198. or mode == "verify"
  199. or mode == 'prereq'
  200. or mode == 'cisetup'):
  201. runner = CIRunnner(mode, testdir)
  202. else:
  203. print usage
  204. sys.exit(1)
  205. retcode = 0
  206. try:
  207. retcode = runner.run()
  208. except KeyError as ke:
  209. log.warning("Environment key missing, are you running inside Travis-CI?")
  210. print traceback.format_exc()
  211. except:
  212. log.critical("Unknown error")
  213. print traceback.format_exc()
  214. finally: # Ensure that logs are printed
  215. # Only print logs if we ran a verify
  216. if mode != 'verify':
  217. sys.exit(retcode)
  218. # Only print logs if we actually did something
  219. if os.path.isfile('.run-ci.should_not_run'):
  220. sys.exit(retcode)
  221. log.error("Running inside Travis-CI, so I will print err and out to console...")
  222. try:
  223. log.error("Here is ERR:")
  224. with open("results/ec2/latest/logs/%s/err.txt" % runner.test.name, 'r') as err:
  225. for line in err:
  226. log.info(line.rstrip('\n'))
  227. except IOError:
  228. log.error("No ERR file found")
  229. try:
  230. log.error("Here is OUT:")
  231. with open("results/ec2/latest/logs/%s/out.txt" % runner.test.name, 'r') as out:
  232. for line in out:
  233. log.info(line.rstrip('\n'))
  234. except IOError:
  235. log.error("No OUT file found")
  236. sys.exit(retcode)