Add irc bot support to elasticRecheck

This commit is contained in:
Matthew 2013-09-16 21:49:22 -04:00
parent c1a2b49ffe
commit c30528d1f1
6 changed files with 212 additions and 6 deletions

View File

@ -11,7 +11,6 @@ Eventually this can be tied into the rechecker tool and launchpad
Future Work
------------
- IRC bot output to #openstack-qa with output
- Pull in list of queries from a more flexible source, so a commit isn't needed to update each time
- Turn into a server app
- Make unit tests robust and not need internet

192
bot.py Executable file
View File

@ -0,0 +1,192 @@
#! /usr/bin/env python
# The configuration file should look like:
"""
[ircbot]
nick=NICKNAME
pass=PASSWORD
server=irc.freenode.net
port=6667
server_password=SERVERPASS
channel_config=/path/to/yaml/config
[gerrit]
user=gerrit2
"""
# The yaml channel config should look like:
"""
openstack-qa:
events:
- positive
- negative
"""
import ConfigParser
import daemon
import lockfile
import irc.bot
import os
import sys
import threading
import time
import yaml
import logging
from elasticRecheck import Stream
from elasticRecheck import Classifier
class RecheckWatchBot(irc.bot.SingleServerIRCBot):
def __init__(self, channels, nickname, password, server, port=6667,
server_password=None):
irc.bot.SingleServerIRCBot.__init__(
self, [(server, port, server_password)], nickname, nickname)
self.channel_list = channels
self.nickname = nickname
self.password = password
self.log = logging.getLogger('gerritbot')
def on_nicknameinuse(self, c, e):
self.log.info('Nick previously in use, recovering.')
c.nick(c.get_nickname() + "_")
c.privmsg("nickserv", "identify %s " % self.password)
c.privmsg("nickserv", "ghost %s %s" % (self.nickname, self.password))
c.privmsg("nickserv", "release %s %s" % (self.nickname, self.password))
time.sleep(1)
c.nick(self.nickname)
self.log.info('Nick previously in use, recovered.')
def on_welcome(self, c, e):
self.log.info('Identifying with IRC server.')
c.privmsg("nickserv", "identify %s " % self.password)
self.log.info('Identified with IRC server.')
for channel in self.channel_list:
c.join(channel)
self.log.info('Joined channel %s' % channel)
time.sleep(0.5)
def send(self, channel, msg):
self.log.info('Sending "%s" to %s' % (msg, channel))
self.connection.privmsg(channel, msg)
time.sleep(0.5)
class RecheckWatch(threading.Thread):
def __init__(self, ircbot, channel_config, username):
threading.Thread.__init__(self)
self.ircbot = ircbot
self.channel_config = channel_config
self.log = logging.getLogger('recheckwatchbot')
self.username = username
self.connected = False
def new_error(self, channel, data):
msg = '%s change: %s failed tempest with an unrecognized error' % (
data['change']['project'],
data['change']['url'])
self.log.info('Compiled Message %s: %s' % (channel, msg))
self.ircbot.send(channel, msg)
def error_found(self, channel, data):
msg = ('%s change: %s failed tempest because of: '
'https://bugs.launchpad.net/bugs/%s' % (
data['change']['project'],
data['change']['url'],
data['bug_number']))
self.log.info('Compiled Message %s: %s' % (channel, msg))
self.ircbot.send(channel, msg)
def _read(self, data):
for channel in self.channel_config.channels:
if data.get('bug_number'):
if channel in self.channel_config.events['positive']:
self.error_found(channel, data)
else:
if channel in self.channel_config.events['negative']:
self.new_error(channel, data)
def run(self):
classifier = Classifier()
stream = Stream(self.username)
while True:
event = stream.get_failed_tempest()
change = event['change']['number']
rev = event['patchSet']['number']
bug_number = classifier.classify(change, rev, event['comment'])
if bug_number is None:
self._read(event)
else:
event['bug_number'] = bug_number
self._read(event)
class ChannelConfig(object):
def __init__(self, data):
self.data = data
keys = data.keys()
for key in keys:
if key[0] != '#':
data['#' + key] = data.pop(key)
self.channels = data.keys()
self.events = {}
for channel, val in self.data.iteritems():
for event in val['events']:
event_set = self.events.get(event, set())
event_set.add(channel)
self.events[event] = event_set
def _main():
config = ConfigParser.ConfigParser({'server_password': None})
config.read(sys.argv[1])
setup_logging(config)
fp = config.get('ircbot', 'channel_config')
if fp:
fp = os.path.expanduser(fp)
if not os.path.exists(fp):
raise Exception("Unable to read layout config file at %s" % fp)
else:
raise Exception("Channel Config must be specified in config file.")
channel_config = ChannelConfig(yaml.load(open(fp)))
bot = RecheckWatchBot(channel_config.channels,
config.get('ircbot', 'nick'),
config.get('ircbot', 'pass'),
config.get('ircbot', 'server'),
config.getint('ircbot', 'port'),
config.get('ircbot', 'server_password'))
recheck = RecheckWatch(bot, channel_config, config.get('gerrit', 'user'))
recheck.start()
bot.start()
def main():
if len(sys.argv) != 2:
print "Usage: %s CONFIGFILE" % sys.argv[0]
sys.exit(1)
pid = lockfile.FileLock(
"/var/run/recheckwatchbot/recheckwatchbot.pid", 10)
# with daemon.DaemonContext(pidfile=pid):
_main()
def setup_logging(config):
if config.has_option('ircbot', 'log_config'):
log_config = config.get('ircbot', 'log_config')
fp = os.path.expanduser(log_config)
if not os.path.exists(fp):
raise Exception("Unable to read logging config file at %s" % fp)
logging.config.fileConfig(fp)
else:
logging.basicConfig(level=logging.DEBUG)
if __name__ == "__main__":
main()

View File

@ -1,2 +1,9 @@
[ircbot]
nick=RecheckWatchBot
pass=
server=irc.freenode.net
port=6667
channel_config=recheckwatchbot.yaml
[gerrit]
user=jogo

View File

@ -17,11 +17,8 @@ class Stream(object):
Monitors gerrit stream looking for tempest-devstack failures.
"""
def __init__(self):
config = ConfigParser.ConfigParser()
config.read('elasticRecheck.conf')
def __init__(self, user):
host = 'review.openstack.org'
user = config.get('gerrit', 'user', 'jogo')
port = 29418
self.gerrit = gerritlib.gerrit.Gerrit(host, user, port)
self.gerrit.startWatching()
@ -154,7 +151,9 @@ class Classifier():
def main():
classifier = Classifier()
#classifier.test()
stream = Stream()
config = ConfigParser.ConfigParser()
user = config.get('gerrit', 'user', 'jogo')
stream = Stream(user)
while True:
event = stream.get_failed_tempest()
change = event['change']['number']

5
recheckwatchbot.yaml Normal file
View File

@ -0,0 +1,5 @@
openstack-recheck-watch-test:
events:
- positive
- negative

View File

@ -1,3 +1,7 @@
pyelasticsearch
gerritlib
testtools
daemon
irc
pyyaml
lockfile