run-ci.py 9.7 KB

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