Add irc bot support to elasticRecheck
This commit is contained in:
parent
c1a2b49ffe
commit
c30528d1f1
@ -11,7 +11,6 @@ Eventually this can be tied into the rechecker tool and launchpad
|
|||||||
|
|
||||||
Future Work
|
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
|
- 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
|
- Turn into a server app
|
||||||
- Make unit tests robust and not need internet
|
- Make unit tests robust and not need internet
|
||||||
|
192
bot.py
Executable file
192
bot.py
Executable 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()
|
@ -1,2 +1,9 @@
|
|||||||
|
[ircbot]
|
||||||
|
nick=RecheckWatchBot
|
||||||
|
pass=
|
||||||
|
server=irc.freenode.net
|
||||||
|
port=6667
|
||||||
|
channel_config=recheckwatchbot.yaml
|
||||||
|
|
||||||
[gerrit]
|
[gerrit]
|
||||||
user=jogo
|
user=jogo
|
||||||
|
@ -17,11 +17,8 @@ class Stream(object):
|
|||||||
Monitors gerrit stream looking for tempest-devstack failures.
|
Monitors gerrit stream looking for tempest-devstack failures.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, user):
|
||||||
config = ConfigParser.ConfigParser()
|
|
||||||
config.read('elasticRecheck.conf')
|
|
||||||
host = 'review.openstack.org'
|
host = 'review.openstack.org'
|
||||||
user = config.get('gerrit', 'user', 'jogo')
|
|
||||||
port = 29418
|
port = 29418
|
||||||
self.gerrit = gerritlib.gerrit.Gerrit(host, user, port)
|
self.gerrit = gerritlib.gerrit.Gerrit(host, user, port)
|
||||||
self.gerrit.startWatching()
|
self.gerrit.startWatching()
|
||||||
@ -154,7 +151,9 @@ class Classifier():
|
|||||||
def main():
|
def main():
|
||||||
classifier = Classifier()
|
classifier = Classifier()
|
||||||
#classifier.test()
|
#classifier.test()
|
||||||
stream = Stream()
|
config = ConfigParser.ConfigParser()
|
||||||
|
user = config.get('gerrit', 'user', 'jogo')
|
||||||
|
stream = Stream(user)
|
||||||
while True:
|
while True:
|
||||||
event = stream.get_failed_tempest()
|
event = stream.get_failed_tempest()
|
||||||
change = event['change']['number']
|
change = event['change']['number']
|
||||||
|
5
recheckwatchbot.yaml
Normal file
5
recheckwatchbot.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
openstack-recheck-watch-test:
|
||||||
|
events:
|
||||||
|
- positive
|
||||||
|
- negative
|
||||||
|
|
@ -1,3 +1,7 @@
|
|||||||
pyelasticsearch
|
pyelasticsearch
|
||||||
gerritlib
|
gerritlib
|
||||||
testtools
|
testtools
|
||||||
|
daemon
|
||||||
|
irc
|
||||||
|
pyyaml
|
||||||
|
lockfile
|
||||||
|
Loading…
x
Reference in New Issue
Block a user