Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add result and build number to tags for jenkins events #1068

Merged
merged 5 commits into from
Aug 29, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion checks.d/jenkins.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,24 @@ def _get_build_results(self, instance_key, job_dir):
except Exception:
continue

# If the metadata does not include the results, then
# the build is still running so do not process it yet.
build_result = build_metadata.get('result', None)
if build_result is None:
break

output = {
'job_name': job_name,
'timestamp': timestamp,
'event_type': 'build result'
}

output.update(build_metadata)
self.high_watermarks[instance_key][job_name] = timestamp
self.log.debug("Processing %s results '%s'" % (job_name, output))

yield output

# If it not a new build, stop here
else:
break
Expand Down Expand Up @@ -140,7 +150,12 @@ def check(self, instance, create_event=True):
self.log.debug("Creating event for job: %s" % output['job_name'])
self.event(output)

tags = ['job_name:%s' % output['job_name']]
tags = [
'job_name:%s' % output['job_name'],
'result:%s' % output['result'],
'build_number:%s' % output['number']
]

if 'branch' in output:
tags.append('branch:%s' % output['branch'])
self.gauge("jenkins.job.duration", float(output['duration'])/1000.0, tags=tags)
Expand Down
172 changes: 172 additions & 0 deletions tests/test_jenkins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import unittest
import os
from collections import defaultdict
import datetime
import tempfile
import shutil
import logging
import xml.etree.ElementTree as ET

from tests.common import get_check

logger = logging.getLogger(__file__)

DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S'
LOG_DATA = 'Finished: SUCCESS'

SUCCESSFUL_BUILD = {'number': '99', 'result': 'SUCCESS', 'duration': '60'}
NO_RESULTS_YET = {'number': '99', 'duration': '60'}
UNSUCCESSFUL_BUILD = {'number': '99', 'result': 'ABORTED', 'duration': '60'}

CONFIG = """
init_config:

instances:
- name: default
jenkins_home: <JENKINS_HOME>
"""

def dict_to_xml(metadata_dict):
""" Convert a dict to xml for use in a build.xml file """
build = ET.Element('build')
for k, v in metadata_dict.iteritems():
node = ET.SubElement(build, k)
node.text = v

return ET.tostring(build)

def write_file(file_name, log_data):
with open(file_name, 'w') as log_file:
log_file.write(log_data)

class TestJenkins(unittest.TestCase):

def setUp(self):
self.tmp_dir = tempfile.mkdtemp()
self.config_yaml = CONFIG.replace('<JENKINS_HOME>', self.tmp_dir)
self._create_old_build()

def tearDown(self):
# Clean up the temp directory
shutil.rmtree(self.tmp_dir)

def _create_old_build(self):
# As coded, the jenkins dd agent needs more than one result
# in order to get the last valid build.
# Create one for yesterday.
metadata = dict_to_xml(SUCCESSFUL_BUILD)
yesterday = datetime.date.today() - datetime.timedelta(days=1)
self._populate_build_dir(metadata, yesterday)

def _create_check(self):
# Create the jenkins check
self.check, instances = get_check('jenkins', self.config_yaml)
self.instance = instances[0]

def _populate_build_dir(self, metadata, time=None):
# The jenkins dd agent requires the build metadata file and a log file of results
time = time or datetime.datetime.now()
datestring = time.strftime(DATETIME_FORMAT)
build_dir = os.path.join(self.tmp_dir, 'jobs', 'foo', 'builds', datestring)
os.makedirs(build_dir)

log_file = os.path.join(build_dir, 'log')
log_data = LOG_DATA
write_file(log_file, log_data)

metadata_file = os.path.join(build_dir, 'build.xml')
build_metadata = metadata
write_file(metadata_file, build_metadata)

def testParseBuildLog(self):
"""
Test doing a jenkins check. This will parse the logs but since there was no
previous high watermark no event will be created.
"""
metadata = dict_to_xml(SUCCESSFUL_BUILD)

self._populate_build_dir(metadata)
self._create_check()
self.check.check(self.instance)

# The check method does not return anything, so this testcase passes
# if the high_watermark was set and no exceptions were raised.
self.assertTrue(self.check.high_watermarks[self.instance['name']]['foo'] > 0)

def testCheckSuccessfulEvent(self):
"""
Test that a successful build will create the correct metrics.
"""
metadata = dict_to_xml(SUCCESSFUL_BUILD)

self._populate_build_dir(metadata)
self._create_check()

# Set the high_water mark so that the next check will create events
self.check.high_watermarks['default'] = defaultdict(lambda: 0)

self.check.check(self.instance)

metrics = self.check.get_metrics()

metrics_names = [m[0] for m in metrics]
assert len(metrics_names) == 2
assert 'jenkins.job.success' in metrics_names
assert 'jenkins.job.duration' in metrics_names

metrics_tags = [m[3] for m in metrics]
for tag in metrics_tags:
assert 'job_name:foo' in tag.get('tags')
assert 'result:SUCCESS' in tag.get('tags')
assert 'build_number:99' in tag.get('tags')


def testCheckUnsuccessfulEvent(self):
"""
Test that an unsuccessful build will create the correct metrics.
"""
metadata = dict_to_xml(UNSUCCESSFUL_BUILD)

self._populate_build_dir(metadata)
self._create_check()

# Set the high_water mark so that the next check will create events
self.check.high_watermarks['default'] = defaultdict(lambda: 0)

self.check.check(self.instance)

metrics = self.check.get_metrics()

metrics_names = [m[0] for m in metrics]
assert len(metrics_names) == 2
assert 'jenkins.job.failure' in metrics_names
assert 'jenkins.job.duration' in metrics_names

metrics_tags = [m[3] for m in metrics]
for tag in metrics_tags:
assert 'job_name:foo' in tag.get('tags')
assert 'result:ABORTED' in tag.get('tags')
assert 'build_number:99' in tag.get('tags')


def testCheckWithRunningBuild(self):
"""
Test under the conditions of a jenkins build still running.
The build.xml file will exist but it will not yet have a result.
"""
metadata = dict_to_xml(NO_RESULTS_YET)

self._populate_build_dir(metadata)
self._create_check()

# Set the high_water mark so that the next check will create events
self.check.high_watermarks['default'] = defaultdict(lambda: 0)

self.check.check(self.instance)

# The check method does not return anything, so this testcase passes
# if the high_watermark was NOT updated and no exceptions were raised.
assert self.check.high_watermarks[self.instance['name']]['foo'] == 0

if __name__ == '__main__':
unittest.main()