jenkins.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. '''
  4. This script is used to test Salt from a Jenkins server, specifically
  5. jenkins.saltstack.com.
  6. This script is intended to be shell-centric!!
  7. '''
  8. # Import python libs
  9. from __future__ import absolute_import, print_function
  10. import glob
  11. import os
  12. import re
  13. import sys
  14. import time
  15. import shutil
  16. import optparse
  17. import subprocess
  18. import random
  19. # Import Salt libs
  20. import salt.utils.files
  21. import salt.utils.json
  22. import salt.utils.stringutils
  23. import salt.utils.yaml
  24. try:
  25. from salt.utils.nb_popen import NonBlockingPopen
  26. except ImportError:
  27. # Salt not installed, or nb_popen was not yet shipped with it
  28. SALT_LIB = os.path.abspath(
  29. os.path.dirname(os.path.dirname(__file__))
  30. )
  31. if SALT_LIB not in sys.path:
  32. sys.path.insert(0, SALT_LIB)
  33. try:
  34. # Let's try using the current checked out code
  35. from salt.utils.nb_popen import NonBlockingPopen
  36. except ImportError:
  37. # Still an ImportError??? Let's use some "brute-force"
  38. sys.path.insert(
  39. 0,
  40. os.path.join(SALT_LIB, 'salt', 'utils')
  41. )
  42. from nb_popen import NonBlockingPopen
  43. # Import 3rd-party libs
  44. try:
  45. import requests
  46. HAS_REQUESTS = True
  47. except ImportError:
  48. HAS_REQUESTS = False
  49. SALT_GIT_URL = 'https://github.com/saltstack/salt.git'
  50. def build_pillar_data(options):
  51. '''
  52. Build a YAML formatted string to properly pass pillar data
  53. '''
  54. pillar = {'test_transport': options.test_transport,
  55. 'cloud_only': options.cloud_only,
  56. 'with_coverage': options.test_without_coverage is False}
  57. if options.test_git_commit is not None:
  58. pillar['test_git_commit'] = options.test_git_commit
  59. if options.test_git_url is not None:
  60. pillar['test_git_url'] = options.test_git_url
  61. if options.bootstrap_salt_url is not None:
  62. pillar['bootstrap_salt_url'] = options.bootstrap_salt_url
  63. if options.bootstrap_salt_commit is not None:
  64. pillar['bootstrap_salt_commit'] = options.bootstrap_salt_commit
  65. if options.package_source_dir:
  66. pillar['package_source_dir'] = options.package_source_dir
  67. if options.package_build_dir:
  68. pillar['package_build_dir'] = options.package_build_dir
  69. if options.package_artifact_dir:
  70. pillar['package_artifact_dir'] = options.package_artifact_dir
  71. if options.pillar:
  72. pillar.update(dict(options.pillar))
  73. return salt.utils.yaml.safe_dump(pillar, default_flow_style=True, indent=0, width=sys.maxint).rstrip()
  74. def build_minion_target(options, vm_name):
  75. target = vm_name
  76. for grain in options.grain_target:
  77. target += ' and G@{0}'.format(grain)
  78. if options.grain_target:
  79. return '"{0}"'.format(target)
  80. return target
  81. def generate_vm_name(options):
  82. '''
  83. Generate a random enough vm name
  84. '''
  85. if 'BUILD_NUMBER' in os.environ:
  86. random_part = 'BUILD{0:0>6}'.format(os.environ.get('BUILD_NUMBER'))
  87. else:
  88. random_part = os.urandom(3).encode('hex')
  89. return '{0}-{1}-{2}'.format(options.vm_prefix, options.platform, random_part)
  90. def delete_vm(options):
  91. '''
  92. Stop a VM
  93. '''
  94. cmd = 'salt-cloud -d {0} -y'.format(options.delete_vm)
  95. print('Running CMD: {0}'.format(cmd))
  96. sys.stdout.flush()
  97. proc = NonBlockingPopen(
  98. cmd,
  99. shell=True,
  100. stdout=subprocess.PIPE,
  101. stderr=subprocess.PIPE,
  102. stream_stds=True
  103. )
  104. proc.poll_and_read_until_finish(interval=0.5)
  105. proc.communicate()
  106. def echo_parseable_environment(options, parser):
  107. '''
  108. Echo NAME=VAL parseable output
  109. '''
  110. output = []
  111. if options.platform:
  112. name = generate_vm_name(options)
  113. output.extend([
  114. 'JENKINS_SALTCLOUD_VM_PLATFORM={0}'.format(options.platform),
  115. 'JENKINS_SALTCLOUD_VM_NAME={0}'.format(name)
  116. ])
  117. if options.provider:
  118. output.append(
  119. 'JENKINS_SALTCLOUD_VM_PROVIDER={0}'.format(options.provider)
  120. )
  121. if options.pull_request:
  122. # This is a Jenkins triggered Pull Request
  123. # We need some more data about the Pull Request available to the
  124. # environment
  125. if HAS_REQUESTS is False:
  126. parser.error(
  127. 'The python \'requests\' library needs to be installed'
  128. )
  129. headers = {}
  130. url = 'https://api.github.com/repos/saltstack/salt/pulls/{0}'.format(options.pull_request)
  131. github_access_token_path = os.path.join(
  132. os.environ.get('JENKINS_HOME', os.path.expanduser('~')),
  133. '.github_token'
  134. )
  135. if os.path.isfile(github_access_token_path):
  136. with salt.utils.files.fopen(github_access_token_path) as rfh:
  137. headers = {
  138. 'Authorization': 'token {0}'.format(rfh.read().strip())
  139. }
  140. http_req = requests.get(url, headers=headers)
  141. if http_req.status_code != 200:
  142. parser.error(
  143. 'Unable to get the pull request: {0[message]}'.format(http_req.json())
  144. )
  145. pr_details = http_req.json()
  146. output.extend([
  147. 'SALT_PR_GIT_URL={0}'.format(pr_details['head']['repo']['clone_url']),
  148. 'SALT_PR_GIT_BRANCH={0}'.format(pr_details['head']['ref']),
  149. 'SALT_PR_GIT_COMMIT={0}'.format(pr_details['head']['sha']),
  150. 'SALT_PR_GIT_BASE_BRANCH={0}'.format(pr_details['base']['ref']),
  151. ])
  152. sys.stdout.write('\n\n{0}\n\n'.format('\n'.join(output)))
  153. sys.stdout.flush()
  154. def download_unittest_reports(options):
  155. print('Downloading remote unittest reports...')
  156. sys.stdout.flush()
  157. workspace = options.workspace
  158. xml_reports_path = os.path.join(workspace, 'xml-test-reports')
  159. if os.path.isdir(xml_reports_path):
  160. shutil.rmtree(xml_reports_path)
  161. os.makedirs(xml_reports_path)
  162. cmds = (
  163. 'salt {0} archive.tar zcvf /tmp/xml-test-reports.tar.gz \'*.xml\' cwd=/tmp/xml-unittests-output/',
  164. 'salt {0} cp.push /tmp/xml-test-reports.tar.gz',
  165. 'mv -f /var/cache/salt/master/minions/{1}/files/tmp/xml-test-reports.tar.gz {2} && '
  166. 'tar zxvf {2}/xml-test-reports.tar.gz -C {2}/xml-test-reports && '
  167. 'rm -f {2}/xml-test-reports.tar.gz'
  168. )
  169. vm_name = options.download_unittest_reports
  170. for cmd in cmds:
  171. cmd = cmd.format(build_minion_target(options, vm_name), vm_name, workspace)
  172. print('Running CMD: {0}'.format(cmd))
  173. sys.stdout.flush()
  174. proc = NonBlockingPopen(
  175. cmd,
  176. shell=True,
  177. stdout=subprocess.PIPE,
  178. stderr=subprocess.PIPE,
  179. stream_stds=True
  180. )
  181. proc.poll_and_read_until_finish(interval=0.5)
  182. proc.communicate()
  183. if proc.returncode != 0:
  184. print(
  185. '\nFailed to execute command. Exit code: {0}'.format(
  186. proc.returncode
  187. )
  188. )
  189. time.sleep(0.25)
  190. def download_coverage_report(options):
  191. print('Downloading remote coverage report...')
  192. sys.stdout.flush()
  193. workspace = options.workspace
  194. vm_name = options.download_coverage_report
  195. if os.path.isfile(os.path.join(workspace, 'coverage.xml')):
  196. os.unlink(os.path.join(workspace, 'coverage.xml'))
  197. cmds = (
  198. 'salt {0} archive.gzip /tmp/coverage.xml',
  199. 'salt {0} cp.push /tmp/coverage.xml.gz',
  200. 'gunzip /var/cache/salt/master/minions/{1}/files/tmp/coverage.xml.gz',
  201. 'mv /var/cache/salt/master/minions/{1}/files/tmp/coverage.xml {2}'
  202. )
  203. for cmd in cmds:
  204. cmd = cmd.format(build_minion_target(options, vm_name), vm_name, workspace)
  205. print('Running CMD: {0}'.format(cmd))
  206. sys.stdout.flush()
  207. proc = NonBlockingPopen(
  208. cmd,
  209. shell=True,
  210. stdout=subprocess.PIPE,
  211. stderr=subprocess.PIPE,
  212. stream_stds=True
  213. )
  214. proc.poll_and_read_until_finish(interval=0.5)
  215. proc.communicate()
  216. if proc.returncode != 0:
  217. print(
  218. '\nFailed to execute command. Exit code: {0}'.format(
  219. proc.returncode
  220. )
  221. )
  222. time.sleep(0.25)
  223. def download_remote_logs(options):
  224. print('Downloading remote logs...')
  225. sys.stdout.flush()
  226. workspace = options.workspace
  227. vm_name = options.download_remote_logs
  228. for fname in ('salt-runtests.log', 'minion.log'):
  229. if os.path.isfile(os.path.join(workspace, fname)):
  230. os.unlink(os.path.join(workspace, fname))
  231. if not options.remote_log_path:
  232. options.remote_log_path = [
  233. '/tmp/salt-runtests.log',
  234. '/var/log/salt/minion'
  235. ]
  236. cmds = []
  237. for remote_log in options.remote_log_path:
  238. cmds.extend([
  239. 'salt {{0}} archive.gzip {0}'.format(remote_log),
  240. 'salt {{0}} cp.push {0}.gz'.format(remote_log),
  241. 'gunzip /var/cache/salt/master/minions/{{1}}/files{0}.gz'.format(remote_log),
  242. 'mv /var/cache/salt/master/minions/{{1}}/files{0} {{2}}/{1}'.format(
  243. remote_log,
  244. '{0}{1}'.format(
  245. os.path.basename(remote_log),
  246. '' if remote_log.endswith('.log') else '.log'
  247. )
  248. )
  249. ])
  250. for cmd in cmds:
  251. cmd = cmd.format(build_minion_target(options, vm_name), vm_name, workspace)
  252. print('Running CMD: {0}'.format(cmd))
  253. sys.stdout.flush()
  254. proc = NonBlockingPopen(
  255. cmd,
  256. shell=True,
  257. stdout=subprocess.PIPE,
  258. stderr=subprocess.PIPE,
  259. stream_stds=True
  260. )
  261. proc.poll_and_read_until_finish(interval=0.5)
  262. proc.communicate()
  263. if proc.returncode != 0:
  264. print(
  265. '\nFailed to execute command. Exit code: {0}'.format(
  266. proc.returncode
  267. )
  268. )
  269. time.sleep(0.25)
  270. def download_packages(options):
  271. print('Downloading packages...')
  272. sys.stdout.flush()
  273. workspace = options.workspace
  274. vm_name = options.download_packages
  275. for fglob in ('salt-*.rpm',
  276. 'salt-*.deb',
  277. 'salt-*.pkg.xz',
  278. 'salt-buildpackage.log'):
  279. for fname in glob.glob(os.path.join(workspace, fglob)):
  280. if os.path.isfile(fname):
  281. os.unlink(fname)
  282. cmds = [
  283. ('salt {{0}} archive.tar czf {0}.tar.gz sources=\'*.*\' cwd={0}'
  284. .format(options.package_artifact_dir)),
  285. 'salt {{0}} cp.push {0}.tar.gz'.format(options.package_artifact_dir),
  286. ('tar -C {{2}} -xzf /var/cache/salt/master/minions/{{1}}/files{0}.tar.gz'
  287. .format(options.package_artifact_dir)),
  288. ]
  289. for cmd in cmds:
  290. cmd = cmd.format(build_minion_target(options, vm_name), vm_name, workspace)
  291. print('Running CMD: {0}'.format(cmd))
  292. sys.stdout.flush()
  293. proc = NonBlockingPopen(
  294. cmd,
  295. shell=True,
  296. stdout=subprocess.PIPE,
  297. stderr=subprocess.PIPE,
  298. stream_stds=True
  299. )
  300. proc.poll_and_read_until_finish(interval=0.5)
  301. proc.communicate()
  302. if proc.returncode != 0:
  303. print(
  304. '\nFailed to execute command. Exit code: {0}'.format(
  305. proc.returncode
  306. )
  307. )
  308. time.sleep(0.25)
  309. def run(opts):
  310. '''
  311. RUN!
  312. '''
  313. vm_name = os.environ.get(
  314. 'JENKINS_SALTCLOUD_VM_NAME',
  315. generate_vm_name(opts)
  316. )
  317. if opts.download_remote_reports:
  318. if opts.test_without_coverage is False:
  319. opts.download_coverage_report = vm_name
  320. opts.download_unittest_reports = vm_name
  321. opts.download_packages = vm_name
  322. if opts.bootstrap_salt_commit is not None:
  323. if opts.bootstrap_salt_url is None:
  324. opts.bootstrap_salt_url = 'https://github.com/saltstack/salt.git'
  325. cmd = (
  326. 'salt-cloud -l debug'
  327. ' --script-args "-D -g {bootstrap_salt_url} -n git {1}"'
  328. ' -p {provider}_{platform} {0}'.format(
  329. vm_name,
  330. os.environ.get(
  331. 'SALT_MINION_BOOTSTRAP_RELEASE',
  332. opts.bootstrap_salt_commit
  333. ),
  334. **opts.__dict__
  335. )
  336. )
  337. else:
  338. cmd = (
  339. 'salt-cloud -l debug'
  340. ' --script-args "-D -n git {1}" -p {provider}_{platform} {0}'.format(
  341. vm_name,
  342. os.environ.get(
  343. 'SALT_MINION_BOOTSTRAP_RELEASE',
  344. opts.bootstrap_salt_commit
  345. ),
  346. **opts.__dict__
  347. )
  348. )
  349. if opts.splay is not None:
  350. # Sleep a random number of seconds
  351. cloud_downtime = random.randint(0, opts.splay)
  352. print('Sleeping random period before calling salt-cloud: {0}'.format(cloud_downtime))
  353. time.sleep(cloud_downtime)
  354. print('Running CMD: {0}'.format(cmd))
  355. sys.stdout.flush()
  356. proc = NonBlockingPopen(
  357. cmd,
  358. shell=True,
  359. stdout=subprocess.PIPE,
  360. stderr=subprocess.PIPE,
  361. stream_stds=True
  362. )
  363. proc.poll_and_read_until_finish(interval=0.5)
  364. proc.communicate()
  365. retcode = proc.returncode
  366. if retcode != 0:
  367. print('Failed to bootstrap VM. Exit code: {0}'.format(retcode))
  368. sys.stdout.flush()
  369. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  370. delete_vm(opts)
  371. sys.exit(retcode)
  372. print('VM Bootstrapped. Exit code: {0}'.format(retcode))
  373. sys.stdout.flush()
  374. # Sleep a random number of seconds
  375. bootstrap_downtime = random.randint(0, opts.splay)
  376. print('Sleeping for {0} seconds to allow the minion to breathe a little'.format(bootstrap_downtime))
  377. sys.stdout.flush()
  378. time.sleep(bootstrap_downtime)
  379. if opts.bootstrap_salt_commit is not None:
  380. # Let's find out if the installed version matches the passed in pillar
  381. # information
  382. print('Grabbing bootstrapped minion version information ... ')
  383. cmd = 'salt -t 100 {0} --out json test.version'.format(build_minion_target(opts, vm_name))
  384. print('Running CMD: {0}'.format(cmd))
  385. sys.stdout.flush()
  386. proc = subprocess.Popen(
  387. cmd,
  388. shell=True,
  389. stdout=subprocess.PIPE,
  390. stderr=subprocess.PIPE,
  391. )
  392. stdout, _ = proc.communicate()
  393. retcode = proc.returncode
  394. if retcode != 0:
  395. print('Failed to get the bootstrapped minion version. Exit code: {0}'.format(retcode))
  396. sys.stdout.flush()
  397. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  398. delete_vm(opts)
  399. sys.exit(retcode)
  400. outstr = salt.utils.stringutils.to_str(stdout).strip()
  401. if not outstr:
  402. print('Failed to get the bootstrapped minion version(no output). Exit code: {0}'.format(retcode))
  403. sys.stdout.flush()
  404. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  405. delete_vm(opts)
  406. sys.exit(retcode)
  407. try:
  408. version_info = salt.utils.json.loads(outstr)
  409. bootstrap_minion_version = os.environ.get(
  410. 'SALT_MINION_BOOTSTRAP_RELEASE',
  411. opts.bootstrap_salt_commit[:7]
  412. )
  413. print('Minion reported salt version: {0}'.format(version_info))
  414. if bootstrap_minion_version not in version_info[vm_name]:
  415. print('\n\nATTENTION!!!!\n')
  416. print('The boostrapped minion version commit does not contain the desired commit:')
  417. print(
  418. ' \'{0}\' does not contain \'{1}\''.format(
  419. version_info[vm_name],
  420. bootstrap_minion_version
  421. )
  422. )
  423. print('\n\n')
  424. sys.stdout.flush()
  425. #if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  426. # delete_vm(opts)
  427. #sys.exit(retcode)
  428. else:
  429. print('matches!')
  430. except ValueError:
  431. print('Failed to load any JSON from \'{0}\''.format(outstr))
  432. if opts.cloud_only:
  433. # Run Cloud Provider tests preparation SLS
  434. cloud_provider_downtime = random.randint(3, opts.splay)
  435. time.sleep(cloud_provider_downtime)
  436. cmd = (
  437. 'salt -t 900 {target} state.sls {cloud_prep_sls} pillar="{pillar}" '
  438. '--no-color'.format(
  439. target=build_minion_target(opts, vm_name),
  440. cloud_prep_sls='cloud-only',
  441. pillar=build_pillar_data(opts),
  442. )
  443. )
  444. else:
  445. # Run standard preparation SLS
  446. standard_sls_downtime = random.randint(3, opts.splay)
  447. time.sleep(standard_sls_downtime)
  448. cmd = (
  449. 'salt -t 1800 {target} state.sls {prep_sls} pillar="{pillar}" '
  450. '--no-color'.format(
  451. target=build_minion_target(opts, vm_name),
  452. prep_sls=opts.prep_sls,
  453. pillar=build_pillar_data(opts),
  454. )
  455. )
  456. print('Running CMD: {0}'.format(cmd))
  457. sys.stdout.flush()
  458. proc = subprocess.Popen(
  459. cmd,
  460. shell=True,
  461. stdout=subprocess.PIPE,
  462. stderr=subprocess.PIPE,
  463. )
  464. stdout, stderr = proc.communicate()
  465. if stdout:
  466. print(salt.utils.stringutils.to_str(stdout))
  467. if stderr:
  468. print(salt.utils.stringutils.to_str(stderr))
  469. sys.stdout.flush()
  470. retcode = proc.returncode
  471. if retcode != 0:
  472. print('Failed to execute the preparation SLS file. Exit code: {0}'.format(retcode))
  473. sys.stdout.flush()
  474. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  475. delete_vm(opts)
  476. sys.exit(retcode)
  477. if opts.cloud_only:
  478. cloud_provider_pillar = random.randint(3, opts.splay)
  479. time.sleep(cloud_provider_pillar)
  480. # Run Cloud Provider tests pillar preparation SLS
  481. cmd = (
  482. 'salt -t 600 {target} state.sls {cloud_prep_sls} pillar="{pillar}" '
  483. '--no-color'.format(
  484. target=build_minion_target(opts, vm_name),
  485. cloud_prep_sls='cloud-test-configs',
  486. pillar=build_pillar_data(opts),
  487. )
  488. )
  489. print('Running CMD: {0}'.format(cmd))
  490. sys.stdout.flush()
  491. proc = subprocess.Popen(
  492. cmd,
  493. shell=True,
  494. stdout=subprocess.PIPE,
  495. stderr=subprocess.STDOUT,
  496. )
  497. stdout, stderr = proc.communicate()
  498. if stdout:
  499. # DO NOT print the state return here!
  500. print('Cloud configuration files provisioned via pillar.')
  501. if stderr:
  502. print(salt.utils.stringutils.to_str(stderr))
  503. sys.stdout.flush()
  504. retcode = proc.returncode
  505. if retcode != 0:
  506. print('Failed to execute the preparation SLS file. Exit code: {0}'.format(retcode))
  507. sys.stdout.flush()
  508. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  509. delete_vm(opts)
  510. sys.exit(retcode)
  511. if opts.prep_sls_2 is not None:
  512. sls_2_downtime = random.randint(3, opts.splay)
  513. time.sleep(sls_2_downtime)
  514. # Run the 2nd preparation SLS
  515. cmd = (
  516. 'salt -t 30 {target} state.sls {prep_sls_2} pillar="{pillar}" '
  517. '--no-color'.format(
  518. prep_sls_2=opts.prep_sls_2,
  519. pillar=build_pillar_data(opts),
  520. target=build_minion_target(opts, vm_name),
  521. )
  522. )
  523. print('Running CMD: {0}'.format(cmd))
  524. sys.stdout.flush()
  525. proc = subprocess.Popen(
  526. cmd,
  527. shell=True,
  528. stdout=subprocess.PIPE,
  529. stderr=subprocess.STDOUT,
  530. )
  531. stdout, stderr = proc.communicate()
  532. if stdout:
  533. print(salt.utils.stringutils.to_str(stdout))
  534. if stderr:
  535. print(salt.utils.stringutils.to_str(stderr))
  536. sys.stdout.flush()
  537. retcode = proc.returncode
  538. if retcode != 0:
  539. print('Failed to execute the 2nd preparation SLS file. Exit code: {0}'.format(retcode))
  540. sys.stdout.flush()
  541. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  542. delete_vm(opts)
  543. sys.exit(retcode)
  544. # Run remote checks
  545. if opts.test_git_url is not None:
  546. test_git_downtime = random.randint(1, opts.splay)
  547. time.sleep(test_git_downtime)
  548. # Let's find out if the cloned repository if checked out from the
  549. # desired repository
  550. print('Grabbing the cloned repository remotes information ... ')
  551. cmd = 'salt -t 100 {0} --out json git.remote_get /testing'.format(build_minion_target(opts, vm_name))
  552. print('Running CMD: {0}'.format(cmd))
  553. sys.stdout.flush()
  554. proc = subprocess.Popen(
  555. cmd,
  556. shell=True,
  557. stdout=subprocess.PIPE,
  558. stderr=subprocess.PIPE,
  559. )
  560. stdout, _ = proc.communicate()
  561. retcode = proc.returncode
  562. if retcode != 0:
  563. print('Failed to get the cloned repository remote. Exit code: {0}'.format(retcode))
  564. sys.stdout.flush()
  565. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  566. delete_vm(opts)
  567. sys.exit(retcode)
  568. if not stdout:
  569. print('Failed to get the cloned repository remote(no output). Exit code: {0}'.format(retcode))
  570. sys.stdout.flush()
  571. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  572. delete_vm(opts)
  573. sys.exit(retcode)
  574. try:
  575. remotes_info = salt.utils.json.loads(stdout.strip())
  576. if remotes_info is None or remotes_info[vm_name] is None or opts.test_git_url not in remotes_info[vm_name]:
  577. print('The cloned repository remote is not the desired one:')
  578. print(' \'{0}\' is not in {1}'.format(opts.test_git_url, remotes_info))
  579. sys.stdout.flush()
  580. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  581. delete_vm(opts)
  582. sys.exit(retcode)
  583. print('matches!')
  584. except ValueError:
  585. print('Failed to load any JSON from \'{0}\''.format(salt.utils.stringutils.to_str(stdout).strip()))
  586. if opts.test_git_commit is not None:
  587. test_git_commit_downtime = random.randint(1, opts.splay)
  588. time.sleep(test_git_commit_downtime)
  589. # Let's find out if the cloned repository is checked out at the desired
  590. # commit
  591. print('Grabbing the cloned repository commit information ... ')
  592. cmd = 'salt -t 100 {0} --out json git.revision /testing'.format(build_minion_target(opts, vm_name))
  593. print('Running CMD: {0}'.format(cmd))
  594. sys.stdout.flush()
  595. proc = subprocess.Popen(
  596. cmd,
  597. shell=True,
  598. stdout=subprocess.PIPE,
  599. stderr=subprocess.PIPE,
  600. )
  601. stdout, _ = proc.communicate()
  602. sys.stdout.flush()
  603. retcode = proc.returncode
  604. if retcode != 0:
  605. print('Failed to get the cloned repository revision. Exit code: {0}'.format(retcode))
  606. sys.stdout.flush()
  607. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  608. delete_vm(opts)
  609. sys.exit(retcode)
  610. if not stdout:
  611. print('Failed to get the cloned repository revision(no output). Exit code: {0}'.format(retcode))
  612. sys.stdout.flush()
  613. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  614. delete_vm(opts)
  615. sys.exit(retcode)
  616. try:
  617. revision_info = salt.utils.json.loads(stdout.strip())
  618. if revision_info[vm_name][7:] != opts.test_git_commit[7:]:
  619. print('The cloned repository commit is not the desired one:')
  620. print(' \'{0}\' != \'{1}\''.format(revision_info[vm_name][:7], opts.test_git_commit[:7]))
  621. sys.stdout.flush()
  622. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  623. delete_vm(opts)
  624. sys.exit(retcode)
  625. print('matches!')
  626. except ValueError:
  627. print('Failed to load any JSON from \'{0}\''.format(salt.utils.stringutils.to_str(stdout).strip()))
  628. # Run tests here
  629. test_begin_downtime = random.randint(3, opts.splay)
  630. time.sleep(test_begin_downtime)
  631. cmd = (
  632. 'salt -t 1800 {target} state.sls {sls} pillar="{pillar}" --no-color'.format(
  633. sls=opts.sls,
  634. pillar=build_pillar_data(opts),
  635. target=build_minion_target(opts, vm_name),
  636. )
  637. )
  638. print('Running CMD: {0}'.format(cmd))
  639. sys.stdout.flush()
  640. proc = subprocess.Popen(
  641. cmd,
  642. shell=True,
  643. stdout=subprocess.PIPE,
  644. stderr=subprocess.PIPE,
  645. )
  646. stdout, stderr = proc.communicate()
  647. outstr = salt.utils.stringutils.to_str(stdout)
  648. if outstr:
  649. print(outstr)
  650. if stderr:
  651. print(salt.utils.stringutils.to_str(stderr))
  652. sys.stdout.flush()
  653. try:
  654. match = re.search(r'Test Suite Exit Code: (?P<exitcode>[\d]+)', outstr)
  655. retcode = int(match.group('exitcode'))
  656. except AttributeError:
  657. # No regex matching
  658. retcode = 1
  659. except ValueError:
  660. # Not a number!?
  661. retcode = 1
  662. except TypeError:
  663. # No output!?
  664. retcode = 1
  665. if outstr:
  666. # Anything else, raise the exception
  667. raise
  668. if retcode == 0:
  669. # Build packages
  670. time.sleep(3)
  671. cmd = (
  672. 'salt -t 1800 {target} state.sls buildpackage pillar="{pillar}" --no-color'.format(
  673. pillar=build_pillar_data(opts),
  674. target=build_minion_target(opts, vm_name),
  675. )
  676. )
  677. print('Running CMD: {0}'.format(cmd))
  678. sys.stdout.flush()
  679. proc = subprocess.Popen(
  680. cmd,
  681. shell=True,
  682. stdout=subprocess.PIPE,
  683. stderr=subprocess.PIPE,
  684. )
  685. stdout, stderr = proc.communicate()
  686. if stdout:
  687. print(salt.utils.stringutils.to_str(stdout))
  688. if stderr:
  689. print(salt.utils.stringutils.to_str(stderr))
  690. sys.stdout.flush()
  691. # Grab packages and log file (or just log file if build failed)
  692. download_packages(opts)
  693. if opts.download_remote_reports:
  694. # Download unittest reports
  695. download_unittest_reports(opts)
  696. # Download coverage report
  697. if opts.test_without_coverage is False:
  698. download_coverage_report(opts)
  699. if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
  700. delete_vm(opts)
  701. return retcode
  702. def parse():
  703. '''
  704. Parse the CLI options
  705. '''
  706. parser = optparse.OptionParser()
  707. parser.add_option(
  708. '--vm-prefix',
  709. default=os.environ.get('JENKINS_VM_NAME_PREFIX', 'ZJENKINS'),
  710. help='The bootstrapped machine name prefix'
  711. )
  712. parser.add_option(
  713. '-w', '--workspace',
  714. default=os.path.abspath(
  715. os.environ.get(
  716. 'WORKSPACE',
  717. os.path.dirname(os.path.dirname(__file__))
  718. )
  719. ),
  720. help='Path the execution workspace'
  721. )
  722. parser.add_option(
  723. '--platform',
  724. default=os.environ.get('JENKINS_SALTCLOUD_VM_PLATFORM', None),
  725. help='The target platform, choose from:\ncent6\ncent5\nubuntu12.04')
  726. parser.add_option(
  727. '--provider',
  728. default=os.environ.get('JENKINS_SALTCLOUD_VM_PROVIDER', None),
  729. help='The vm provider')
  730. parser.add_option(
  731. '--bootstrap-salt-url',
  732. default=None,
  733. help='The salt git repository url used to boostrap a minion')
  734. parser.add_option(
  735. '--bootstrap-salt-commit',
  736. default=None,
  737. help='The salt git commit used to boostrap a minion')
  738. parser.add_option(
  739. '--test-git-url',
  740. default=None,
  741. help='The testing git repository url')
  742. parser.add_option(
  743. '--test-git-commit',
  744. default=None,
  745. help='The testing git commit to track')
  746. parser.add_option(
  747. '--test-transport',
  748. default='zeromq',
  749. choices=('zeromq', 'tcp'),
  750. help=('Select which transport to run the integration tests with, '
  751. 'zeromq or tcp. Default: %default')
  752. )
  753. parser.add_option(
  754. '--test-without-coverage',
  755. default=False,
  756. action='store_true',
  757. help='Do not generate coverage reports'
  758. )
  759. parser.add_option(
  760. '--prep-sls',
  761. default='git.salt',
  762. help='The sls file to execute to prepare the system')
  763. parser.add_option(
  764. '--prep-sls-2',
  765. default=None,
  766. help='An optional 2nd system preparation SLS')
  767. parser.add_option(
  768. '--sls',
  769. default='testrun-no-deps',
  770. help='The final sls file to execute')
  771. parser.add_option(
  772. '--pillar',
  773. action='append',
  774. nargs=2,
  775. help='Pillar (key, value)s to pass to the sls file. '
  776. 'Example: \'--pillar pillar_key pillar_value\'')
  777. parser.add_option(
  778. '--no-clean',
  779. dest='clean',
  780. default=True,
  781. action='store_false',
  782. help='Clean up the built vm')
  783. parser.add_option(
  784. '--echo-parseable-environment',
  785. default=False,
  786. action='store_true',
  787. help='Print a parseable KEY=VAL output'
  788. )
  789. parser.add_option(
  790. '--pull-request',
  791. type=int,
  792. help='Include the PR info only'
  793. )
  794. parser.add_option(
  795. '--delete-vm',
  796. default=None,
  797. help='Delete a running VM'
  798. )
  799. parser.add_option(
  800. '--download-remote-reports',
  801. default=False,
  802. action='store_true',
  803. help='Download remote reports when running remote \'testrun\' state'
  804. )
  805. parser.add_option(
  806. '--download-unittest-reports',
  807. default=None,
  808. help='Download the XML unittest results'
  809. )
  810. parser.add_option(
  811. '--download-coverage-report',
  812. default=None,
  813. help='Download the XML coverage reports'
  814. )
  815. parser.add_option(
  816. '--remote-log-path',
  817. action='append',
  818. default=[],
  819. help='Provide additional log paths to download from remote minion'
  820. )
  821. parser.add_option(
  822. '--download-remote-logs',
  823. default=None,
  824. help='Download remote minion and runtests log files'
  825. )
  826. parser.add_option(
  827. '--grain-target',
  828. action='append',
  829. default=[],
  830. help='Match minions using compound matchers, the minion ID, plus the passed grain.'
  831. )
  832. parser.add_option(
  833. '--cloud-only',
  834. default=False,
  835. action='store_true',
  836. help='Run the cloud provider tests only.'
  837. )
  838. parser.add_option(
  839. '--build-packages',
  840. default=True,
  841. action='store_true',
  842. help='Run buildpackage.py to create packages off of the git build.'
  843. )
  844. # These next three options are ignored if --build-packages is False
  845. parser.add_option(
  846. '--package-source-dir',
  847. default='/testing',
  848. help='Directory where the salt source code checkout is found '
  849. '(default: %default)',
  850. )
  851. parser.add_option(
  852. '--package-build-dir',
  853. default='/tmp/salt-buildpackage',
  854. help='Build root for automated package builds (default: %default)',
  855. )
  856. parser.add_option(
  857. '--package-artifact-dir',
  858. default='/tmp/salt-packages',
  859. help='Location on the minion from which packages should be '
  860. 'retrieved (default: %default)',
  861. )
  862. parser.add_option(
  863. '--splay',
  864. default='10',
  865. help='The number of seconds across which calls to provisioning components should be made'
  866. )
  867. options, args = parser.parse_args()
  868. if options.delete_vm is not None and not options.test_git_commit:
  869. delete_vm(options)
  870. parser.exit(0)
  871. if options.download_unittest_reports is not None and not options.test_git_commit:
  872. download_unittest_reports(options)
  873. parser.exit(0)
  874. if options.test_without_coverage is False:
  875. if options.download_coverage_report is not None and not options.test_git_commit:
  876. download_coverage_report(options)
  877. parser.exit(0)
  878. if options.download_remote_logs is not None and not options.test_git_commit:
  879. download_remote_logs(options)
  880. parser.exit(0)
  881. if not options.platform and not options.pull_request:
  882. parser.exit('--platform or --pull-request is required')
  883. if not options.provider and not options.pull_request:
  884. parser.exit('--provider or --pull-request is required')
  885. if options.echo_parseable_environment:
  886. echo_parseable_environment(options, parser)
  887. parser.exit(0)
  888. if not options.test_git_commit and not options.pull_request:
  889. parser.exit('--commit or --pull-request is required')
  890. return options
  891. if __name__ == '__main__':
  892. exit_code = run(parse())
  893. print('Exit Code: {0}'.format(exit_code))
  894. sys.exit(exit_code)