run-ci.py 10.0 KB

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