run-ci.py 9.9 KB

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