需求來源
測試過程中,業務的同事經常會需要臨時執行一些批量程序去處理聯機準備的業務測試數據。批量程序一般部署在服務器或數據庫上,由應用維護人員登錄服務器執行,這樣就存在溝通與執行上的成本。我們的目標是通過簡單易行的方法,實現業務同事自主靈活調度批量程序的需求。
這個需求早在幾年前就有了,我們當時使用vbs腳本這種最簡單的方法,寫了一個自動登錄服務器執行shell的自動化腳本。這種方法用了好幾年,還算穩定,也基本能滿足業務需求。直到最近,我們發現批量作業調度經常出現執行順序的錯誤,最後定位問題原因是批量調度框架邏輯更新,但是業務使用的vbs腳本沒有及時更新,這就暴露了vbs調度的最大的弱點:每個需求都是一個vbs腳本,難於集中管理和更新。
上述問題是自動化運維中一個非常簡單的需求,解決方案基本都是登陸服務器執行腳本這種方法,只不過用到的工具不同而已。我們這次的設想是能夠將批量執行封裝成一個服務,業務不需要直接去執行批量程序,而是去觸發批量執行的服務。這樣做的好處主要有三點,一、業務使用的客戶端只是一個服務調用的程序,不涉及批量執行邏輯,無需隨版本更新,不同業務需求使用相同的客戶端,省去了客戶端管理和更新的工作;二、技術人員可以在後臺對批量服務執行的目標環境、執行邏輯順序進行定製,並且對於業務是透明的;三、跳過了登錄服務器和切換用戶的步驟,獲取程序回顯更加簡單穩定。基於上述需求,我們決定採用jenkins作爲遠程調度服務的框架,使用python調用jenkins的api接口作爲業務使用的客戶端。
爲什麼使用jenkins
使用jenkins出於以下考慮:
1、jenkins安裝部署簡單,windows平臺基本是一鍵安裝。
2、有豐富的插件支持,可以滿足用戶管理、服務管理、作業控制及歷史執行統計等功能
3、jenkins的分佈式設計簡單穩定,只需要在從節點加載jar包就能夠實現主從節點的管理
4、jenkins本身就是以web服務作爲載體,提供api的接口調用方式,這樣可以在任意客戶端環境調用jenkins的服務
具體實現
這裏略過jenkins的安裝和配置等細節,大家有興趣可以自行百度。
處理流程如下:
客戶端 --> 數據庫(保存用戶與服務關係數據、服務調用邏輯元數據信息)--> 調用jenkins api調用對應服務 --> jenkins去服務所在節點執行shell腳本
1、服務端:
我們選擇了一臺windows服務器作爲jenkins的主節點,在從節點加載slave.jar包,實現分佈式框架。在任務定義是,選擇只在選擇的從節點執行任務,任務執行輸入批量調度程序的shell(如果涉及多個批量,可以通過一個shell進行批量編排和輸出的控制,簡化在jenkins中任務定義過程)
2、數據庫:
數據庫記錄了一些元數據信息,主要設計了兩張表:用戶與服務對照關係表,定義了業務同事姓名與jenkins上定義的服務名。服務元數據信息表,定義了服務的輸入參數、簡單的邏輯關係信息等,客戶端根據這些信息控制用戶的輸入。
3、jenkins api
根據客戶端輸入,使用python加載jenkins的api包,通過buidjob和get output兩個方法去調用jenkins的作業並獲得結果數據。具體代碼如下:
import jenkins
import cx_Oracle
import time
import sys
import urllib2
reload(sys)
sys.setdefaultencoding('utf-8')
#修改了_init_.py文件的438行,解決中文回顯得問題
if __name__=='__main__':
conn = cx_Oracle.connect('test/[email protected]/db')
username = raw_input("請輸入您的姓名:".encode("GBK"))
#根據傳入的用戶名,獲取用戶下注冊的服務信息
str0 = "SELECT M.SERVICE_NAME,M.JENKINS_NAME,N.PARAM1 FROM JENKINS_SERVICE N , JENKINS_SERVICE_PARAM M" \
" WHERE M.SERVICE_NAME=N.SERVICE_NAME AND N.USER_NAME = '"+username+"' GROUP BY M.SERVICE_NAME,M.JENKINS_NAME,N.PARAM1 ORDER BY N.PARAM1"
cursor0 = conn.cursor()
cursor0.execute(str0)
res0 = cursor0.fetchall()
rowcount0 = len(res0)
x = 0
while x < rowcount0:
service_name = str(res0[x][0])
param1 = str(res0[x][2])
print (param1+" "+service_name+"\n")
x=x+1
#獲取需要調用的服務,並根據服務進一步獲取輸入參數
tmp1=raw_input("請選擇需要執行的服務:".encode("GBK"))
job=str(res0[int(tmp1)-1][1])
str1="SELECT PARAM2,PARAM3 FROM JENKINS_SERVICE_PARAM WHERE PARAM1='INPUT' AND JENKINS_NAME='"+job+"'"
cursor0.execute(str1)
res1 = cursor0.fetchall()
rowcount1 = len(res1)
y=0
#構建jenkins api的傳入參數字典
param_dict={}
while y<rowcount1:
input_param=res1[y][0]
input_desc=str(res1[y][1])
tmp_input=""
tmp_input=raw_input("請輸入 ".encode("GBK")+input_desc+" 的傳入參數值:".encode("GBK"))
param_dict[input_param]=tmp_input
y=y+1
#連接jenkins api
server_ip='http://2.2.2.2:8080/login?from=%2F'
user_id='admin'
api_token='2544a1af98ba9677ae7f041d0959df3d'
server=jenkins.Jenkins(server_ip,username=user_id,password=api_token)
#檢查上一次服務調用是否已經完成
build_no = server.get_job_info(job)["lastBuild"]["number"]
while True:
status = server.get_build_info(job, build_no)["building"]
if status is True:
print "案例執行中,請稍等".encode("GBK")
time.sleep(5)
else:
break
print "上一次執行結束,可以開始執行".encode("GBK")
#調用服務
server.build_job(job,param_dict)
time.sleep(5)
build_no = build_no + 1
#獲取調用結果的Output信息
while True:
try:
status = server.get_build_info(job, build_no)["building"]
if status is True:
print "案例執行中,請稍等".encode("GBK")
time.sleep(5)
else:
break
except Exception,e:
print "任務正在初始化".encode("GBK")
time.sleep(5)
continue
print server.get_build_console_output(job, build_no).decode("GBK").encode("GBK")
print "執行完畢".encode("GBK")