jenkins.py 33 KB

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