#!/usr/bin/env python
# coding: utf-8
import re
import subprocess
import shlex
import os
import argparse, sys, collections
import urllib2
import json
import httplib
import time, datetime
# Just server address for all builds, otherwise only for given list of views
# place in jenkins
# get total number of failed builds
# run the job daily
# Create email groups
# Arguments: A. comma separated total number of failed builds - increasing order of builds, B. comma separated email addresses corresponding to the build-number groups
# usage: ./p4_create_user.py user_id manager_id group_id location_id : 4 required arguments
script_objective = '''
Purpose of the script:
Send email to different group of email addresses based on total number of failed builds
'''
# Message display when there are not enough arguments on command line
if len(sys.argv) < 2:
print "Please run the script with argument -h for help"
exit(0)
# argument parsing:
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
add_help=True, description = '', epilog = script_objective)
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument('-view_urls', nargs = '?', const = '', help = 'Comma separated Jenkins view(s)/server url', required = True)
requiredNamed.add_argument('-build_nums', nargs = '?', const = '', help = 'Comma separated build mumbers for mapping with email groups', required = True)
requiredNamed.add_argument('-emails', nargs = '?', const = '', help = 'Semicolon separated email groups for mapping with build groups. Emails in a group are comma separated', required = True)
args = parser.parse_args()
view_urls = args.view_urls
build_nums = args.build_nums # group = its number or more
emails = args.emails
email_domain = '@ruckuswireless.com'
email_from = 'releng-team' + email_domain
email_to = 'gopal.singh' + email_domain
email_cc = ''
#email_to = email_from
#email_cc = 'releng-team' + email_domain
email_bcc = ''
subject = 'Build failure notification'
email_msg = '''
-Jenkins build failed
'''
jenkins_server = 'http://jenkins.video54.local'
# Jenkins class
INFO = 'api/json'
SCRIPT_OBJECTIVE = '''
Purpose of the script: Detects totals builds, failed builds, and automation failures.
'''
class JenkinsException(Exception): pass
class Jenkins(object):
def __init__(self, url, username=None, password=None):
server_split = (self.fix_server_url(url)).split('/')
self.server = server_split[0] + '//' + server_split[2] + '/'
self.auth = None
def fix_server_url(self, urls): # urls may be comma separated server, view, job, or build urls
url = urls.split(',')[0]
if url[-1] != '/':
url = url + '/'
return url
def fix_urls(self, urls): # urls may be comma separated server, view, job, or build urls
url_str = ''
for url in urls.split(','):
if url[-1] != '/':
url_str += url + '/,'
else:
url_str += url + ','
return url_str.rstrip(',')
def get_jenkins_server_url(self): # extract server url from list of comma separated urls
server_url = (self.server).split('')
return server_url
def jenkins_open(self, req): # req = HTTP request using urllib2
try:
if self.auth:
req.add_header('Authorization', self.auth)
return urllib2.urlopen(req).read()
except urllib2.HTTPError, e:
# Jenkins's funky authentication means its highly impossible to distinguish errors.
if e.code in [401, 403, 500]:
raise JenkinsException('Error in request. Possibly authentication failed [%s]'%(e.code))
def get_info(self, url = None): # get_info gets api/json value for the server url (if server url is provided as jenkinsurl)
if url is None:
url = self.server
try:
return json.loads(self.jenkins_open(urllib2.Request(url + INFO)))
except urllib2.HTTPError:
raise JenkinsException("Error communicating with server[%s]"%url)
except httplib.BadStatusLine:
raise JenkinsException("Error communicating with server[%s]"%url)
except ValueError:
raise JenkinsException("Could not parse JSON info for server[%s]"%url)
except:
raise JenkinsException("Possibly wrong url[%s]"%url)
@staticmethod
def valid_view(info_str): # checks if the given url is a view url and has associated jobs
if 'description' in info_str and 'jobs' in info_str and (len(info_str['jobs']) > 0):
return True
else:
return False
@staticmethod
def valid_job(info_str): # checks if the given url is a job url
if 'builds' in info_str and (len(info_str['builds']) > 0):
return True
else:
return False
@staticmethod
def valid_build(info_str): # checks if the given url is a view url and has associated jobs
if 'number' in info_str:
return True
else:
return False
def get_consecutive_failed_builds_in_job(self, job_url, max_builds_count=None):
jenkins_info = self.get_info(job_url)
builds_list = []
try:
first_build = jenkins_info['firstBuild']['number']
except:
print 'No valid build found: for: ' + job_url
return 0, job_url
last_build = jenkins_info['lastBuild']['number']
build_count = 0
for build_num in reversed(range(first_build, last_build + 1)): # range is reversed to start from the latest build
try:
build_url = job_url + str(build_num) + '/'
jenkins_info = self.get_info(url=build_url)
if jenkins_info['result'] == 'SUCCESS': break
if jenkins_info['result'] == 'FAILURE':
builds_list.append(build_url)
build_count += 1
if max_builds_count is not None and build_count >= max_builds_count:
break
except: continue
return build_count, builds_list
def get_consecutive_failed_builds_in_views(self, urls_list, max_builds_count=None): # the list of view urls should be comma separated string
total_failed_builds = []
list_urls = self.fix_urls(urls_list).split(',')
for url in list_urls:
try:
jenkins_info = self.get_info(url)
except:
print 'Not a valid view: ' + url
continue
if self.valid_view(jenkins_info):
failed_builds = []
for job in jenkins_info['jobs']:
num, urls = self.get_consecutive_failed_builds_in_job(job['url'], max_builds_count)
if num > 0:
failed_builds.append([job['url'], num])
print 'Processing JOB: ' + job['url'] + ' ... Failed Builds: ' + str(num)
total_failed_builds.append({url: failed_builds})
else: continue # continue to process next url if the current one is not a valid view url
return total_failed_builds
def update_email_data(build_data, build_nums, view_urls, emails):
email_data = []
for x in build_data:
job_data = []
for y in x.values()[0]: # x.values()[0] => list of job and corresponding failed builds, i.e.: [[u'http://jenkins.video54.local/job/ML_DAILY_SCG_AUTO_INIT/', 2]]
view_email_groups = select_view_email_groups(x.keys()[0], view_urls, emails) # x.keys()[0] => view_url
view_email_groups = fix_builds_email_group(build_nums, view_email_groups)
email_group = select_email_group(build_nums, view_email_groups, y[1]) # y[1] => number of failed builds for the job y[0]
job_data.append(y + [email_group])
email_data.append({x.keys()[0]:job_data})
return email_data
def runcmd(cmd):
cmd = shlex.split(cmd)
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, error = proc.communicate()
return out + error
except:
msg = 'Invalid Command: ' + str(cmd)
print msg
return msg
def parse_buildnums_emails(build_nums, emails):
list_buildnums = build_nums.split(',')
a = [int(x) for x in list_buildnums]
list_emails = emails.split(':')
pair = dict(zip(a, list_emails))
return collections.OrderedDict(sorted(pair.items()))
def parse_views_emails(build_nums, emails):
list_buildnums = build_nums.split(',')
a = [int(x) for x in list_buildnums]
list_emails = emails.split(':')
pair = dict(zip(a, list_emails))
return collections.OrderedDict(sorted(pair.items()))
def select_email_group(build_nums, view_email_groups, failed_builds): # email group will be selected for if number of failed builds >= key
#view_email_groups = fix_builds_email_group(build_nums, view_email_groups)
email_dict = parse_buildnums_emails(build_nums, view_email_groups)
email_group = None
#email_group = email_dict.items()[0][1]
for key, value in email_dict.iteritems():
if failed_builds >= key:
email_group = value
return email_group
def fix_view_email_groups(view_urls, emails):
views = view_urls.split(',')
emails_group = emails.split('|')
for num in range(len(views)):
if num < len(emails_group): # use last provided emails group
if emails_group[num] == '': emails_group[num] = emails_group[num - 1]
else:
emails_group.append(emails_group[num - 1])
return zip(views, emails_group)
def fix_builds_email_group(build_nums, build_email_groups):
build_group = build_nums.split(',')
email_group = build_email_groups.split(':')
for num in range(len(build_group)):
if num < len(email_group): # use last provided emails group
if email_group[num] == '': email_group[num] = email_group[num - 1]
else:
email_group.append(email_group[num - 1])
return ':'.join(email_group)
def select_view_email_groups(view_url, view_urls, emails): # gives view corresponding email groups
pair_list = fix_view_email_groups(view_urls, emails)
view_email_groups = ''
for x in pair_list:
if x[0] == view_url:
view_email_groups = x[1]
return view_email_groups
def get_failed_builds(views_list, build_duration):
pass
def verify_email_address(receivers):
error_message = 'One or more email address is invalid'
valid_emails = []
invalid_emails = []
for email_address in (''.join(receivers.split())).split(','): # Taken care off white spaces in the specified emails list
match = re.match('^[_a-z0-9-\.]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email_address)
if match == None:
invalid_emails += [email_address]
else:
valid_emails += [email_address]
if len(invalid_emails) > 0:
print(error_message)
raise ValueError(error_message)
return valid_emails
def send_email(email_from, email_to, email_subject, email_message, email_cc='', email_bcc=''):
sendmail_location = runcmd('which sendmail').strip() # strip() used to trim line feed
p = os.popen('%s -t' % sendmail_location, 'w')
p.write('From: %s\n' % email_from)
p.write('To: %s\n' % email_to)
if email_cc is not '':
p.write('Cc: %s\n' % email_cc)
if email_bcc is not '':
p.write('Bcc: %s\n' % email_bcc)
p.write('Subject: %s\n' % email_subject)
p.write('\n') # blank line separating headers from body
p.write(email_message)
status = p.close()
if status is None: status = 'OK'
print "Sendmail exit status: ", str(status)
return status
def send_verified_email():
global email_from, email_to, email_cc, email_bcc, email_msg, subject
status = None
# send email
valid_emails_to = ','.join(verify_email_address(email_to)) # sendmail does not need list
# Validate CC and BCC addresses
if email_cc is not '':
valid_emails_cc = ','.join(verify_email_address(email_cc))
else:
valid_emails_cc = ''
if email_bcc is not '':
valid_emails_bcc = ','.join(verify_email_address(email_bcc))
else:
valid_emails_bcc = ''
if len(valid_emails_to) > 0:
send_email(email_from, valid_emails_to, subject, email_msg, email_cc=valid_emails_cc, email_bcc=valid_emails_bcc)
else:
print 'No valid email specified'
status = 'Error'
def complete_email_send():
print '\n' # print line feed for cleaner nessage
status = None
status = get_failed_builds(views_list, build_duration)
if status is None:
status = send_verified_email()
return status
def main():
global build_nums, view_urls, emails, subject, email_msg
jenkins = Jenkins('http://jenkins.video54.local/')
failed_job_data = jenkins.get_consecutive_failed_builds_in_views(view_urls)
email_data = update_email_data(failed_job_data, build_nums, view_urls, emails)
print "Failed job and Email data: " + str(email_data)
for x in email_data:
for y in x.values()[0]:
if y[2] is not None:
email_to = y[2]
email_msg = str(y[1]) + ' continuous builds failed for the job: ' + y[0]
send_verified_email()
else:
print 'No valid email to notify: ' + email_msg
if __name__ == '__main__':
main()
No comments:
Post a Comment