2013-08-10 07:14:57 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
'''
|
2013-08-11 05:50:47 +00:00
|
|
|
This script is used to test Salt from a Jenkins server, specifically
|
|
|
|
jenkins.saltstack.com.
|
2013-08-10 07:14:57 +00:00
|
|
|
|
2013-08-11 05:50:47 +00:00
|
|
|
This script is intended to be shell-centric!!
|
2013-08-10 07:14:57 +00:00
|
|
|
'''
|
|
|
|
|
2013-08-10 07:23:32 +00:00
|
|
|
# Import python libs
|
2013-08-13 04:54:47 +00:00
|
|
|
import os
|
|
|
|
import re
|
2013-08-10 07:23:32 +00:00
|
|
|
import sys
|
2013-09-06 16:35:30 +00:00
|
|
|
import time
|
2013-08-10 07:14:57 +00:00
|
|
|
import random
|
2013-09-08 15:32:35 +00:00
|
|
|
import shutil
|
2013-09-06 16:35:30 +00:00
|
|
|
import hashlib
|
2013-08-10 07:14:57 +00:00
|
|
|
import optparse
|
2013-09-06 16:35:30 +00:00
|
|
|
import subprocess
|
2013-08-10 07:14:57 +00:00
|
|
|
|
2013-08-13 04:54:47 +00:00
|
|
|
try:
|
|
|
|
from salt.utils.nb_popen import NonBlockingPopen
|
|
|
|
except ImportError:
|
|
|
|
# Salt not installed, or nb_popen was not yet shipped with it
|
2013-09-06 11:14:27 +00:00
|
|
|
SALT_LIB = os.path.abspath(
|
2013-08-13 04:54:47 +00:00
|
|
|
os.path.dirname(os.path.dirname(__file__))
|
|
|
|
)
|
2013-09-06 11:14:27 +00:00
|
|
|
if SALT_LIB not in sys.path:
|
|
|
|
sys.path.insert(0, SALT_LIB)
|
2013-08-13 04:54:47 +00:00
|
|
|
try:
|
|
|
|
# Let's try using the current checked out code
|
|
|
|
from salt.utils.nb_popen import NonBlockingPopen
|
|
|
|
except ImportError:
|
|
|
|
# Still an ImportError??? Let's use some "brute-force"
|
|
|
|
sys.path.insert(
|
|
|
|
0,
|
2013-09-06 11:14:27 +00:00
|
|
|
os.path.join(SALT_LIB, 'salt', 'utils')
|
2013-08-13 04:54:47 +00:00
|
|
|
)
|
|
|
|
from nb_popen import NonBlockingPopen
|
|
|
|
|
|
|
|
|
2013-09-06 11:14:27 +00:00
|
|
|
def generate_vm_name(platform):
|
|
|
|
'''
|
|
|
|
Generate a random enough vm name
|
|
|
|
'''
|
2013-09-06 16:13:05 +00:00
|
|
|
if 'BUILD_NUMBER' in os.environ:
|
|
|
|
random_part = 'BUILD{0:0>6}'.format(os.environ.get('BUILD_NUMBER'))
|
|
|
|
else:
|
|
|
|
random_part = hashlib.md5(
|
|
|
|
str(random.randint(1, 100000000))).hexdigest()[:6]
|
|
|
|
|
|
|
|
return 'ZJENKINS-{0}-{1}'.format(platform, random_part)
|
2013-08-17 06:59:13 +00:00
|
|
|
|
2013-09-06 11:14:27 +00:00
|
|
|
|
|
|
|
def delete_vm(vm_name):
|
|
|
|
'''
|
|
|
|
Stop a VM
|
|
|
|
'''
|
2013-08-17 06:59:13 +00:00
|
|
|
cmd = 'salt-cloud -d {0} -y'.format(vm_name)
|
|
|
|
print('Running CMD: {0}'.format(cmd))
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
proc = NonBlockingPopen(
|
|
|
|
cmd,
|
|
|
|
shell=True,
|
|
|
|
stdout=subprocess.PIPE,
|
2013-08-22 10:59:25 +00:00
|
|
|
stderr=subprocess.STDOUT,
|
2013-08-17 06:59:13 +00:00
|
|
|
stream_stds=True
|
|
|
|
)
|
|
|
|
proc.poll_and_read_until_finish()
|
|
|
|
proc.communicate()
|
|
|
|
|
|
|
|
|
2013-09-06 11:14:27 +00:00
|
|
|
def echo_parseable_environment(platform, provider):
|
|
|
|
'''
|
|
|
|
Echo NAME=VAL parseable output
|
|
|
|
'''
|
|
|
|
name = generate_vm_name(platform)
|
|
|
|
output = (
|
2013-09-08 11:06:45 +00:00
|
|
|
'JENKINS_SALTCLOUD_VM_PROVIDER={provider}\n'
|
|
|
|
'JENKINS_SALTCLOUD_VM_PLATFORM={platform}\n'
|
|
|
|
'JENKINS_SALTCLOUD_VM_NAME={name}\n').format(name=name,
|
2013-09-08 17:14:03 +00:00
|
|
|
provider=provider,
|
|
|
|
platform=platform)
|
2013-09-06 11:14:27 +00:00
|
|
|
sys.stdout.write(output)
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
|
2013-09-08 15:32:35 +00:00
|
|
|
def download_unittest_reports(vm_name):
|
2013-09-08 17:48:32 +00:00
|
|
|
print('Downloading remote unittest reports...')
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
2013-09-08 15:32:35 +00:00
|
|
|
if os.path.isdir('xml-test-reports'):
|
|
|
|
shutil.rmtree('xml-test-reports')
|
|
|
|
|
|
|
|
os.makedirs('xml-test-reports')
|
|
|
|
|
|
|
|
cmds = (
|
2013-09-08 17:48:32 +00:00
|
|
|
'salt {0} archive.tar zcvf /tmp/xml-test-reports.tar.gz \'*.xml\' cwd=/tmp/salt-tests-tmpdir/xml-test-reports/',
|
2013-09-08 15:32:35 +00:00
|
|
|
'salt {0} cp.push /tmp/xml-test-reports.tar.gz',
|
2013-09-08 17:14:03 +00:00
|
|
|
'tar zxvf /var/cache/salt/master/minions/{0}/files/tmp/xml-test-reports.tar.gz -C xml-test-reports',
|
|
|
|
'rm /var/cache/salt/master/minions/{0}/files/tmp/xml-test-reports.tar.gz'
|
2013-09-08 15:32:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
for cmd in cmds:
|
|
|
|
print('Running CMD: {0}'.format(cmd.format(vm_name)))
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
proc = NonBlockingPopen(
|
|
|
|
cmd.format(vm_name),
|
|
|
|
shell=True,
|
|
|
|
stdout=subprocess.PIPE,
|
2013-09-08 17:18:21 +00:00
|
|
|
stderr=subprocess.PIPE,
|
2013-09-08 15:32:35 +00:00
|
|
|
stream_stds=True
|
|
|
|
)
|
|
|
|
proc.poll_and_read_until_finish()
|
|
|
|
proc.communicate()
|
|
|
|
if proc.returncode != 0:
|
|
|
|
print(
|
|
|
|
'Failed to download unittest data: Exit code: {0}'.format(
|
|
|
|
proc.returncode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2013-09-08 17:18:21 +00:00
|
|
|
|
2013-09-08 16:43:55 +00:00
|
|
|
def download_coverage_report(vm_name):
|
2013-09-08 17:48:32 +00:00
|
|
|
print('Downloading remote coverage report...')
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
2013-09-08 16:43:55 +00:00
|
|
|
cmds = (
|
|
|
|
'salt {0} cp.push /tmp/coverage.xml',
|
2013-09-08 17:48:32 +00:00
|
|
|
'mv /var/cache/salt/master/minions/{0}/files/tmp/coverage.xml {1}'
|
2013-09-08 16:43:55 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
for cmd in cmds:
|
2013-09-08 17:48:32 +00:00
|
|
|
cmd = cmd.format(vm_name, os.environ.get('WORKSPACE', '.'))
|
|
|
|
print('Running CMD: {0}'.format(cmd))
|
2013-09-08 16:43:55 +00:00
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
proc = NonBlockingPopen(
|
2013-09-08 17:48:32 +00:00
|
|
|
cmd,
|
2013-09-08 16:43:55 +00:00
|
|
|
shell=True,
|
|
|
|
stdout=subprocess.PIPE,
|
2013-09-08 17:18:21 +00:00
|
|
|
stderr=subprocess.PIPE,
|
2013-09-08 16:43:55 +00:00
|
|
|
stream_stds=True
|
|
|
|
)
|
|
|
|
proc.poll_and_read_until_finish()
|
|
|
|
proc.communicate()
|
|
|
|
if proc.returncode != 0:
|
|
|
|
print(
|
|
|
|
'Failed to download coverage report: Exit code: {0}'.format(
|
|
|
|
proc.returncode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2013-09-06 11:14:27 +00:00
|
|
|
def run(opts):
|
2013-08-10 07:14:57 +00:00
|
|
|
'''
|
|
|
|
RUN!
|
|
|
|
'''
|
2013-09-06 11:14:27 +00:00
|
|
|
vm_name = os.environ.get(
|
2013-09-06 13:42:20 +00:00
|
|
|
'JENKINS_SALTCLOUD_VM_NAME',
|
2013-09-06 11:14:27 +00:00
|
|
|
generate_vm_name(opts.platform)
|
|
|
|
)
|
|
|
|
|
|
|
|
cmd = (
|
|
|
|
'salt-cloud -l debug --script-args "-D -n git {commit}" -p '
|
2013-09-06 11:32:22 +00:00
|
|
|
'{provider}_{platform} {0}'.format(vm_name, **opts.__dict__)
|
2013-09-06 11:14:27 +00:00
|
|
|
)
|
2013-08-10 07:53:28 +00:00
|
|
|
print('Running CMD: {0}'.format(cmd))
|
2013-08-13 10:54:12 +00:00
|
|
|
sys.stdout.flush()
|
|
|
|
|
2013-08-13 04:54:47 +00:00
|
|
|
proc = NonBlockingPopen(
|
|
|
|
cmd,
|
|
|
|
shell=True,
|
|
|
|
stdout=subprocess.PIPE,
|
2013-08-22 10:59:25 +00:00
|
|
|
stderr=subprocess.STDOUT,
|
2013-08-13 04:54:47 +00:00
|
|
|
stream_stds=True
|
|
|
|
)
|
|
|
|
proc.poll_and_read_until_finish()
|
2013-08-22 10:59:25 +00:00
|
|
|
proc.communicate()
|
2013-08-21 22:40:20 +00:00
|
|
|
|
2013-09-05 04:37:28 +00:00
|
|
|
retcode = proc.returncode
|
|
|
|
if retcode != 0:
|
|
|
|
print('Failed to bootstrap VM. Exit code: {0}'.format(retcode))
|
2013-08-13 14:47:11 +00:00
|
|
|
sys.stdout.flush()
|
2013-09-06 13:40:01 +00:00
|
|
|
if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
|
2013-09-06 11:14:27 +00:00
|
|
|
delete_vm(vm_name)
|
2013-09-05 04:37:28 +00:00
|
|
|
sys.exit(retcode)
|
2013-08-13 10:54:12 +00:00
|
|
|
|
2013-09-05 04:37:28 +00:00
|
|
|
print('VM Bootstrapped. Exit code: {0}'.format(retcode))
|
2013-08-17 05:06:51 +00:00
|
|
|
sys.stdout.flush()
|
|
|
|
|
2013-09-06 16:35:30 +00:00
|
|
|
print('Sleeping for 5 seconds to allow the minion to breath a little')
|
2013-09-06 17:13:05 +00:00
|
|
|
sys.stdout.flush()
|
2013-09-06 16:35:30 +00:00
|
|
|
time.sleep(5)
|
|
|
|
|
2013-08-17 05:06:51 +00:00
|
|
|
# Run tests here
|
2013-09-06 11:14:27 +00:00
|
|
|
cmd = (
|
|
|
|
'salt -t 1800 {vm_name} state.sls {sls} pillar="{pillar}" '
|
|
|
|
'--no-color'.format(
|
|
|
|
sls=opts.sls,
|
|
|
|
pillar=opts.pillar.format(commit=opts.commit),
|
|
|
|
vm_name=vm_name,
|
|
|
|
commit=opts.commit
|
|
|
|
)
|
|
|
|
)
|
2013-08-17 05:06:51 +00:00
|
|
|
print('Running CMD: {0}'.format(cmd))
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
2013-08-22 11:27:58 +00:00
|
|
|
#proc = NonBlockingPopen(
|
|
|
|
proc = subprocess.Popen(
|
2013-08-17 05:06:51 +00:00
|
|
|
cmd,
|
|
|
|
shell=True,
|
|
|
|
stdout=subprocess.PIPE,
|
2013-08-22 10:59:25 +00:00
|
|
|
stderr=subprocess.STDOUT,
|
2013-08-22 11:27:58 +00:00
|
|
|
# stream_stds=True
|
2013-08-17 05:06:51 +00:00
|
|
|
)
|
2013-08-22 11:27:58 +00:00
|
|
|
#proc.poll_and_read_until_finish()
|
2013-08-17 05:06:51 +00:00
|
|
|
stdout, stderr = proc.communicate()
|
|
|
|
|
|
|
|
if stdout:
|
|
|
|
print(stdout)
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
2013-08-13 04:54:47 +00:00
|
|
|
try:
|
|
|
|
match = re.search(r'Test Suite Exit Code: (?P<exitcode>[\d]+)', stdout)
|
|
|
|
retcode = int(match.group('exitcode'))
|
|
|
|
except AttributeError:
|
|
|
|
# No regex matching
|
2013-08-11 18:22:30 +00:00
|
|
|
retcode = 1
|
2013-08-13 04:54:47 +00:00
|
|
|
except ValueError:
|
|
|
|
# Not a number!?
|
|
|
|
retcode = 1
|
|
|
|
except TypeError:
|
|
|
|
# No output!?
|
|
|
|
retcode = 1
|
|
|
|
if stdout:
|
|
|
|
# Anything else, raise the exception
|
|
|
|
raise
|
|
|
|
|
2013-09-06 13:40:01 +00:00
|
|
|
if opts.clean and 'JENKINS_SALTCLOUD_VM_NAME' not in os.environ:
|
2013-09-06 11:14:27 +00:00
|
|
|
delete_vm(vm_name)
|
2013-08-10 08:33:58 +00:00
|
|
|
return retcode
|
2013-08-10 07:23:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
def parse():
|
|
|
|
'''
|
2013-08-11 05:50:47 +00:00
|
|
|
Parse the CLI options
|
2013-08-10 07:23:32 +00:00
|
|
|
'''
|
|
|
|
parser = optparse.OptionParser()
|
2013-09-08 22:41:38 +00:00
|
|
|
parser.add_option(
|
|
|
|
'--platform',
|
2013-09-06 13:40:01 +00:00
|
|
|
default=os.environ.get('JENKINS_SALTCLOUD_VM_PLATFORM', None),
|
2013-09-06 11:14:27 +00:00
|
|
|
help='The target platform, choose from:\ncent6\ncent5\nubuntu12.04')
|
2013-09-08 22:41:38 +00:00
|
|
|
parser.add_option(
|
|
|
|
'--provider',
|
2013-09-06 13:40:01 +00:00
|
|
|
default=os.environ.get('JENKINS_SALTCLOUD_VM_PROVIDER', None),
|
2013-09-06 11:14:27 +00:00
|
|
|
help='The vm provider')
|
2013-09-08 22:41:38 +00:00
|
|
|
parser.add_option(
|
|
|
|
'--commit',
|
2013-09-06 11:14:27 +00:00
|
|
|
help='The git commit to track')
|
2013-09-08 22:41:38 +00:00
|
|
|
parser.add_option(
|
|
|
|
'--sls',
|
2013-09-06 11:14:27 +00:00
|
|
|
default='testrun',
|
|
|
|
help='The sls file to execute')
|
2013-09-08 22:41:38 +00:00
|
|
|
parser.add_option(
|
|
|
|
'--pillar',
|
2013-09-06 11:14:27 +00:00
|
|
|
default='{{git_commit: {commit}}}',
|
|
|
|
help='Pillar values to pass to the sls file')
|
2013-08-10 08:06:03 +00:00
|
|
|
parser.add_option('--no-clean',
|
2013-09-06 11:14:27 +00:00
|
|
|
dest='clean',
|
|
|
|
default=True,
|
|
|
|
action='store_false',
|
|
|
|
help='Clean up the built vm')
|
|
|
|
parser.add_option(
|
|
|
|
'--echo-parseable-environment',
|
|
|
|
default=False,
|
|
|
|
action='store_true',
|
|
|
|
help='Print a parseable KEY=VAL output'
|
|
|
|
)
|
|
|
|
parser.add_option(
|
|
|
|
'--delete-vm',
|
2013-09-06 13:40:01 +00:00
|
|
|
default=os.environ.get('JENKINS_SALTCLOUD_VM_NAME', None),
|
|
|
|
help='Delete a running VM'
|
2013-09-06 11:14:27 +00:00
|
|
|
)
|
2013-09-08 15:32:35 +00:00
|
|
|
parser.add_option(
|
|
|
|
'--download-unittest-reports',
|
|
|
|
default=os.environ.get('JENKINS_SALTCLOUD_VM_NAME', None),
|
2013-09-08 16:43:55 +00:00
|
|
|
help='Download the XML unittest results'
|
|
|
|
)
|
|
|
|
parser.add_option(
|
|
|
|
'--download-coverage-report',
|
|
|
|
default=os.environ.get('JENKINS_SALTCLOUD_VM_NAME', None),
|
|
|
|
help='Download the XML coverage reports'
|
2013-09-08 15:32:35 +00:00
|
|
|
)
|
2013-09-06 11:14:27 +00:00
|
|
|
|
2013-08-10 07:23:32 +00:00
|
|
|
options, args = parser.parse_args()
|
2013-09-06 11:14:27 +00:00
|
|
|
|
2013-09-06 13:40:01 +00:00
|
|
|
if options.delete_vm is not None and not options.commit:
|
2013-09-06 11:14:27 +00:00
|
|
|
delete_vm(options.delete_vm)
|
|
|
|
parser.exit(0)
|
|
|
|
|
2013-09-08 15:32:35 +00:00
|
|
|
if options.download_unittest_reports is not None and not options.commit:
|
2013-09-08 22:41:38 +00:00
|
|
|
try:
|
|
|
|
download_unittest_reports(options.download_unittest_reports)
|
|
|
|
except Exception as exc:
|
|
|
|
print 'Caught exception while downloading unittest reports', exc
|
2013-09-08 15:32:35 +00:00
|
|
|
parser.exit(0)
|
|
|
|
|
2013-09-08 16:43:55 +00:00
|
|
|
if options.download_coverage_report is not None and not options.commit:
|
2013-09-08 22:41:38 +00:00
|
|
|
try:
|
|
|
|
download_coverage_report(options.download_coverage_report)
|
|
|
|
except Exception as exc:
|
|
|
|
print 'Caught exception while downloading coverage reports', exc
|
2013-09-08 16:43:55 +00:00
|
|
|
parser.exit(0)
|
|
|
|
|
2013-08-11 11:42:44 +00:00
|
|
|
if not options.platform:
|
|
|
|
parser.exit('--platform is required')
|
2013-09-06 11:14:27 +00:00
|
|
|
|
2013-08-11 11:42:44 +00:00
|
|
|
if not options.provider:
|
|
|
|
parser.exit('--provider is required')
|
2013-09-06 11:14:27 +00:00
|
|
|
|
|
|
|
if options.echo_parseable_environment:
|
|
|
|
echo_parseable_environment(options.platform, options.provider)
|
|
|
|
parser.exit(0)
|
|
|
|
|
2013-08-11 11:42:44 +00:00
|
|
|
if not options.commit:
|
|
|
|
parser.exit('--commit is required')
|
2013-09-06 11:14:27 +00:00
|
|
|
return options
|
2013-08-10 07:23:32 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2013-09-06 11:14:27 +00:00
|
|
|
exit_code = run(parse())
|
2013-08-10 07:40:47 +00:00
|
|
|
print('Exit Code: {0}'.format(exit_code))
|
2013-08-10 07:23:32 +00:00
|
|
|
sys.exit(exit_code)
|