trigger-lava-jobs: Add LAVA RPC trigger pipeline script

trigger-lava-jobs accepts the YAML pipeline lava-job config file
generated by run-jinja-parser scripts. This triggers a new job at
LAVA end thru RPC and parses the authentication token and user
credentials to launch/start the hardware automation on LAVA
Dispatcher.Script will exit on error when lava-job return a state
either incomplete or canceling stage.

trigger-lava-jobs uses lava_scheduler.py python module where the
LAVA classes and library constructed from XML-RPC API which are
define and supported by Linaro, LAVA.

Signed-off-by: Aaron Chan <aaron.chun.yew.chan@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Aaron Chan 2018-08-29 21:25:52 +08:00 committed by Richard Purdie
parent fc933b86e5
commit bbf23b22b4
2 changed files with 288 additions and 0 deletions

70
lava/lava_scheduler.py Normal file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env python3
'''
author__ = "Aaron Chan"
__copyright__ = "Copyright 2018, Intel Corp"
__credits__" = ["Aaron Chan"]
__license__" = "GPL"
__version__" = "1.0"
__maintainer__ = "Aaron Chan"
__email__ = "aaron.chun.yew.chan@intel.com"
'''
import xmlrpc
class scheduler():
def __init__(self, server, user, token, url):
self.server = server
self.user = user
self.token = token
self.url = url
@classmethod
# Description: Submit the given job data which is in LAVA
# job JSON or YAML format as a new job to
# LAVA scheduler.
# Return: dict <type>
def lava_jobs_submit(self, server, data):
return server.scheduler.jobs.submit(data)
@classmethod
# Description: Cancel the given job referred by its id
# Return: Boolean <type>
def lava_jobs_cancel(self, server, jobid):
state = server.scheduler.jobs.cancel(jobid)
if type(state) is bool: return state
@classmethod
def lava_jobs_resubmit(self, server, jobid):
return server.scheduler.jobs.resubmit(jobid)
@classmethod
# Description: Return the logs for the given job
# Args: jobid <str>, line <int> - Show only after the given line
# Return: tuple <type>
def lava_jobs_logs(self, server, jobid, line):
return server.scheduler.jobs.logs(jobid, line)
@classmethod
# Description: Show job details
# Return: Dict <type>
def lava_jobs_show(self, server, jobid):
return server.scheduler.jobs.show(jobid)
@classmethod
# Description: Return the job definition
# Return: Instance <type>
def lava_jobs_define(self, server, jobid):
return server.scheduler.jobs.definition(jobid)
@classmethod
def lava_jobs_status(self, server, jobid):
return server.scheduler.job_status(jobid)
@classmethod
def lava_jobs_output(self, server, jobid, offset):
return server.scheduler.job_output(jobid, offset)
@classmethod
def lava_jobs_details(self, server, jobid):
return server.scheduler.job_details(jobid)

218
lava/trigger-lava-jobs Executable file
View File

@ -0,0 +1,218 @@
#!/usr/bin/env python3
#
# =====================================================================================
# XML-RPC API reference taken from
# -- https://validation.linaro.org/static/docs/v2/data-export.html#xml-rpc
# Developed By : Chan, Aaron <aaron.chun.yew.chan@intel.com>
# Organization : Yocto Project Open Source Technology Center (Intel)
# Date : 27-Aug-2018 (Initial release)
# =====================================================================================
#
# Triggers a job execution define by YAML template on LAVA server end from autobuilder.
# This script will monitor the lava-job status until the hardware boots up successfully
# and returns the IPv4 addr pre-configure over network boot (PXE) on the board.
# Once the IPv4 addr has been recovered, script will update the auto.conf with
# TEST_TARGET_IP, TEST_SERVER_IP to establish a client-host connection and prepare to
# execute automated harware test case(s) on hardware on the next step.
#
# Options:
#
# $1 - Supply lava-job template in a YAML format (e.g. <filename>.yaml)
# $2 - Supply autobuilder buildername (e.g. nightly-x86-64-bsp, nightly-arm64-bsp)
# $3 - By default set to "None", else parse in the buildnumber to create the NFS path
# $4 - Supply device/board name (same as LAVA device type)
#
import xmlrpc.client
import sys
import os
import time
import re
import json
import netifaces
import time
from shutil import copyfile
from lava_scheduler import *
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),"scripts"))
import utils
# Enable this section on manual run
# os.environ['ABHELPER_JSON'] = "config.json /home/pokybuild/yocto-autobuilder-helper/config-intelqa-x86_64-lava.json"
def lava_jobsStatus(server, jobid, timeout, bootUp=False, ipaddr=None, iparch=None, timeInt=5):
jobStatus = scheduler.lava_jobs_status(server, jobid)['job_status']
while jobStatus == "Submitted" or jobStatus == "Running" or bootUp == False:
time.sleep(timeInt)
timeout += timeInt
for logs in scheduler.lava_jobs_logs(server, jobid, 0):
ipaddr = re.search("Station\s*IP\s*address\s*is\s*(.*)\"", str(logs), re.I)
iparch = re.search("Detected\s*architecture\s*(.*)\.", str(logs), re.I)
dbmsg = re.search("Board\s*boot\s*up\s*successfully", str(logs), re.I)
if ipaddr: ipaddr = ipaddr.group(1)
if iparch: iparch = iparch.group(1)
if dbmsg:
bootUp = True
break
if bootUp:
print("INFO : Board booted up successfully. LAVA is ready to handover to Buildbot-CI [%s]" % str(bootUp))
break
if timeout > 3000:
scheduler.lava_jobs_cancel(server, jobid)
print("WARNING: Board exceeded bootup time threshold %d, Job will be %s and powered down" % (
timeout, scheduler.lava_jobs_status(server, jobid)['job_status']))
break
jobStatus = scheduler.lava_jobs_status(server, jobid)['job_status']
print("INFO : Job has current status [%s]" % jobStatus)
# Job Status #
if jobStatus == 'Incomplete':
print("ABORTED: %s test!. Rerun again if required" % jobStatus)
elif jobStatus == 'Cancel':
print("ABORTED: Job has been [%s]led by user" % jobStatus)
elif jobStatus == 'Complete':
print("SUCCESS: Current JobID [%s] has been successfully [%s] and passed" % (jobid, jobStatus))
elif jobStatus == 'Running':
print("INFO : %s in %d seconds ..." % (jobStatus, timeout))
elif jobStatus == 'Submitted':
print("INFO : Lava job has been successfully %s and running in progress..." % jobStatus)
else:
print("ERROR : Job is either %s or in an unknown state. Report to LAVA Mailing Lists" % jobStatus)
return ipaddr, iparch, jobStatus
def lava_jobsSubmit(server, hostname, cfgfile, debug=False):
#if os.path.isfile(cfgfile):
with open(cfgfile, 'r') as yaml:
yamlCfg = yaml.read()
yaml.close()
if debug: print("INFO : Current YAML Job Definition\n%s" % yamlCfg)
jobid = scheduler.lava_jobs_submit(server, yamlCfg)
if jobid is not None:
print("SUCCESS: Job submitted to http://%s/scheduler/job/%s#bottom to LAVA-CI server" % (hostname, jobid))
(boardIp, boardArch, boardStat) = lava_jobsStatus(server, jobid, 0)
#else:
# print("ERROR: YAML Config not found on the LAVA-Server")
return boardIp, boardArch, boardStat, jobid
def lava_jobsDetail(server, jobid, elements, items=[]):
jobInfo = scheduler.lava_jobs_details(server, jobid)
if type(elements) is str:
return jobInfo[elements]
elif type(elements) is list:
for item in elements:
items.append(jobInfo[item])
return items
else:
return jobInfo
def lava_listmethods(server):
print(server.system.listMethods())
def lava_publisher(username, token, server):
return xmlrpc.client.ServerProxy("http://%s:%s@%s/RPC2/" % (username, token, server))
def check_isfile(filename):
if not os.path.isfile(filename):
print("ERROR: Failed to locate filename %s" % filename)
sys.exit(1)
return True
def check_until(filename, isfound=None):
if os.path.isfile(filename):
isfound=False
else:
isfound=True
return isfound
# Starts here
def main():
"""
For Yocto Project Reference scripts, this tool was developed to trigger a Job in LAVA
server based on URL, TCP/IP port defined on config-intelqa-x86_64-lava.json.
Requirement to run this script to ensure <filename>.yaml, build/build/conf/auto.conf
is present.
"""
yamlconf = sys.argv[1]
autoconf = sys.argv[2]
try : boardinfo = sys.argv[3]
except: boardinfo = None
ourconfig = utils.loadconfig()
lavadefs = ourconfig["lava-defaults"]
username = lavadefs['username']
token = lavadefs['token']
server = lavadefs['server']
interface = lavadefs['interface']
timemin = timesec = 0
cwd=os.path.join(os.getcwd(), "board_info.json")
# Instantiate LAVA server connection with RPC
lavaserver = lava_publisher(username, token, server)
isfile = check_isfile(yamlconf)
if isfile:
target_ip, boardArch, boardStat, jobid = lava_jobsSubmit(lavaserver, server, yamlconf)
if boardStat == 'Canceling' and boardStat == 'Incomplete':
print("Board/hardware unresponsive or software image loaded is incompatiable. Ending session.")
sys.exit(1)
if boardinfo is not None:
boardinfo = os.path.join(boardinfo, str(jobid), 'board_info.json')
print("Search if board info exists [%s]" % boardinfo)
while(check_until(boardinfo)):
time.sleep(1)
timesec += 1
if timesec > 2500:
print("Board discovery exceeds timeout %s. Ending session." % str(timesec))
sys.exit(1)
print("Board discovery in %s secs ..." % str(timesec))
(timemin, timesec)=divmod(timesec, 60)
print("Board has been discovered in %s mins, %s secs" % (timemin, timesec))
if os.path.isfile(cwd):
os.system(" ls -al %s" % boardinfo)
print("Board info existed, file will be deleted and recopied over to %s" % cwd)
os.remove(cwd)
else:
print("Board info to be copied over to %s" % cwd)
copyfile(boardinfo, cwd)
os.environ['ABHELPER_JSON'] += (" " + boardinfo)
ourconfig = utils.loadconfig()
target_ip = ourconfig['network']['ipaddr']
lavaurl = "http://" + server + str(lava_jobsDetail(lavaserver, jobid, 'absolute_url'))
jobinfo = lava_jobsDetail(lavaserver, jobid, ['actual_device_id', 'start_time', 'end_time'])
server_ip = netifaces.ifaddresses(interface)[2][0]['addr']
# Update auto.conf with board IPv4 and server IPv4 addressing
isauto = check_isfile(autoconf)
if isauto:
with open(autoconf, "a") as autof:
autof.writelines("TEST_SERVER_IP = \"%s\"\n" % server_ip)
autof.writelines("TEST_TARGET_IP = \"%s\"\n" % target_ip)
autof.close()
print("="*50)
print("""
SUMMARY:
LAVA-url : %s
LAVA-job : %s
LAVA-status : %s
Device-IP : %s
Device-ARCH : %s
""" % (lavaurl, jobinfo, boardStat, target_ip, boardArch))
print("="*50)
if __name__ == '__main__':
main()