We want to default to running all tox environments under python 3, so set the basepython value in each environment. We do not want to specify a minor version number, because we do not want to have to update the file every time we upgrade python. We do not want to set the override once in testenv, because that breaks the more specific versions used in default environments like py35 and py36. Change-Id: I12967d5f5e707efe2b271b28bc7ea4b40e7f1c15
159 lines
6.0 KiB
Python
159 lines
6.0 KiB
Python
# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from collections import defaultdict
|
|
from glob import glob
|
|
import os
|
|
import time
|
|
|
|
from six import moves
|
|
|
|
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: # nosec
|
|
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 moves.range(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: # nosec
|
|
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):
|
|
if self.high_watermarks.get(instance.get('name'), None) is None:
|
|
# On the first run of check(), prime the high_watermarks dict
|
|
# (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)
|
|
|
|
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()
|