Further shuffling to get this to fit the standard code layout
* Move tests and code into elastic_recheck * Get 'tox -epy27' working * Tweak setup.cfg for elastic_recheck The goal here is: pip install elastic-recheck, import elastic_recheck Change-Id: Iab9fdf49dc51127a826f0908da3c238c49cbe434
This commit is contained in:
224
elastic_recheck/bot.py
Executable file
224
elastic_recheck/bot.py
Executable file
@@ -0,0 +1,224 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
# 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 irc.bot
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import yaml
|
||||
|
||||
try:
|
||||
import daemon.pidlockfile
|
||||
pid_file_module = daemon.pidlockfile
|
||||
except Exception:
|
||||
# as of python-daemon 1.6 it doesn't bundle pidlockfile anymore
|
||||
# instead it depends on lockfile-0.9.1
|
||||
import daemon.pidfile
|
||||
pid_file_module = daemon.pidfile
|
||||
|
||||
|
||||
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('recheckwatchbot')
|
||||
|
||||
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, queries, host, key):
|
||||
threading.Thread.__init__(self)
|
||||
self.ircbot = ircbot
|
||||
self.channel_config = channel_config
|
||||
self.log = logging.getLogger('recheckwatchbot')
|
||||
self.username = username
|
||||
self.queries = queries
|
||||
self.host = host
|
||||
self.connected = False
|
||||
self.key = key
|
||||
|
||||
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):
|
||||
# Import here because it needs to happen after daemonization
|
||||
from elasticRecheck import Classifier
|
||||
from elasticRecheck import Stream
|
||||
classifier = Classifier(self.queries)
|
||||
stream = Stream(self.username, self.host, self.key)
|
||||
while True:
|
||||
event = stream.get_failed_tempest()
|
||||
change = event['change']['number']
|
||||
rev = event['patchSet']['number']
|
||||
change_id = "%s,%s" % (change, rev)
|
||||
project = event['change']['project']
|
||||
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)
|
||||
stream.leave_comment(project, change_id, bug_number)
|
||||
|
||||
|
||||
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'),
|
||||
config.get('gerrit', 'query_file'),
|
||||
config.get('gerrit', 'host', 'review.openstack.org'),
|
||||
config.get('gerrit', 'key'))
|
||||
|
||||
recheck.start()
|
||||
bot.start()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print "Usage: %s CONFIGFILE" % sys.argv[0]
|
||||
sys.exit(1)
|
||||
|
||||
pid = pid_file_module.TimeoutPIDLockFile(
|
||||
"/tmp/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()
|
||||
Reference in New Issue
Block a user