161 lines
6.2 KiB
Python
161 lines
6.2 KiB
Python
from collections import defaultdict
|
|
from glob import glob
|
|
import os
|
|
import time
|
|
|
|
try:
|
|
from xml.etree.ElementTree import ElementTree
|
|
except ImportError:
|
|
try:
|
|
from elementtree import ElementTree
|
|
except ImportError:
|
|
pass
|
|
|
|
from monasca_agent.collector.checks import AgentCheck
|
|
from monasca_agent.common.util import get_hostname
|
|
|
|
|
|
class Skip(Exception):
|
|
|
|
"""Raised by :class:`Jenkins` when it comes across
|
|
|
|
a build or job that should be excluded from being checked.
|
|
"""
|
|
|
|
def __init__(self, reason, dir_name):
|
|
message = 'skipping build or job at %s because %s' % (dir_name, reason)
|
|
Exception.__init__(self, message)
|
|
|
|
|
|
class Jenkins(AgentCheck):
|
|
datetime_format = '%Y-%m-%d_%H-%M-%S'
|
|
|
|
def __init__(self, name, init_config, agent_config):
|
|
AgentCheck.__init__(self, name, init_config, agent_config)
|
|
self.high_watermarks = {}
|
|
|
|
def _extract_timestamp(self, dir_name):
|
|
if not os.path.isdir(dir_name):
|
|
raise Skip('its not a build directory', dir_name)
|
|
|
|
try:
|
|
# Parse the timestamp from the directory name
|
|
date_str = os.path.basename(dir_name)
|
|
time_tuple = time.strptime(date_str, self.datetime_format)
|
|
return time.mktime(time_tuple)
|
|
except ValueError:
|
|
raise Exception("Error with build directory name, not a parsable date: %s" % (dir_name))
|
|
|
|
def _get_build_metadata(self, dir_name):
|
|
if os.path.exists(os.path.join(dir_name, 'jenkins_build.tar.gz')):
|
|
raise Skip('the build has already been archived', dir_name)
|
|
|
|
# Read the build.xml metadata file that Jenkins generates
|
|
build_metadata = os.path.join(dir_name, 'build.xml')
|
|
|
|
if not os.access(build_metadata, os.R_OK):
|
|
self.log.debug("Can't read build file at %s" % (build_metadata))
|
|
raise Exception("Can't access build.xml at %s" % (build_metadata))
|
|
else:
|
|
tree = ElementTree()
|
|
tree.parse(build_metadata)
|
|
|
|
keys = ['result', 'number', 'duration']
|
|
|
|
kv_pairs = ((k, tree.find(k)) for k in keys)
|
|
d = dict([(k, v.text) for k, v in kv_pairs if v is not None])
|
|
|
|
try:
|
|
d['branch'] = tree.find('actions') \
|
|
.find('hudson.plugins.git.util.BuildData') \
|
|
.find('buildsByBranchName') \
|
|
.find('entry') \
|
|
.find('hudson.plugins.git.util.Build') \
|
|
.find('revision') \
|
|
.find('branches') \
|
|
.find('hudson.plugins.git.Branch') \
|
|
.find('name') \
|
|
.text
|
|
except Exception:
|
|
pass
|
|
return d
|
|
|
|
def _get_build_results(self, instance_key, job_dir):
|
|
job_name = os.path.basename(job_dir)
|
|
|
|
try:
|
|
dirs = glob(os.path.join(job_dir, 'builds', '*_*'))
|
|
if len(dirs) > 0:
|
|
dirs = sorted(dirs, reverse=True)
|
|
# We try to get the last valid build
|
|
for index in xrange(0, len(dirs) - 1):
|
|
dir_name = dirs[index]
|
|
try:
|
|
timestamp = self._extract_timestamp(dir_name)
|
|
except Skip:
|
|
continue
|
|
|
|
# Check if it's a new build
|
|
if timestamp > self.high_watermarks[instance_key][job_name]:
|
|
# If we can't get build metadata, we try the previous one
|
|
try:
|
|
build_metadata = self._get_build_metadata(dir_name)
|
|
except Exception:
|
|
continue
|
|
|
|
output = {
|
|
'job_name': job_name,
|
|
'timestamp': timestamp,
|
|
'event_type': 'build result'
|
|
}
|
|
output.update(build_metadata)
|
|
self.high_watermarks[instance_key][job_name] = timestamp
|
|
yield output
|
|
# If it not a new build, stop here
|
|
else:
|
|
break
|
|
except Exception as e:
|
|
self.log.error("Error while working on job %s, exception: %s" % (job_name, e))
|
|
|
|
def check(self, instance, create_event=True):
|
|
dimensions = self._set_dimensions(None, instance)
|
|
if self.high_watermarks.get(instance.get('name'), None) is None:
|
|
# On the first run of check(), prime the high_watermarks dict
|
|
# so that we only send events that occurred after the agent
|
|
# started.
|
|
# (Setting high_watermarks in the next statement prevents
|
|
# any kind of infinite loop (assuming nothing ever sets
|
|
# high_watermarks to None again!))
|
|
self.high_watermarks[instance.get('name')] = defaultdict(lambda: 0)
|
|
self.check(instance, create_event=False)
|
|
|
|
jenkins_home = instance.get('jenkins_home', None)
|
|
|
|
if not jenkins_home:
|
|
raise Exception("No jenkins_home directory set in the config file")
|
|
|
|
jenkins_jobs_dir = os.path.join(jenkins_home, 'jobs', '*')
|
|
job_dirs = glob(jenkins_jobs_dir)
|
|
|
|
if not job_dirs:
|
|
raise Exception('No jobs found in `%s`! '
|
|
'Check `jenkins_home` in your config' % (jenkins_jobs_dir))
|
|
|
|
for job_dir in job_dirs:
|
|
for output in self._get_build_results(instance.get('name'), job_dir):
|
|
output['host'] = get_hostname(self.agent_config)
|
|
if create_event:
|
|
self.log.debug("Creating event for job: %s" % output['job_name'])
|
|
self.event(output)
|
|
|
|
dimensions.update({'job_name': output['job_name']})
|
|
if 'branch' in output:
|
|
dimensions.update({'branch': output['branch']})
|
|
self.gauge("jenkins.job.duration", float(
|
|
output['duration']) / 1000.0, dimensions=dimensions)
|
|
|
|
if output['result'] == 'SUCCESS':
|
|
self.increment('jenkins.job.success', dimensions=dimensions)
|
|
else:
|
|
self.increment('jenkins.job.failure', dimensions=dimensions)
|