Skip to content

Commit

Permalink
Merge pull request #1068 from jzoldak/jenkins-add-result-tag
Browse files Browse the repository at this point in the history
Add result and build number to tags for jenkins events
  • Loading branch information
Remi Hakim committed Aug 29, 2014
2 parents 389fc10 + fd6313f commit 3e8b54d
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 1 deletion.
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()

0 comments on commit 3e8b54d

Please sign in to comment.