Make Elastic Recheck Watch more reusable
Refactor to use a config class to hold all the params needed so that they can be more easily overridden and reused across all the elastic-recheck tools. In addition, use the new class to make the jobs_regex and ci_username configurable. Change-Id: Ic6f115a6882494bf4c087ded4d7cafa557765c28
This commit is contained in:
parent
35e31cf56b
commit
49999256f4
|
@ -6,6 +6,12 @@ server=irc.freenode.net
|
||||||
port=6667
|
port=6667
|
||||||
channel_config=/home/mtreinish/elasticRecheck/recheckwatchbot.yaml
|
channel_config=/home/mtreinish/elasticRecheck/recheckwatchbot.yaml
|
||||||
|
|
||||||
|
[recheckwatch]
|
||||||
|
#Any project that has a job that matches this regex will have all their
|
||||||
|
#jobs included in the recheck algorithm
|
||||||
|
jobs_regex=(tempest-dsvm-full|gate-tempest-dsvm-virtual-ironic)
|
||||||
|
ci_username=jenkins
|
||||||
|
|
||||||
[gerrit]
|
[gerrit]
|
||||||
user=treinish
|
user=treinish
|
||||||
host=review.openstack.org
|
host=review.openstack.org
|
||||||
|
|
|
@ -42,7 +42,6 @@ openstack-qa:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ConfigParser
|
|
||||||
import daemon
|
import daemon
|
||||||
import os
|
import os
|
||||||
import textwrap
|
import textwrap
|
||||||
|
@ -53,6 +52,7 @@ import yaml
|
||||||
import irc.bot
|
import irc.bot
|
||||||
from launchpadlib import launchpad
|
from launchpadlib import launchpad
|
||||||
|
|
||||||
|
import elastic_recheck.config as er_conf
|
||||||
from elastic_recheck import log as logging
|
from elastic_recheck import log as logging
|
||||||
|
|
||||||
LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
|
LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
|
||||||
|
@ -73,13 +73,16 @@ class ElasticRecheckException(Exception):
|
||||||
|
|
||||||
|
|
||||||
class RecheckWatchBot(irc.bot.SingleServerIRCBot):
|
class RecheckWatchBot(irc.bot.SingleServerIRCBot):
|
||||||
def __init__(self, channels, nickname, password, server, port=6667,
|
def __init__(self, channels, config):
|
||||||
server_password=None):
|
|
||||||
super(RecheckWatchBot, self).__init__(
|
super(RecheckWatchBot, self).__init__(
|
||||||
[(server, port, server_password)], nickname, nickname)
|
[(config.ircbot_server,
|
||||||
|
config.ircbot_port,
|
||||||
|
config.ircbot_server_password)],
|
||||||
|
config.ircbot_nick,
|
||||||
|
config.ircbot_nick)
|
||||||
self.channel_list = channels
|
self.channel_list = channels
|
||||||
self.nickname = nickname
|
self.nickname = config.ircbot_nick
|
||||||
self.password = password
|
self.password = config.ircbot_pass
|
||||||
self.log = logging.getLogger('recheckwatchbot')
|
self.log = logging.getLogger('recheckwatchbot')
|
||||||
|
|
||||||
def on_nicknameinuse(self, c, e):
|
def on_nicknameinuse(self, c, e):
|
||||||
|
@ -111,26 +114,24 @@ class RecheckWatchBot(irc.bot.SingleServerIRCBot):
|
||||||
|
|
||||||
|
|
||||||
class RecheckWatch(threading.Thread):
|
class RecheckWatch(threading.Thread):
|
||||||
def __init__(self, ircbot, channel_config, msgs, username,
|
def __init__(self, ircbot, channel_config, msgs, config=None,
|
||||||
queries, host, key, commenting=True, es_url=None,
|
commenting=True):
|
||||||
db_uri=None):
|
|
||||||
super(RecheckWatch, self).__init__()
|
super(RecheckWatch, self).__init__()
|
||||||
|
self.config = config or er_conf.Config()
|
||||||
self.ircbot = ircbot
|
self.ircbot = ircbot
|
||||||
self.channel_config = channel_config
|
self.channel_config = channel_config
|
||||||
self.msgs = msgs
|
self.msgs = msgs
|
||||||
self.log = logging.getLogger('recheckwatchbot')
|
self.log = logging.getLogger('recheckwatchbot')
|
||||||
self.username = username
|
self.username = config.gerrit_user
|
||||||
self.queries = queries
|
self.queries = config.gerrit_query_file
|
||||||
self.host = host
|
self.host = config.gerrit_host
|
||||||
self.connected = False
|
self.connected = False
|
||||||
self.commenting = commenting
|
self.commenting = commenting
|
||||||
self.key = key
|
self.key = config.gerrit_host_key
|
||||||
self.lp = launchpad.Launchpad.login_anonymously('grabbing bugs',
|
self.lp = launchpad.Launchpad.login_anonymously('grabbing bugs',
|
||||||
'production',
|
'production',
|
||||||
LPCACHEDIR,
|
LPCACHEDIR,
|
||||||
timeout=60)
|
timeout=60)
|
||||||
self.es_url = es_url
|
|
||||||
self.db_uri = db_uri
|
|
||||||
|
|
||||||
def display(self, channel, event):
|
def display(self, channel, event):
|
||||||
display = False
|
display = False
|
||||||
|
@ -200,10 +201,9 @@ class RecheckWatch(threading.Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
# Import here because it needs to happen after daemonization
|
# Import here because it needs to happen after daemonization
|
||||||
import elastic_recheck.elasticRecheck as er
|
import elastic_recheck.elasticRecheck as er
|
||||||
classifier = er.Classifier(self.queries, es_url=self.es_url,
|
classifier = er.Classifier(self.queries, config=self.config)
|
||||||
db_uri=self.db_uri)
|
|
||||||
stream = er.Stream(self.username, self.host, self.key,
|
stream = er.Stream(self.username, self.host, self.key,
|
||||||
es_url=self.es_url)
|
config=self.config)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
event = stream.get_failed_tempest()
|
event = stream.get_failed_tempest()
|
||||||
|
@ -285,7 +285,7 @@ def get_options():
|
||||||
def _main(args, config):
|
def _main(args, config):
|
||||||
logging.setup_logging(config)
|
logging.setup_logging(config)
|
||||||
|
|
||||||
fp = config.get('ircbot', 'channel_config')
|
fp = config.ircbot_channel_config
|
||||||
if fp:
|
if fp:
|
||||||
fp = os.path.expanduser(fp)
|
fp = os.path.expanduser(fp)
|
||||||
if not os.path.exists(fp):
|
if not os.path.exists(fp):
|
||||||
|
@ -301,11 +301,7 @@ def _main(args, config):
|
||||||
if not args.noirc:
|
if not args.noirc:
|
||||||
bot = RecheckWatchBot(
|
bot = RecheckWatchBot(
|
||||||
channel_config.channels,
|
channel_config.channels,
|
||||||
config.get('ircbot', 'nick'),
|
config=config)
|
||||||
config.get('ircbot', 'pass'),
|
|
||||||
config.get('ircbot', 'server'),
|
|
||||||
config.getint('ircbot', 'port'),
|
|
||||||
config.get('ircbot', 'server_password'))
|
|
||||||
else:
|
else:
|
||||||
bot = None
|
bot = None
|
||||||
|
|
||||||
|
@ -313,16 +309,8 @@ def _main(args, config):
|
||||||
bot,
|
bot,
|
||||||
channel_config,
|
channel_config,
|
||||||
msgs,
|
msgs,
|
||||||
config.get('gerrit', 'user'),
|
config=config,
|
||||||
config.get('gerrit', 'query_file'),
|
commenting=not args.nocomment,
|
||||||
config.get('gerrit', 'host', 'review.openstack.org'),
|
|
||||||
config.get('gerrit', 'key'),
|
|
||||||
not args.nocomment,
|
|
||||||
config.get('data_source', 'es_url',
|
|
||||||
'http://logstash.openstack.org:80/elasticsearch'),
|
|
||||||
config.get('data_source', 'db_uri',
|
|
||||||
'mysql+pymysql://query:query@logstash.openstack.org/'
|
|
||||||
'subunit2sql'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
recheck.start()
|
recheck.start()
|
||||||
|
@ -333,18 +321,12 @@ def _main(args, config):
|
||||||
def main():
|
def main():
|
||||||
args = get_options()
|
args = get_options()
|
||||||
|
|
||||||
config = ConfigParser.ConfigParser({'server_password': None})
|
config = er_conf.Config(config_file=args.conffile)
|
||||||
config.read(args.conffile)
|
|
||||||
|
|
||||||
if config.has_option('ircbot', 'pidfile'):
|
|
||||||
pid_fn = os.path.expanduser(config.get('ircbot', 'pidfile'))
|
|
||||||
else:
|
|
||||||
pid_fn = '/var/run/elastic-recheck/elastic-recheck.pid'
|
|
||||||
|
|
||||||
if args.foreground:
|
if args.foreground:
|
||||||
_main(args, config)
|
_main(args, config)
|
||||||
else:
|
else:
|
||||||
pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
|
pid = pid_file_module.TimeoutPIDLockFile(config.pid_fn, 10)
|
||||||
with daemon.DaemonContext(pidfile=pid):
|
with daemon.DaemonContext(pidfile=pid):
|
||||||
_main(args, config)
|
_main(args, config)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ConfigParser
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -38,6 +37,8 @@ except ImportError:
|
||||||
from urllib3.exceptions import InsecurePlatformWarning
|
from urllib3.exceptions import InsecurePlatformWarning
|
||||||
urllib3.disable_warnings(InsecurePlatformWarning)
|
urllib3.disable_warnings(InsecurePlatformWarning)
|
||||||
|
|
||||||
|
|
||||||
|
import elastic_recheck.config as er_conf
|
||||||
import elastic_recheck.elasticRecheck as er
|
import elastic_recheck.elasticRecheck as er
|
||||||
from elastic_recheck import log as logging
|
from elastic_recheck import log as logging
|
||||||
import elastic_recheck.query_builder as qb
|
import elastic_recheck.query_builder as qb
|
||||||
|
@ -111,22 +112,9 @@ def main():
|
||||||
help='print out details as we go')
|
help='print out details as we go')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Start with defaults
|
config = er_conf.Config(config_file=args.conf)
|
||||||
es_url = er.ES_URL
|
|
||||||
ls_url = er.LS_URL
|
|
||||||
db_uri = er.DB_URI
|
|
||||||
|
|
||||||
if args.conf:
|
classifier = er.Classifier(args.queries, config=config)
|
||||||
config = ConfigParser.ConfigParser({'es_url': er.ES_URL,
|
|
||||||
'ls_url': er.LS_URL,
|
|
||||||
'db_uri': er.DB_URI})
|
|
||||||
config.read(args.conf)
|
|
||||||
if config.has_section('data_source'):
|
|
||||||
es_url = config.get('data_source', 'es_url')
|
|
||||||
ls_url = config.get('data_source', 'ls_url')
|
|
||||||
db_uri = config.get('data_source', 'db_uri')
|
|
||||||
|
|
||||||
classifier = er.Classifier(args.queries, es_url=es_url, db_uri=db_uri)
|
|
||||||
|
|
||||||
buglist = []
|
buglist = []
|
||||||
|
|
||||||
|
@ -160,7 +148,7 @@ def main():
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the cluster health for the header
|
# Get the cluster health for the header
|
||||||
es = pyelasticsearch.ElasticSearch(es_url)
|
es = pyelasticsearch.ElasticSearch(config.es_url)
|
||||||
jsondata['status'] = es.health()['status']
|
jsondata['status'] = es.health()['status']
|
||||||
|
|
||||||
for query in classifier.queries:
|
for query in classifier.queries:
|
||||||
|
@ -174,7 +162,7 @@ def main():
|
||||||
logstash_query = qb.encode_logstash_query(query['query'],
|
logstash_query = qb.encode_logstash_query(query['query'],
|
||||||
timeframe=timeframe)
|
timeframe=timeframe)
|
||||||
logstash_url = ("%s/#/dashboard/file/logstash.json?%s"
|
logstash_url = ("%s/#/dashboard/file/logstash.json?%s"
|
||||||
% (ls_url, logstash_query))
|
% (config.ls_url, logstash_query))
|
||||||
bug_data = get_launchpad_bug(query['bug'])
|
bug_data = get_launchpad_bug(query['bug'])
|
||||||
bug = dict(number=query['bug'],
|
bug = dict(number=query['bug'],
|
||||||
query=query['query'],
|
query=query['query'],
|
||||||
|
|
|
@ -15,18 +15,16 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ConfigParser
|
|
||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
import elastic_recheck.elasticRecheck as er
|
import elastic_recheck.config as er_conf
|
||||||
import elastic_recheck.log as logging
|
import elastic_recheck.log as logging
|
||||||
import elastic_recheck.results as er_results
|
import elastic_recheck.results as er_results
|
||||||
|
|
||||||
LOG = logging.getLogger('erquery')
|
LOG = logging.getLogger('erquery')
|
||||||
|
|
||||||
DEFAULT_INDEX_FORMAT = 'logstash-%Y.%m.%d'
|
|
||||||
DEFAULT_NUMBER_OF_DAYS = 10
|
DEFAULT_NUMBER_OF_DAYS = 10
|
||||||
DEFAULT_MAX_QUANTITY = 5
|
DEFAULT_MAX_QUANTITY = 5
|
||||||
IGNORED_ATTRIBUTES = [
|
IGNORED_ATTRIBUTES = [
|
||||||
|
@ -64,11 +62,11 @@ def analyze_attributes(attributes):
|
||||||
return analysis
|
return analysis
|
||||||
|
|
||||||
|
|
||||||
def query(query_file_name, days=DEFAULT_NUMBER_OF_DAYS, es_url=er.ES_URL,
|
def query(query_file_name, config=None, days=DEFAULT_NUMBER_OF_DAYS,
|
||||||
quantity=DEFAULT_MAX_QUANTITY, verbose=False,
|
quantity=DEFAULT_MAX_QUANTITY, verbose=False,):
|
||||||
indexfmt=DEFAULT_INDEX_FORMAT):
|
_config = config or er_conf.Config()
|
||||||
|
es = er_results.SearchEngine(url=_config.es_url,
|
||||||
es = er_results.SearchEngine(url=es_url, indexfmt=indexfmt)
|
indexfmt=_config.es_index_format)
|
||||||
|
|
||||||
with open(query_file_name) as f:
|
with open(query_file_name) as f:
|
||||||
query_file = yaml.load(f.read())
|
query_file = yaml.load(f.read())
|
||||||
|
@ -119,21 +117,10 @@ def main():
|
||||||
"elastic search url, logstash url, and database uri.")
|
"elastic search url, logstash url, and database uri.")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Start with defaults
|
config = er_conf.Config(config_file=args.conf)
|
||||||
es_url = er.ES_URL
|
|
||||||
es_index_format = DEFAULT_INDEX_FORMAT
|
|
||||||
|
|
||||||
if args.conf:
|
query(args.query_file.name, config=config, days=args.days,
|
||||||
config = ConfigParser.ConfigParser({
|
quantity=args.quantity, verbose=args.verbose)
|
||||||
'es_url': er.ES_URL,
|
|
||||||
'index_format': DEFAULT_INDEX_FORMAT})
|
|
||||||
config.read(args.conf)
|
|
||||||
if config.has_section('data_source'):
|
|
||||||
es_url = config.get('data_source', 'es_url')
|
|
||||||
es_index_format = config.get('data_source', 'index_format')
|
|
||||||
|
|
||||||
query(args.query_file.name, days=args.days, quantity=args.quantity,
|
|
||||||
verbose=args.verbose, es_url=es_url, indexfmt=es_index_format)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import collections
|
import collections
|
||||||
import ConfigParser
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
|
@ -27,6 +26,7 @@ import requests
|
||||||
import dateutil.parser as dp
|
import dateutil.parser as dp
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
|
import elastic_recheck.config as er_config
|
||||||
import elastic_recheck.elasticRecheck as er
|
import elastic_recheck.elasticRecheck as er
|
||||||
import elastic_recheck.query_builder as qb
|
import elastic_recheck.query_builder as qb
|
||||||
import elastic_recheck.results as er_results
|
import elastic_recheck.results as er_results
|
||||||
|
@ -315,22 +315,10 @@ def collect_metrics(classifier, fails):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
opts = get_options()
|
opts = get_options()
|
||||||
# Start with defaults
|
|
||||||
es_url = er.ES_URL
|
|
||||||
ls_url = er.LS_URL
|
|
||||||
db_uri = er.DB_URI
|
|
||||||
|
|
||||||
if opts.conf:
|
config = er_config.Config(config_file=opts.conf)
|
||||||
config = ConfigParser.ConfigParser({'es_url': er.ES_URL,
|
|
||||||
'ls_url': er.LS_URL,
|
|
||||||
'db_uri': er.DB_URI})
|
|
||||||
config.read(opts.conf)
|
|
||||||
if config.has_section('data_source'):
|
|
||||||
es_url = config.get('data_source', 'es_url')
|
|
||||||
ls_url = config.get('data_source', 'ls_url')
|
|
||||||
db_uri = config.get('data_source', 'db_uri')
|
|
||||||
|
|
||||||
classifier = er.Classifier(opts.dir, es_url=es_url, db_uri=db_uri)
|
classifier = er.Classifier(opts.dir, config=config)
|
||||||
all_gate_fails = all_fails(classifier)
|
all_gate_fails = all_fails(classifier)
|
||||||
for group in all_gate_fails:
|
for group in all_gate_fails:
|
||||||
fails = all_gate_fails[group]
|
fails = all_gate_fails[group]
|
||||||
|
@ -338,7 +326,7 @@ def main():
|
||||||
continue
|
continue
|
||||||
data = collect_metrics(classifier, fails)
|
data = collect_metrics(classifier, fails)
|
||||||
engine = setup_template_engine(opts.templatedir, group=group)
|
engine = setup_template_engine(opts.templatedir, group=group)
|
||||||
html = classifying_rate(fails, data, engine, classifier, ls_url)
|
html = classifying_rate(fails, data, engine, classifier, config.ls_url)
|
||||||
if opts.output:
|
if opts.output:
|
||||||
out_dir = opts.output
|
out_dir = opts.output
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import ConfigParser
|
||||||
|
import os
|
||||||
|
|
||||||
|
DEFAULT_INDEX_FORMAT = 'logstash-%Y.%m.%d'
|
||||||
|
|
||||||
|
ES_URL = 'http://logstash.openstack.org:80/elasticsearch'
|
||||||
|
LS_URL = 'http://logstash.openstack.org'
|
||||||
|
DB_URI = 'mysql+pymysql://query:query@logstash.openstack.org/subunit2sql'
|
||||||
|
|
||||||
|
JOBS_RE = '(tempest-dsvm-full|gate-tempest-dsvm-virtual-ironic)'
|
||||||
|
CI_USERNAME = 'jenkins'
|
||||||
|
|
||||||
|
PID_FN = '/var/run/elastic-recheck/elastic-recheck.pid'
|
||||||
|
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
config_file=None,
|
||||||
|
config_obj=None,
|
||||||
|
es_url=None,
|
||||||
|
ls_url=None,
|
||||||
|
db_uri=None,
|
||||||
|
jobs_re=None,
|
||||||
|
ci_username=None,
|
||||||
|
pid_fn=None,
|
||||||
|
es_index_format=None):
|
||||||
|
|
||||||
|
self.es_url = es_url or ES_URL
|
||||||
|
self.ls_url = ls_url or LS_URL
|
||||||
|
self.db_uri = db_uri or DB_URI
|
||||||
|
self.jobs_re = jobs_re or JOBS_RE
|
||||||
|
self.ci_username = ci_username or CI_USERNAME
|
||||||
|
self.es_index_format = es_index_format or DEFAULT_INDEX_FORMAT
|
||||||
|
self.pid_fn = pid_fn or PID_FN
|
||||||
|
self.ircbot_channel_config = None
|
||||||
|
self.irc_log_config = None
|
||||||
|
|
||||||
|
if config_file or config_obj:
|
||||||
|
if config_obj:
|
||||||
|
config = config_obj
|
||||||
|
else:
|
||||||
|
config = ConfigParser.ConfigParser(
|
||||||
|
{'es_url': ES_URL,
|
||||||
|
'ls_url': LS_URL,
|
||||||
|
'db_uri': DB_URI,
|
||||||
|
'server_password': None,
|
||||||
|
'ci_username': CI_USERNAME,
|
||||||
|
'jobs_regex': JOBS_RE,
|
||||||
|
'pidfile': PID_FN,
|
||||||
|
'index_format': DEFAULT_INDEX_FORMAT,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
config.read(config_file)
|
||||||
|
|
||||||
|
if config.has_section('data_source'):
|
||||||
|
self.es_url = config.get('data_source', 'es_url')
|
||||||
|
self.ls_url = config.get('data_source', 'ls_url')
|
||||||
|
self.db_uri = config.get('data_source', 'db_uri')
|
||||||
|
self.es_index_format = config.get('data_source',
|
||||||
|
'index_format')
|
||||||
|
|
||||||
|
if config.has_section('recheckwatch'):
|
||||||
|
self.ci_username = config.get('recheckwatch', 'ci_username')
|
||||||
|
self.jobs_regex = config.get('recheckwatch', 'jobs_regex')
|
||||||
|
|
||||||
|
if config.has_section('gerrit'):
|
||||||
|
self.gerrit_user = config.get('gerrit', 'user')
|
||||||
|
self.gerrit_query_file = config.get('gerrit', 'query_file')
|
||||||
|
self.gerrit_host = config.get('gerrit', 'host',
|
||||||
|
'review.openstack.org')
|
||||||
|
self.gerrit_host_key = config.get('gerrit', 'key')
|
||||||
|
|
||||||
|
if config.has_section('ircbot'):
|
||||||
|
self.pid_fn = os.path.expanduser(config.get('ircbot',
|
||||||
|
'pidfile'))
|
||||||
|
self.ircbot_nick = config.get('ircbot', 'nick')
|
||||||
|
self.ircbot_pass = config.get('ircbot', 'pass')
|
||||||
|
self.ircbot_server = config.get('ircbot', 'server')
|
||||||
|
self.ircbot_port = config.getint('ircbot', 'port')
|
||||||
|
self.ircbot_server_password = config.get('ircbot',
|
||||||
|
'server_password')
|
||||||
|
self.ircbot_channel_config = config.get('ircbot',
|
||||||
|
'channel_config')
|
||||||
|
if config.has_option('ircbot', 'log_config'):
|
||||||
|
self.irc_log_config = config.get('ircbot', 'log_config')
|
|
@ -24,14 +24,11 @@ import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import elastic_recheck.config as er_conf
|
||||||
import elastic_recheck.loader as loader
|
import elastic_recheck.loader as loader
|
||||||
import elastic_recheck.query_builder as qb
|
import elastic_recheck.query_builder as qb
|
||||||
from elastic_recheck import results
|
from elastic_recheck import results
|
||||||
|
|
||||||
ES_URL = 'http://logstash.openstack.org:80/elasticsearch'
|
|
||||||
LS_URL = 'http://logstash.openstack.org'
|
|
||||||
DB_URI = 'mysql+pymysql://query:query@logstash.openstack.org/subunit2sql'
|
|
||||||
|
|
||||||
|
|
||||||
def required_files(job):
|
def required_files(job):
|
||||||
files = ['console.html']
|
files = ['console.html']
|
||||||
|
@ -112,7 +109,7 @@ class FailEvent(object):
|
||||||
comment = None
|
comment = None
|
||||||
failed_jobs = []
|
failed_jobs = []
|
||||||
|
|
||||||
def __init__(self, event, failed_jobs):
|
def __init__(self, event, failed_jobs, config=None):
|
||||||
self.change = int(event['change']['number'])
|
self.change = int(event['change']['number'])
|
||||||
self.rev = int(event['patchSet']['number'])
|
self.rev = int(event['patchSet']['number'])
|
||||||
self.project = event['change']['project']
|
self.project = event['change']['project']
|
||||||
|
@ -120,10 +117,10 @@ class FailEvent(object):
|
||||||
self.comment = event["comment"]
|
self.comment = event["comment"]
|
||||||
# TODO(jogo): make FailEvent generate the jobs
|
# TODO(jogo): make FailEvent generate the jobs
|
||||||
self.failed_jobs = failed_jobs
|
self.failed_jobs = failed_jobs
|
||||||
|
self.config = config or er_conf.Config()
|
||||||
|
|
||||||
def is_openstack_project(self):
|
def is_included_job(self):
|
||||||
return ("tempest-dsvm-full" in self.comment or
|
return re.search(self.config.jobs_re, self.comment)
|
||||||
"gate-tempest-dsvm-virtual-ironic" in self.comment)
|
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
return "%d,%d" % (self.change, self.rev)
|
return "%d,%d" % (self.change, self.rev)
|
||||||
|
@ -201,22 +198,22 @@ class Stream(object):
|
||||||
|
|
||||||
log = logging.getLogger("recheckwatchbot")
|
log = logging.getLogger("recheckwatchbot")
|
||||||
|
|
||||||
def __init__(self, user, host, key, thread=True, es_url=None):
|
def __init__(self, user, host, key, config=None, thread=True):
|
||||||
self.es_url = es_url or ES_URL
|
self.config = config or er_conf.Config()
|
||||||
port = 29418
|
port = 29418
|
||||||
self.gerrit = gerritlib.gerrit.Gerrit(host, user, port, key)
|
self.gerrit = gerritlib.gerrit.Gerrit(host, user, port, key)
|
||||||
self.es = results.SearchEngine(self.es_url)
|
self.es = results.SearchEngine(self.config.es_url)
|
||||||
if thread:
|
if thread:
|
||||||
self.gerrit.startWatching()
|
self.gerrit.startWatching()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_jenkins_failure(event):
|
def parse_jenkins_failure(event, ci_username=er_conf.CI_USERNAME):
|
||||||
"""Is this comment a jenkins failure comment."""
|
"""Is this comment a jenkins failure comment."""
|
||||||
if event.get('type', '') != 'comment-added':
|
if event.get('type', '') != 'comment-added':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
username = event['author'].get('username', '')
|
username = event['author'].get('username', '')
|
||||||
if (username not in ['jenkins', 'zuul']):
|
if (username not in [ci_username, 'zuul']):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not ("Build failed" in
|
if not ("Build failed" in
|
||||||
|
@ -324,15 +321,17 @@ class Stream(object):
|
||||||
while True:
|
while True:
|
||||||
event = self.gerrit.getEvent()
|
event = self.gerrit.getEvent()
|
||||||
|
|
||||||
failed_jobs = Stream.parse_jenkins_failure(event)
|
failed_jobs = Stream.parse_jenkins_failure(
|
||||||
|
event, ci_username=self.config.ci_username)
|
||||||
if not failed_jobs:
|
if not failed_jobs:
|
||||||
# nothing to see here, lets try the next event
|
# nothing to see here, lets try the next event
|
||||||
continue
|
continue
|
||||||
|
|
||||||
fevent = FailEvent(event, failed_jobs)
|
fevent = FailEvent(event, failed_jobs)
|
||||||
|
|
||||||
# bail if it's not an openstack project
|
# bail if the failure is from a project
|
||||||
if not fevent.is_openstack_project():
|
# that hasn't run any of the included jobs
|
||||||
|
if not fevent.is_included_job():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.log.info("Looking for failures in %d,%d on %s" %
|
self.log.info("Looking for failures in %d,%d on %s" %
|
||||||
|
@ -379,10 +378,9 @@ class Classifier(object):
|
||||||
|
|
||||||
queries = None
|
queries = None
|
||||||
|
|
||||||
def __init__(self, queries_dir, es_url=None, db_uri=None):
|
def __init__(self, queries_dir, config=None):
|
||||||
self.es_url = es_url or ES_URL
|
self.config = config or er_conf.Config()
|
||||||
self.db_uri = db_uri or DB_URI
|
self.es = results.SearchEngine(self.config.es_url)
|
||||||
self.es = results.SearchEngine(self.es_url)
|
|
||||||
self.queries_dir = queries_dir
|
self.queries_dir = queries_dir
|
||||||
self.queries = loader.load(self.queries_dir)
|
self.queries = loader.load(self.queries_dir)
|
||||||
|
|
||||||
|
@ -409,7 +407,7 @@ class Classifier(object):
|
||||||
# Reload each time
|
# Reload each time
|
||||||
self.queries = loader.load(self.queries_dir)
|
self.queries = loader.load(self.queries_dir)
|
||||||
bug_matches = []
|
bug_matches = []
|
||||||
engine = sqlalchemy.create_engine(self.db_uri)
|
engine = sqlalchemy.create_engine(self.config.db_uri)
|
||||||
Session = orm.sessionmaker(bind=engine)
|
Session = orm.sessionmaker(bind=engine)
|
||||||
session = Session()
|
session = Session()
|
||||||
for x in self.queries:
|
for x in self.queries:
|
||||||
|
|
|
@ -38,9 +38,8 @@ def setup_logging(config=None):
|
||||||
"urllib3.connectionpool": logging.WARN
|
"urllib3.connectionpool": logging.WARN
|
||||||
}
|
}
|
||||||
|
|
||||||
if config is not None and config.has_option('ircbot', 'log_config'):
|
if config and config.irc_log_config:
|
||||||
log_config = config.get('ircbot', 'log_config')
|
fp = os.path.expanduser(config.irc_log_config)
|
||||||
fp = os.path.expanduser(log_config)
|
|
||||||
if not os.path.exists(fp):
|
if not os.path.exists(fp):
|
||||||
raise Exception("Unable to read logging config file at %s" % fp)
|
raise Exception("Unable to read logging config file at %s" % fp)
|
||||||
logging.config.fileConfig(fp)
|
logging.config.fileConfig(fp)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import fixtures
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from elastic_recheck import bot
|
from elastic_recheck import bot
|
||||||
|
import elastic_recheck.config as er_conf
|
||||||
from elastic_recheck import elasticRecheck
|
from elastic_recheck import elasticRecheck
|
||||||
from elastic_recheck import tests
|
from elastic_recheck import tests
|
||||||
import elastic_recheck.tests.unit.fake_gerrit as fg
|
import elastic_recheck.tests.unit.fake_gerrit as fg
|
||||||
|
@ -29,10 +30,11 @@ def _set_fake_config(fake_config):
|
||||||
fake_config.add_section('ircbot')
|
fake_config.add_section('ircbot')
|
||||||
fake_config.add_section('gerrit')
|
fake_config.add_section('gerrit')
|
||||||
# Set fake ircbot config
|
# Set fake ircbot config
|
||||||
|
fake_config.set('ircbot', 'pidfile', er_conf.PID_FN)
|
||||||
fake_config.set('ircbot', 'nick', 'Fake_User')
|
fake_config.set('ircbot', 'nick', 'Fake_User')
|
||||||
fake_config.set('ircbot', 'pass', '')
|
fake_config.set('ircbot', 'pass', '')
|
||||||
fake_config.set('ircbot', 'server', 'irc.fake.net')
|
fake_config.set('ircbot', 'server', 'irc.fake.net')
|
||||||
fake_config.set('ircbot', 'port', 6667)
|
fake_config.set('ircbot', 'port', '6667')
|
||||||
fake_config.set('ircbot', 'channel_config',
|
fake_config.set('ircbot', 'channel_config',
|
||||||
'fake_recheck_watch_bot.yaml')
|
'fake_recheck_watch_bot.yaml')
|
||||||
# Set fake gerrit config
|
# Set fake gerrit config
|
||||||
|
@ -49,22 +51,21 @@ class TestBot(unittest.TestCase):
|
||||||
super(TestBot, self).setUp()
|
super(TestBot, self).setUp()
|
||||||
self.fake_config = ConfigParser.ConfigParser({'server_password': None})
|
self.fake_config = ConfigParser.ConfigParser({'server_password': None})
|
||||||
_set_fake_config(self.fake_config)
|
_set_fake_config(self.fake_config)
|
||||||
|
config = er_conf.Config(config_obj=self.fake_config)
|
||||||
self.channel_config = bot.ChannelConfig(yaml.load(
|
self.channel_config = bot.ChannelConfig(yaml.load(
|
||||||
open('recheckwatchbot.yaml')))
|
open('recheckwatchbot.yaml')))
|
||||||
with mock.patch('launchpadlib.launchpad.Launchpad'):
|
with mock.patch('launchpadlib.launchpad.Launchpad'):
|
||||||
self.recheck_watch = bot.RecheckWatch(
|
self.recheck_watch = bot.RecheckWatch(
|
||||||
None,
|
None,
|
||||||
self.channel_config,
|
self.channel_config,
|
||||||
self.fake_config.get('gerrit', 'user'),
|
None,
|
||||||
self.fake_config.get('gerrit', 'query_file'),
|
config=config,
|
||||||
self.fake_config.get('gerrit', 'host'),
|
commenting=False)
|
||||||
self.fake_config.get('gerrit', 'key'),
|
|
||||||
False)
|
|
||||||
|
|
||||||
def test_read_channel_config_not_specified(self):
|
def test_read_channel_config_not_specified(self):
|
||||||
self.fake_config.set('ircbot', 'channel_config', None)
|
self.fake_config.set('ircbot', 'channel_config', None)
|
||||||
with self.assertRaises(bot.ElasticRecheckException) as exc:
|
with self.assertRaises(bot.ElasticRecheckException) as exc:
|
||||||
bot._main([], self.fake_config)
|
bot._main([], er_conf.Config(config_obj=self.fake_config))
|
||||||
raised_exc = exc.exception
|
raised_exc = exc.exception
|
||||||
self.assertEqual(str(raised_exc), "Channel Config must be specified "
|
self.assertEqual(str(raised_exc), "Channel Config must be specified "
|
||||||
"in config file.")
|
"in config file.")
|
||||||
|
@ -72,7 +73,7 @@ class TestBot(unittest.TestCase):
|
||||||
def test_read_channel_config_invalid_path(self):
|
def test_read_channel_config_invalid_path(self):
|
||||||
self.fake_config.set('ircbot', 'channel_config', 'fake_path.yaml')
|
self.fake_config.set('ircbot', 'channel_config', 'fake_path.yaml')
|
||||||
with self.assertRaises(bot.ElasticRecheckException) as exc:
|
with self.assertRaises(bot.ElasticRecheckException) as exc:
|
||||||
bot._main([], self.fake_config)
|
bot._main([], er_conf.Config(config_obj=self.fake_config))
|
||||||
raised_exc = exc.exception
|
raised_exc = exc.exception
|
||||||
error_msg = "Unable to read layout config file at fake_path.yaml"
|
error_msg = "Unable to read layout config file at fake_path.yaml"
|
||||||
self.assertEqual(str(raised_exc), error_msg)
|
self.assertEqual(str(raised_exc), error_msg)
|
||||||
|
@ -94,17 +95,16 @@ class TestBotWithTestTools(tests.TestCase):
|
||||||
fg.Gerrit))
|
fg.Gerrit))
|
||||||
self.fake_config = ConfigParser.ConfigParser({'server_password': None})
|
self.fake_config = ConfigParser.ConfigParser({'server_password': None})
|
||||||
_set_fake_config(self.fake_config)
|
_set_fake_config(self.fake_config)
|
||||||
|
config = er_conf.Config(config_obj=self.fake_config)
|
||||||
self.channel_config = bot.ChannelConfig(yaml.load(
|
self.channel_config = bot.ChannelConfig(yaml.load(
|
||||||
open('recheckwatchbot.yaml')))
|
open('recheckwatchbot.yaml')))
|
||||||
with mock.patch('launchpadlib.launchpad.Launchpad'):
|
with mock.patch('launchpadlib.launchpad.Launchpad'):
|
||||||
self.recheck_watch = bot.RecheckWatch(
|
self.recheck_watch = bot.RecheckWatch(
|
||||||
None,
|
None,
|
||||||
self.channel_config,
|
self.channel_config,
|
||||||
self.fake_config.get('gerrit', 'user'),
|
None,
|
||||||
self.fake_config.get('gerrit', 'query_file'),
|
config=config,
|
||||||
self.fake_config.get('gerrit', 'host'),
|
commenting=False)
|
||||||
self.fake_config.get('gerrit', 'key'),
|
|
||||||
False)
|
|
||||||
|
|
||||||
def fake_print(self, cls, channel, msg):
|
def fake_print(self, cls, channel, msg):
|
||||||
reference = ("openstack/keystone change: https://review.openstack.org/"
|
reference = ("openstack/keystone change: https://review.openstack.org/"
|
||||||
|
|
|
@ -47,7 +47,7 @@ class TestStream(tests.TestCase):
|
||||||
self.assertEqual(event.url, "https://review.openstack.org/64750")
|
self.assertEqual(event.url, "https://review.openstack.org/64750")
|
||||||
self.assertEqual(sorted(event.build_short_uuids()),
|
self.assertEqual(sorted(event.build_short_uuids()),
|
||||||
["5dd41fe", "d3fd328"])
|
["5dd41fe", "d3fd328"])
|
||||||
self.assertTrue(event.is_openstack_project())
|
self.assertTrue(event.is_included_job())
|
||||||
self.assertEqual(event.queue(), "gate")
|
self.assertEqual(event.queue(), "gate")
|
||||||
self.assertEqual(event.bug_urls(), None)
|
self.assertEqual(event.bug_urls(), None)
|
||||||
self.assertEqual(event.bug_urls_map(), None)
|
self.assertEqual(event.bug_urls_map(), None)
|
||||||
|
@ -65,7 +65,7 @@ class TestStream(tests.TestCase):
|
||||||
self.assertEqual(event.url, "https://review.openstack.org/64749")
|
self.assertEqual(event.url, "https://review.openstack.org/64749")
|
||||||
self.assertEqual(sorted(event.build_short_uuids()),
|
self.assertEqual(sorted(event.build_short_uuids()),
|
||||||
["5dd41fe", "d3fd328"])
|
["5dd41fe", "d3fd328"])
|
||||||
self.assertTrue(event.is_openstack_project())
|
self.assertTrue(event.is_included_job())
|
||||||
self.assertEqual(event.queue(), "check")
|
self.assertEqual(event.queue(), "check")
|
||||||
self.assertEqual(event.bug_urls(), None)
|
self.assertEqual(event.bug_urls(), None)
|
||||||
self.assertEqual(event.bug_urls_map(), None)
|
self.assertEqual(event.bug_urls_map(), None)
|
||||||
|
@ -147,7 +147,7 @@ class TestStream(tests.TestCase):
|
||||||
self.assertEqual(event.url, "https://review.openstack.org/64750")
|
self.assertEqual(event.url, "https://review.openstack.org/64750")
|
||||||
self.assertEqual(sorted(event.build_short_uuids()),
|
self.assertEqual(sorted(event.build_short_uuids()),
|
||||||
["5dd41fe", "d3fd328"])
|
["5dd41fe", "d3fd328"])
|
||||||
self.assertTrue(event.is_openstack_project())
|
self.assertTrue(event.is_included_job())
|
||||||
self.assertEqual(event.queue(), "gate")
|
self.assertEqual(event.queue(), "gate")
|
||||||
self.assertEqual(event.bug_urls(),
|
self.assertEqual(event.bug_urls(),
|
||||||
['https://bugs.launchpad.net/bugs/123456'])
|
['https://bugs.launchpad.net/bugs/123456'])
|
||||||
|
@ -175,7 +175,7 @@ class TestStream(tests.TestCase):
|
||||||
self.assertEqual(event.url, "https://review.openstack.org/64749")
|
self.assertEqual(event.url, "https://review.openstack.org/64749")
|
||||||
self.assertEqual(sorted(event.build_short_uuids()),
|
self.assertEqual(sorted(event.build_short_uuids()),
|
||||||
["5dd41fe", "d3fd328"])
|
["5dd41fe", "d3fd328"])
|
||||||
self.assertTrue(event.is_openstack_project())
|
self.assertTrue(event.is_included_job())
|
||||||
self.assertEqual(event.queue(), "check")
|
self.assertEqual(event.queue(), "check")
|
||||||
self.assertEqual(event.bug_urls(),
|
self.assertEqual(event.bug_urls(),
|
||||||
['https://bugs.launchpad.net/bugs/123456'])
|
['https://bugs.launchpad.net/bugs/123456'])
|
||||||
|
|
Loading…
Reference in New Issue