用Python启动并监控Jenkins任务

任务需求

  • 上一步:Python解析程序参数,生成一组Jenkins Job的参数。这些Job覆盖不同操作系统并占用各自的Lockable Resource。
  • 本任务的功能:使用Python代码启动一组Jenkins Job,并监控这些Job的完成情况。
  • 下一步:拉取这组Job的运行结果,进行统计分析和汇总。记录有问题的Job,以备重跑。

其中,Lockable Resource这个技术点已经研究过了(见以前写的3篇文档):

  • Jenkins的Lockable Resource插件 - 1:基本使用
  • Jenkins的Lockable Resource插件 - 2:解决最多只能创建一个等待任务的问题
  • http://t.csdnimg.cn/jt22wJenkins的Lockable Resource插件 - 3:在Freestyle Job中实现根据参数动态绑定资源

本文主要描述如何使用Python代码启动一组Jenkins Job,并监控这些Job的运行状态。

流程步骤

  1. 首先,通过Python代码连接到Jenkins服务器。
  2. 选择要启动的Job,触发新的构建过程。
  3. 监控构建过程的完成情况:获取Job的构建状态和结果。
  4. 通过轮询获取构建状态,直到构建完成。
  5. 获取构建日志和其他相关信息(可以在步骤4的轮询中作为事件)
  6. 对于有问题的Job,可以记录下来以备重跑或进行进一步分析。

Jenkins的Job、Build、Queue

Job类

Jenkins中的Job对象(文档:https://javadoc.jenkins.io/hudson/model/Job.html),是在Jenkins中定义的任务,可以由Jenkins服务器执行和管理。Job的配置信息都保存在一个XML文件中,定义了一系列的构建步骤和配置选项。

Job的REST API是:http://:8080/job//api

Build类

Jenkins中的Build对象(文档:https://javadoc.jenkins.io/hudson/model/Build.html),是Job的一个具体实例,代表了一次构建的过程和结果。每次触发构建,都会生成一个新的Build实例。可以通过Build获取构建的状态、结果和日志信息。

Queue类

Jenkins中的Queue对象(文档:https://javadoc.jenkins-ci.org/hudson/model/Queue.html),任务队列,用于管理待执行的Job。当触发构建时,Job会被添加到Queue中等待执行,生成QueueItem对象。

Queue的REST API是:http://:8080/queue/api

QueueItem接口

在Jenkins中,QueueItem是一个接口,用于表示Jenkins队列中的对象。当启动一个Build时,Jenkins会首先在队列中创建一个QueueItem,其中包含了Job的配置信息、启动Parameters以及发起Job的Actions。当QueueItem被分配给一个executor执行时,它会与一个Build对象关联。可以通过执行cancel操作来取消QueueItem,一旦QueueItem被取消或者关联的Build 开始执行后几分钟后它会被删除。据说StackOverflow上某个非官方的帖子说是5分钟:“once a build job has started, the queue item has 5 minutes before it is deleted”。

Queue的REST API是:http://:8080/queue/item//api

build with params
cancel a QueueItem
ready for build
timeout
created
cancelled
building
out of queue
deleted

选择Python下的Jenkins库

常用于Jenkins管理的Python库包括:

  • jenkinsapi:提供高级接口,将信息封装为对象,可用于启动、停止和管理Jenkins任务。
  • python-jenkins:实际上是一个Restful API的封装器,以简单直接的方式管理Jenkins任务。返回的JSON数据未封装为对象,而是直接转换为字典供用户使用。

在封装对象时,jenkinsapi对Restful API返回的数据进行了筛选,舍弃了一些不常用的信息。然而,对于QueueItem的封装无法体现QueueItem ID和对应的Build ID之间的对应关系。python-jenkins库的返回值就是API原始的返回值,包含了所有的信息,因此选择python-jenkins库。

实现代码

JenkinsJob类的实现:

import jenkins

class JenkinsJob:
	def __init__(self, job_url: str):
		self.queue_id = None
		self.queue_item = None
		self.executable = None
		self.params = None
		self.status = None
		# TODO:省略
		# self.server = “获取根据地址缓存的jenkins.Jenkins实例”
		# ...
		
	def build_job(self, params: dict):
		self.queue_id = self.server.build_job(self.job_name, params)
		self.params = params
		logging.info(f'{self} entered the queue')

	def monitor_status(self) -> bool:
		# The job has not been started: ignore to monitor
		if self.queue_id is None:
			self.status = 'NOT_BUILD'
			return True
		# The job is finished with status: ignore to monitor
		if self.status:
			return True
		# The job is in queue: check if it is cancelled
		if not self.executable:
			self.queue_item = self.server.get_queue_item(self.queue_id)
			if self.queue_item.get('cancelled'):
				logging.info(f'{self} has been cancelled.')
				self.status = 'QUEUE_CANCELLED'
				return True
		# The job is in queue: check if it is started
		if self.executable is None and 'executable' in self.queue_item:
			self.executable = self.queue_item['executable']
			logging.info(f'{self} left queue, building started...')
		# The job is started: check if it is finished with status
		if self.executable:
			self.build = self.server.get_build_info(self.job_name, self.executable['number'])
		if status := self.build.get('result'):
			logging.info(f'{self} set build status: {status}')
			self.status = status
			return True
		return False

		@classmethod
		def wait_for_all(cls, jobs: List['JenkinsJob'], delay: int = 10):
			while any(job.status is None for job in jobs):
		  		time.sleep(delay)
		   		for job in jobs:
		    		job.monitor_status()
			logger.debug('all jobs done')

调用代码:

def start_jenkins_job(job_url, test_params, timestamp):
	jenkins_job = JenkinsJob(job_url)
	params = {
		'OS_NAME': test_params.os.upper(),
		'MIXING': test_params.params['mixing'],
		'TIMESTAMP': timestamp,
		'PARAMS': str(test_params.params),
	}
	jenkins_job.build_job(params)
	return jenkins_job

def run_all_jenkins_suite():
	running_items = []
	suite_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
	for params in generate_parameters():
		item = start_jenkins_job(job_urls[params.item], params, suite_timestamp)
		if item is not None:
			running_items.append(item)
	
	JenkinsJob.wait_for_all(running_items)
	return running_items

run_all_jenkins_suite()函数调用start_jenkins_job()函数,启动一个个的构建并将其添加到running_items中。然后调用JenkinsJob.wait_for_all()函数,等待所有的构建执行完毕。

JenkinsJob.wait_for_all()函数每隔10秒(使用delay参数)检查所有Job的执行状态(通过monitor_status()函数)。如果所有的Job的执行状态都不为None,则退出等待。

monitor_status()函数的执行逻辑如下:

  1. 首先检查queue_id:如果Job没有queue_id,则表示尚未进行构建,将status设置为NOT_BUILD,然后停止状态检查。
  2. 检查status字段,如果不为None,表示构建已经完成,停止状态检查。
  3. 检查executable字段,这个字段只有在QueueItem获取到executor后才会被设置。
  4. 当发现executable字段时,保存其中构建的urlnumber信息。
  5. 有了构建的urlnumber信息后,可以调用RESTful API检查构建的状态。
  6. 如果构建的字段中有result字段,表示构建已结束,将其值用于更新status字段。

参考文档:

  1. https://stackoverflow.com/questions/45472604/get-jenkins-job-build-id-from-queue-id
  2. https://python-jenkins.readthedocs.io/
  3. https://jenkinsapi.readthedocs.io/en/latest/index.html

你可能感兴趣的:(软件开发,python,jenkins,restful)