運維流程系統

一 圖論概述

1 圖的分類

1 無向圖

運維流程系統

圖 graph由頂點和邊組成,頂點的又窮非空集合爲V,邊的集合爲E,記做G(V,E)
頂點vertex,數據元素的集合,頂點的集合,又窮非空,
邊edge,數據元素關係的集合,頂點關係的集合,可以爲空,邊分爲有向和無向兩種

無向邊記做(A,B),或者(B,A),使用小括號

運維流程系統

無向圖,記做undirected Graph 無向邊的邊構成的圖,G=(V,E),V={A,B,C,D},E={(A,B),(A,C),(B,C),(B,D),(C,D)}

2 有向圖

有向邊記做<A,B>,即從頂點A指向頂點B,<B,A>表示頂點B指向頂點A,使用尖括號,有向邊也叫做弧,邊表示爲弧尾指向弧頭。

運維流程系統

有向圖directed graph

有方向的邊組成的圖

G=(V,E) V={A,B,C,D} E={<A,B>,<A,C>,<C,B>,<B,D>}

3 圖的其他概念

1 稀疏圖 sparse graph

圖中邊很少,最稀疏的情況是隻有頂點沒有邊,這就是數據結構SET

2 稠密圖 dense Graph

圖中邊很多,最稠密的情況,任意2個頂點之間都有關係

3 完全圖 complete graph

包括了所有可能的邊,達到了稠密圖最稠密的情況,任意兩個頂點之間都有邊相連


有向的邊的完全圖,叫做有向完全圖,邊數爲n*(n-1)
無向的邊的完全圖,叫做無向完全圖,邊數爲n(n-1)/2

4 子圖

如果圖G(V,E)滿足V` <=V,且E` <=E,則G`是G的子圖

換句話說,就是一個圖的部分頂點和部分邊組成的圖爲子圖,有向圖需要注意邊的方向

前面的圖包含後面的圖,後面的圖可以稱爲前面的圖的子圖

一個圖的部分頂點可能是所有頂點,其部分邊也可能是所有邊

運維流程系統

有向圖,邊是由方向的,如果找不到對應的方向,則不是其子圖,或者方向相反,則不是其子圖

5 邊的權weight和網

給邊賦予的值稱爲權,權可以表示距離,所需的時間,耗費的時間等

網network 圖中有邊有權,圖稱爲網

運維流程系統

6 自環 loop

若一條邊的兩個頂點爲同一個頂點,則此邊稱爲自環

邊中存在這樣一個邊(u,v) 或者<u,v> ,u=v

運維流程系統

7 簡單圖

無重複的邊或者頂點到自身的邊(自環)的圖

下面的兩個圖都不是簡單圖

運維流程系統

4 鄰接關聯

1 鄰接

圖的邊集合爲E

無向圖,若 (u,v) 屬於 E,則稱u和v相互鄰接,互爲鄰接頂點

有向圖,若<u,v>屬於E,則邊u鄰接到v,或者v鄰接於u

簡單說,就是2點之間有條邊,2點鄰接

2 關聯(依附)

若 (u,v)屬於E或者 <u,v>屬於E,則稱邊依附於頂點u,v或者頂點u,v 與邊相關聯。

5 路徑path

1 基礎路徑

圖G(V,E),其任意一個頂點序列,相鄰2個頂點都能找到邊或者弧依次鏈接,就說明有路徑存在,有向圖的弧注意方向,所有的頂點都屬於V,所有的邊都屬於E。

頂點之間形成了路徑,此處稱爲弧。

路徑長度

等於頂點數減一,等於此路徑上的邊數

2 簡單路徑

路徑上的頂點不重複出現,這兩的路徑就是簡單路徑

運維流程系統

無向圖中A到D的路徑有A-B-D,A-C-D,A-C-B-D 等

運維流程系統

有向圖中A到D的路徑有A-B-D,A-C-B-D等

3 迴路

運維流程系統

路徑的起點和終點相同,稱爲迴路
A-B-C-A-B-A

4 簡單迴路

除了路徑的起點和終點相同外,其他頂點都不相同
A-B-C-A

6 連通

1 連通

運維流程系統

無向圖中,頂點存在路徑,則兩個頂點是連通的

注意: 連通是指A-D之間有路徑,而不是說這兩個個頂點要鄰接

2 連通圖

無向圖中,如果圖中任意兩個頂點之間都連通,就是連通圖

3 連通分量

無向圖中,指的是極大連通子圖

無向圖未必是連通圖,但是它可以包含連通子圖

4 強連通

有向圖中,頂點鍵存在2條相關的路徑,及從A到B有路徑,也存在從B到A的路徑,兩個頂點是強連通的

運維流程系統

上述中第三個圖中有強連接,如B-D和D-B,A-C-B和B-A

5 強聯通圖

有向圖中,如果圖中任意2個頂點都是強聯通的圖

6 強聯通分量

有向圖中,指的是"極大連通子圖"

有向圖未必是強聯通圖,但是可以包含強聯通分量

7 度 degree

一個頂點的度指的是與該頂點相關聯的邊的條數,頂點v的度記做TD(v),無向圖頂點的邊數叫做度

有向圖的頂點有入度和出度,頂點的度數爲入度和出度之和 TD(v)=ID(v)+OD(v)

入度(In-degree): 一個頂點的入度是指與其關聯的各個邊中,以其爲終點的邊數

出度(Out-degree): 出度則是相對的概念,指以該頂點爲起點的邊數。

8 生成樹

1 簡單描述

它是一個極小連通子圖,它要包含圖的所有n個節點,但只只要有構成一顆數的n-1條邊


如果一個圖有n個頂點,且少於n-1條邊,則一定是非連通圖,因爲至少要有n-1條邊才行

如果一個圖有n個頂點,且多於n-1條邊,則一定有環存在,一定有2個頂點之間存在第二條路徑,但不一定是連通圖

如果一個圖由n個頂點,且有n-1條邊,但不一定是生成樹,要整好等於n-1條邊,且這些邊足以構成一顆數

2 有向樹

一個有向樹恰好有一個入度爲0的頂點,其他頂點的入度都爲1,注意,這裏不關心出度。

3 生成樹森林

若干有向樹構成有向樹森林

有向無環樹不一定能轉化爲數,但數一定是有向無環圖。

二 鄰接矩陣

1 概述

圖是由vertex 和edge組成,所以可以分爲2個數組表示
頂點使用一維數組表示,如v0,v1,v3
邊使用二維數組表示,由頂點構成二維數組

2 無向鄰接矩陣

運維流程系統

下圖中,若存在邊。則爲1,否則爲0

A B C D
A 0 1 1 0 2
B 1 0 1 1 3
C 1 1 0 1 3
D 0 1 1 0 2
2 3 3 2

此處的相關的最後行和最後一列表示度數,如果上述的對角線上的數字爲1,則表示有了自環
如果除了對角線全是1,說明沒有自環,且是一個無向完全圖
上面的矩陣,稱爲圖的鄰接矩陣
頂點的度數,等於對應行或者列求和
鄰接點,矩陣中爲1的值對應的行與列的頂點就是鄰接點。
無向圖的鄰接矩陣是一個對稱矩陣

3 有向鄰接矩陣

運維流程系統

有向鄰接矩陣因爲是有向的,只有方向正確纔是1,否則都是0

A B C D
A 1 1 1 0 2
B 0 0 0 1 1
C 0 1 0 0
D 0 0 0 0
0 2

有向圖的鄰接矩陣不一定對稱,對稱的說明兩個頂點之間存在環

三 運維流程系統設計

1 概述

某一個基點上有一批任務需要執行,如何執行

一個接一個排隊開始執行,但是這樣的執行可能很沒有效率,而且沒必要,如獲取兩個毫不相關的信息,誰先執行都可以,同時執行也沒問題,這樣的任務便可以並行處理,而不只是串行化進行處理。

運維流程系統

任務的執行過程中無非是串行和並行的問題,但串行的效率可能太低。

任務處理和任務流是沒有關係的,任務的編排和任務分配不同

2 如何設計一個有向無環圖(DAG)

1 有向無環圖 directed acyclic graph (DAG)

無環路的有向圖
假設有下面幾種情況

運維流程系統

兩個任務,任務本身就是頂點,任務先後執行

運維流程系統

三個任務。任務1執行完成後,才能分別執行任務2和任務3

運維流程系統

四個任務,執行任務1完成後,才能分別執行任務2和任務3,最後執行任務4.執行任務4 的時機應該是任務2 and 任務3

可以看到任務的執行過程就是流程的設定(pipeline),所以要設計一個流程系統來跑任務

2 起點和終點選擇

1 入度爲0的頂點就是起始的點
DAG 可以有多個起始點
我們的系統約定有且只有一個起始點

終點的判斷

出度爲0的頂點,pipeline執行結束
pipeline可能有多個終點

3 環路預防

pipeline設計的過程中應當注意避免出現環路,因爲出現環路就不是DAG了

自環檢查,弧頭指向頂點自身

多頂點構成環路的檢測


環路檢測必須實現,否則當定義好的流程執行起來,有可能進入環路後,永遠執行不能終止。

3 schema 元數據表設計

使用數據庫表的存儲方式定義DAG
問題是如何使用數據庫的表描述一個DAG
DAG也是圖,是圖就有頂點,邊,所以可以設計2個表,頂點表,邊表,邊表用於描述一個圖,爲了存儲多個圖,定義一個圖的表

1 圖的定義(graph)

字段名 類型 說明
id int 主鍵
name varchar 非空,唯一,圖的名稱
desc varchar 可爲空,描述

2 頂點表定義 vertex

字段名 類型 說明
id int 主鍵
name varchar 非空,頂點的名稱
g_id int 外鍵,描述其屬於哪個圖

3 邊表 edge

字段名 類型 說明
id int 主鍵
tail int 外鍵,弧尾頂點,頂點在vertex 表中必須存在
head int 外鍵,弧頭頂點,定在在vertex表中必須存在
g_id int 外鍵,描述邊屬於哪一個圖

通過弧尾,弧頭頂點來描述有向邊

4 具體關係如下

運維流程系統

其中,graph表中主要是流水線的名稱,id和描述,頂點表(vertex)中主要包含頂點名稱,頂點id及頂點所屬的流水線,因爲頂點必須和流水線之間建立關係,script 表示頂點要執行的腳本,及流水線要執行的腳本。

edge表中主要包括弧尾和弧頭以及此頂點屬於哪個表,用於描述流水線的執行順序

4 script 的設計

1 設計思路

流程定義表中,任務的處理和描述

在任務調度系統中,任務的實現我們使用script腳本實現


方法一

supprocess 執行bash 腳本script

優點:簡單,易行
缺點:要啓動外部進程,bash 腳本表達能力較弱,難調試


方法二
嵌入其他語言的腳本,如lua語言
優點:不啓動子進程,功能強大。
缺點:技術要求高,需要學習其他腳本語言。

2 python 中執行lua腳本

安裝

pip   install lupa 
#!/usr/bin/poython3.6
#conding:utf-8
from  lupa import  LuaRuntime
lua=LuaRuntime()  #對其進行實例化處理
print  (lua.eval('1+10'))  #調用方法,執行基本的函數運算操作

def pythonc(n):   # 定義python方法
    import  socket
    print('socket',n)
    return   socket.gethostname()

# 定義lua 腳本函數,並傳遞兩個值,一個是f,及函數,另一個則是常數
luafunc=lua.eval('''  
        function(f,n)
            return  f,n
        end
    ''')

print (luafunc(pythonc(1),10))  #調用函數並打印

add=lua.eval('''
    function  (x,y)
        return  x+y 
    end
''')

print (add(10,20))

結果如下

運維流程系統

5 執行條件(input)

1 概述

腳本在執行之前,可能需要提供一些參數,才能開始執行腳本
此處需要在頂點表vertex中增加input字段,用於存儲需要傳遞的參數。

2 表字段設計

字段名 類型 說明
id int 主鍵
name varchar 非空,頂點的名稱
g_id int 外鍵,描述頂點屬於哪一個圖
script text 可以爲空,存儲任務腳本
input text 可以爲空,存儲json格式的輸入參數定義

3 input 格式

定義如下,json 格式

{
    "name1" : {
        "type":"",
        "required" : True 
    },
    "name2" :{
        "type" :"",
        "requried": True,
        "default" :1
    }

}

name 就是參數的名稱,後面定義該參數的類型,是否是必選參數等等屬性及默認屬性,其可以定義多個參數

4 作用

進入某個節點的時候,就必須滿足條件,提供足夠的參數
如果提供的參數滿足要求,就進入節點,否則一直等待到參數滿足
如果滿足了,才能去執行script
input 就是一個約束的定義


交互:
input 可以不交互,缺省值爲自動

6 任務執行

1 概述

當流程走到某一個頂點的時候,讀取任務及腳本,執行這個腳本

2 手動執行和自動執行

1 手動執行

流程走到這個頂點等待用戶操作,需要用戶手動干預,
如由用戶選擇下一個執行頂點
如下一個頂點的任務需要一些配置參數,等待用戶輸入後才能進行下一步


2 自動執行
自動填寫input,如使用缺省值,來滿足用戶爲交互式填寫的時候自動補全數據,腳本執行後,自動跳轉到下一個節點,當然這個所謂的自動,程序不會智能的選擇路徑,需要提前指定好,執行完腳本,就可以跳轉到下一個頂點了。

7 任務流轉的設計

1 概述

當流程走到某一個頂點的時候,讀取任務即腳本,或手動執行,或自動執行

2 流轉分類

手動執行,需要人工選擇下一個頂點,可以提供可視化界面供用戶方便選擇,


自動執行,就需要在信息中提供下一個節點的信息,供程序自動完成


那麼,如何區分一個頂點是否自動執行

如果vertex表中的script字段修改爲json.
如果next 不存在,則不能自動執行,需要手動操作
如果next存在,則程序自動跳轉

3 消息格式

{
    "script" :"echo  test"
}

{
    "script" :"echo  test",
    "next" : 'B'  # 填寫下一跳爲頂點名稱
}

{
    "script" : "echo test",
    "next" : 2  # 填寫下一跳爲頂點id
}

爲了方便用戶,next可以提供2種類型的參數:
1 int 表示vertex的id
2 str 表示使用vertex的name,但是是同一個graph id, 同一個DAG的定義中名字不能衝突,所以可以用。

8 流程結束

如果一個頂點的出度爲0,則此節點爲終點。

如何判斷出度爲0.
在edge表中,使用當前節點的頂點id作爲弧尾,找不到弧頭h的任何記錄。

9 執行引擎之pipeline 設計

1 概述

前面的設計僅僅是流程DAG定義,流程真正執行的時候需要記錄執行這個流程的任務流的數據,

2 表pipeline

字段名 類型 說明
id int 主鍵
g_id int 外鍵,指明使用的是哪一個流程DAG定義
current int 外鍵,頂點id,表示當前走到哪一個節點

這個表以後還要添加其他字段,存儲一些附加信息。如誰加入的流程,執行時間等。


起點的選擇,通過查詢edge表來確定起點位置,當入度爲0的點則是起點,通過頂點表和邊表來進行處理


一個pipeline應該指向哪一個DAG,並選擇DAG的起點,因爲DAG 可能存在多個起點,即入度爲0的頂點,需要指定,然後把這些信息記錄在pipeline表中,current爲起點頂點的id,提取current 頂點的input信息,用戶輸入滿足了,才能執行script腳本


不管是手動執行還是自動執行,如果到了下一個節點,需要修改current字段的值,

任務流執行完畢,修改最後一個節點的狀態爲完成

3 舉例

當前節點任務是打包,調用maven命令執行打包,先要提取inout,要求用戶輸入ip地址,輸出目錄等信息。然後才能執行打包腳本。

10 執行引擎歷史軌跡設計

1 概述

pipeline表只能看到有哪些流正在運行,但是究竟走了DAG中的那些節點,不清楚,執行節點前輸入了那些參數也是不清楚的

如何查詢,回溯當前的pipeline的運行軌跡

2 track 表

字段名 類型 說明
id int 主鍵
p_id int 外鍵,哪一個流程的歷史
v_idint 外鍵,頂點的ID,經歷過的歷史節點
input text 可以爲空,輸入的參數值
output text 可以爲空,任務的輸出

3 狀態設計

在pipeline表,track表中增加state字段,用於描述在某個節點上執行的狀態,是等待中,還是正在運行,還是成功或者失敗,還是執行完畢。
STATE_WAITING=0
STATE_RUNNING=1
STATE_SUCCEED=2
STATE_FAILED=3
STATE_FINISH=4

11 最終設計模型

運維流程系統

左邊主要針對的是基礎設計,右邊主要針對的是引擎層面的設計,流程看似一樣,但其實際是不同的

12 DAG 檢測

1 DFS 算法

DFS ( depth first search)深度優先遍歷,遞歸算法
需要改進算法適用於有向圖
不能直接檢測有向圖是否有環

2 拓撲排序算法

拓撲排序就是把有向圖中的頂點以線性方式排序,如果有弧<u,b>,則最後線性排序的結果,頂點u總是在頂點b的前面

一個有向圖能被拓撲排序的充要條件是: 它必須是DAG

3 kahr算法

1 選擇一個入度爲0的頂點並輸出它
2 刪除以此頂點爲弧尾的弧

重複上面2步,直到輸出全部頂點爲止,或者圖中不存在入度爲0的頂點爲止。

實例如下

運維流程系統

第一步,找到入度爲0的點A,然後刪除A和以A爲弧尾的邊,第二步,找到入度爲0的C,刪除頂點C和以C爲弧尾的邊,第三步,找到B,刪除頂點B,並刪除以B爲弧尾的邊,最後,刪除入度爲0的頂點D

運維流程系統

上面2個圖都不是DAG,左圖一個環,右圖2個環
這2個圖都找不到入度爲0的起始點,都不是DAG

運維流程系統

上圖中雖然能找到入度爲0的頂點,但是移除它和關聯的邊,剩下的頂點找不到入度爲0的頂點,其不是DAG

四 項目配置流程系統

1 概述

本項目使用service層和module層進行配置和處理工作,其中,service層主要處理相關數據,而module層的作用則是創建和操作數據庫配置

2 項目創建

1 虛擬目錄創建

mkdir pipeline 

cd pipeline/

pyenv  virtualenv  3.5.3  pipe 

pyenv local pipe

2 數據庫相關配置

create database  pipeline charset utf8mb4;

grant all on  pipeline.*  to  pipe@localhost  identified by 'pipe';

flush privileges;

運維流程系統

2 model層創建

1 概述

數據持久化最終的結果是需要通過關係數據庫的表達來實現的

運維流程系統

2 配置文件創建

創建config文件,用於存儲數據庫配置信息

#!/usr/bin/poython3.6
#conding:utf-8

USERNAME="pipe"
PASSWORD="pipe"
DBIP="localhost"
DBPORT=3306
DBNAME="pipeline"
PARAMS="charset=utf8mb4"
URL="mysql+pymysql://{}:{}@{}:{}/{}?{}".format(USERNAME,PASSWORD,DBIP,DBPORT,DBNAME,PARAMS)
DATABASE_DEBUG=True

3 創建模型模板

用於存儲和封裝數據類

添加插件sqlachemy 和 pymysql

pip install  sqlalchemy pymysql

model.py 內容如下

#!/usr/local/bin/python3.6
#coding:utf-8
# @Time    : 2019/11/27 11:18
# @Author  : ZhangBing
# @Email   : [email protected]
# @File    : model.py
# @Software: PyCharm

from pipeline.config import  URL,DATABASE_DEBUG
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,ForeignKey,Text
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker

STATE_WAITING=0
STATE_RUNNING=1
STATE_SUCCEED=2
STATE_FAILED=3
STATE_FINISH=4

Base=declarative_base()
# 創建圖,用於存儲pipeline 流水
class  Graph(Base):
    __tablename__='graph'
    id=Column(Integer,primary_key=True,autoincrement=True)
    name=Column(String(48),nullable=False)
    desc=Column(String(128))

    # 創建relationship關係圖,用於配置,此處的含義是可以使用此關係來處理查看此pipe下的頂點和邊的信息
    vertexs=relationship("Vertex")
    edges=relationship("Edge")

# 創建頂點表,用於存儲頂點信息
class  Vertex(Base):
    __tablename__='vertex'
    id=Column(Integer,primary_key=True,autoincrement=True)
    name=Column(String(48),nullable=False)
    graph_id=Column(Integer,ForeignKey('graph.id')) # 配置外鍵,用於
    input=Column(String(128),nullable=True)
    script=Column(Text,nullable=True)
    graph=relationship("Graph")
    # 從頂點看邊,一個頂點對應兩個邊,肯定會出現問題,因此此處使用此方式來指定到對應的邊,此處必須使用引號,否則會導致報錯
    tails=relationship("Edge",foreign_keys="[Edge.tail]")
    heads=relationship("Edge",foreign_keys="Edge.head")

# 創建邊表,用戶存儲邊的相關信息
class  Edge(Base):
    __tablename__='edge'
    id=Column(Integer,primary_key=True,autoincrement=True)
    tail=Column(Integer,ForeignKey('vertex.id'),nullable=False)
    head=Column(Integer,ForeignKey('vertex.id'),nullable=False)
    graph_id=Column(Integer,ForeignKey('graph.id'),nullable=False)
# 用於記錄當前流水線執行到那塊了,自然需要節點信息

# 此處用於查看流水線的執行情況,如再那個流水線的那個節點。執行的結果狀態如何
class Pipeline(Base):
    __tablename__='pipeline'
    id=Column(Integer,primary_key=True,autoincrement=True)
    current=Column(Integer,ForeignKey('vertex.id'))
    graph_id = Column(Integer, ForeignKey('graph.id'))
    state=Column(Integer,nullable=False,default=STATE_WAITING)
    vertex=relationship("Vertex")

# 此處用於創建記錄表,此表必須和pipeline進行聯繫,並和頂點表倆西,獲取對應的頂點的最終執行信息
class  Track(Base):
    __tablename__='track'
    id = Column(Integer, primary_key=True, autoincrement=True)
    pipeline_id=Column(Integer,ForeignKey('pipeline.id'))
    vertex_id=Column(Integer,ForeignKey('vertex.id'))
    input=Column(Text,nullable=True)
    output=Column(Text,nullable=True)
        state=Column(Integer,nullable=False,default=STATE_WAITING)  # 用於記錄該節點是否執行任務成功

    vertex=relationship("Vertex")
    pipeline=relationship("Pipeline")

# 此處用於創建存儲引擎
class  DBcreate:
    def  __init__(self):
        # 初始化時創建引擎
        self.__engine=None
        self.__session=None
        self.flag=False
    def  db_init(self,DB_URL,DATABASE_DEBUG):
        if not  self.flag:
            self.__engine = create_engine(DB_URL, echo=DATABASE_DEBUG)
            # 創建會話
            self.__session = sessionmaker(bind=self.__engine)()
            self.flag = True
        return  self
    @property
    def  session(self):
        if not  self.flag:
            raise   ArithmeticError("Not  initialized")
        return self.__session
    @property
    def  engine(self):
        if not self.flag:
            raise ArithmeticError("Not  initialized")
        return  self.__engine
    def  db_create(self):  # 創建表
        Base.metadata.create_all(self.__engine)
    def  db_delete(self):  # 刪除表
        Base.metadata.drop_all(self.__engine)

db=DBcreate().db_init(URL,DATABASE_DEBUG)  # 通過此處的實例化實現了向外創建的目的 

db.db_create()  # 創建表 

if __name__ == "__main__":
    pass

運行結果如下

運維流程系統

創建數據庫如下

運維流程系統

2 service 層處理

1 需求

1 定義DAG,及就是schema定義
2 執行某一個DAG流程

2 問題

DAG 是否允許修改

可以這樣考慮。如果DAG 定義好還未使用,可以進行修改操作,一旦使用過,則不能修改,因此便需要在圖graph表中增加一個字段用於區分是否是執行過的pipeline

3 DAG 定義

#!/usr/bin/poython3.6
#conding:utf-8
from .model import   db
from  .model import   Graph,Vertex,Edge
from  .model import   Pipeline,Track

# 創建 DAG
def  create_graph(name,desc=None): # 此處用於創建圖表,主要需要傳遞的參數是name和desc 描述信息
    g=Graph()
    g.name=name
    g.desc=desc

    db.session.add(g)

    try:
        db.session.commit()
        return  g
    except:
        db.session.rollback()
# 創建頂點表
def  add_vertex(graph:Graph,name,input=None,script=None):
    v=Vertex()
    v.graph_id=graph.id
    v.name=name
    v.script=script
    v.input=input
    db.session.add(v)
    try:
        db.session.commit()
        return   v
    except:
        db.session.rollback()

# 創建邊表,用存儲邊的數據信息
def add_edge(graph:Graph,tail:Vertex,head:Vertex):
    e=Edge()
    e.graph_id=graph.id
    e.tail=tail.id
    e.head=head.id
    db.session.add(e)
    try:
        db.session.commit()
        return  e
    except:
        db.session.rollback()

def del_vertex(id):  # 通過頂點表的id來刪除相關的信息,需要刪除頂點和頂點對應的邊的信息
    query=db.session.query(Vertex).filter(Vertex.id==id)
    v=query.first()
    if  v: # 找到頂點。刪除相關的邊,然後刪除頂點
        try:
            db.session.query(Edge).filter((Edge.tail==v.id)  | Edge.head==v.id).delete()  # 刪除對應的邊
            query.delete()  # 刪除頂點
            db.session.commit()  # 提交。若失敗,則回滾
        except:
            db.session.rollback()
        return  v

使用裝飾器處理上述提交問題,避免繁瑣,創建util 文件,用於處理數據庫數據的提交和回撤問題

運維流程系統

#!/usr/bin/poython3.6
#conding:utf-8

from  pipeline.model import db
from functools import  wraps

def transactional(fn):
    @wraps(fn)
    def __wapper(*args,**kwargs):
        ret=fn(*args,**kwargs)
        try:
            db.session.commit()
            return  ret
        except:
            db.session.rollback()
    return  __wapper

if __name__ == "__main__":
    pass

4 service 層修改結果如下

#!/usr/bin/poython3.6
#conding:utf-8
from .model import   db
from  .model import   Graph,Vertex,Edge
from  .model import   Pipeline,Track
from  util import transactional

# 創建 DAG
@transactional
def  create_graph(name,desc=None): # 此處用於創建圖表,主要需要傳遞的參數是name和desc 描述信息
    g=Graph()
    g.name=name
    g.desc=desc
    db.session.add(g)
# 創建頂點表
    return   g
@transactional
def  add_vertex(graph:Graph,name,input=None,script=None):
    v=Vertex()
    v.graph_id=graph.id
    v.name=name
    v.script=script
    v.input=input
    db.session.add(v)
    return  v

# 創建邊表,用存儲邊的數據信息

@transactional
def add_edge(graph:Graph,tail:Vertex,head:Vertex):
    e=Edge()
    e.graph_id=graph.id
    e.tail=tail
    e.head=head
    db.session.add(e)
    return  e

def del_vertex(id):  # 通過頂點表的id來刪除相關的信息,需要刪除頂點和頂點對應的邊的信息
    query=db.session.query(Vertex).filter(Vertex.id==id)
    v=query.first()
    if  v: # 找到頂點。刪除相關的邊,然後刪除頂點
        try:
            db.session.query(Edge).filter((Edge.tail==v.id)  | Edge.head==v.id).delete()  # 刪除對應的邊
            query.delete()  # 刪除頂點
            db.session.commit()  # 提交。若失敗,則回滾
        except:
            db.session.rollback()
        return  v

3 DAG 驗證和測試數據處理

1 測試數據

測試數據函數,暫時放置在service.py中進行處理

```
def  test_create_dag():
try:
    g=create_graph('test1')  # 此處成功返回一個graph對象
    # 增加節點
    input='''
        {
            "ip" :{
                "type" :"str",
                "required" :"true",
                "default" : 192.168.1.200

            }
        }
    '''
    script={
        "script" : "echo  test1.A",
        'next' : 'B'
        }
    # 此處可以設置爲爲了用戶方面,next可以設置接收兩種類型,數字表示頂點的id,字符串表示用一個DAG中的該名稱的頂點,其不能重複

    a=add_vertex(g,'A',None,json.dumps(script))  # 對數據進行處理
    b=add_vertex(g,'B',None,'echo  B')
    c=add_vertex(g,'C',None,'echo C')
    d=add_vertex(g,'D',None,'echo D')
    # 增加邊
    ab=add_edge(g,a,b)
    ac=add_edge(g,a,c)
    cb=add_edge(g,c,b)
    bd=add_edge(g,b,d)

    # 創建環路

    g=create_graph('test2') # 環路

    # 增加頂點
    a = add_vertex(g,'A',None,'echo A')
    b = add_vertex(g, 'B', None, 'echo  B')
    c = add_vertex(g, 'C', None, 'echo C')
    d = add_vertex(g, 'D', None, 'echo D')
    # 增加邊。abc之間環路
    ba=add_edge(g,b,a)
    ac=add_edge(g,a,c)
    cb=add_edge(g,c,b)
    bd=add_edge(g,b,d)

    # 創建DAG
    g=create_graph('test3')  # 多個頂點

    # 增加頂點
    a = add_vertex(g, 'A', None, 'echo A')
    b = add_vertex(g, 'B', None, 'echo  B')
    c = add_vertex(g, 'C', None, 'echo C')
    d = add_vertex(g, 'D', None, 'echo D')
    # 增加邊

    ba=add_edge(g,b,a)
    ac=add_edge(g,a,c)
    bc=add_edge(g,b,c)
    bd=add_edge(g,b,d)

    # 多起點處理方式
    g = create_graph('test4')  # 多個頂點

    # 增加頂點
    a = add_vertex(g, 'A', None, 'echo A')
    b = add_vertex(g, 'B', None, 'echo  B')
    c = add_vertex(g, 'C', None, 'echo C')
    d = add_vertex(g, 'D', None, 'echo D')
    # 增加邊

    ab = add_edge(g, a, b)
    ac = add_edge(g, a, c)
    cb = add_edge(g, c, b)
    db = add_edge(g, d, b)
except  Exception as  e:
    print (e)

```

2 執行寫入測試數據

創建app文件

運維流程系統

執行結果如下

運維流程系統

3 DAG 驗證概述

當增加一個DAG定義後,或者修改了DAG定義後,就需要對DAG 進行驗證,判斷是否是一個DAG圖,如何知道寫入的數據庫的數據是有效的,則需要通過在graph表中增加一個checked字段,用於判斷是否是通過驗證的,在以後的創建流程操作中,若檢測到其字段爲0時,則表示其未通過DAG 驗證


注意:如果有一個流程使用了這個DAG,其將不被允許修改沒了實現這個功能,且不要每一次都查詢一下這個DAG是否被使用,可以在graph表中提供一個字段sealed,一旦設置就不能修改和刪除,表示有人使用了,


在DAG定義後,修改後,就立即進行DAG檢驗,這樣使用的時候就不用每次都檢驗。


4 DAG 設計表結構結果

字段名 類型 說明
id int 主鍵
name varchar 非空,唯一,圖的名稱
desc varchar 可爲空,描述信息
checked int 流程檢驗數據
sealed int 不可爲空,默認爲0,0表示未使用,1表示已經有執行流程使用了,被封閉不可修改

結果如下

class  Graph(Base):
    __tablename__='graph'
    id=Column(Integer,primary_key=True,autoincrement=True)
    name=Column(String(48),nullable=False)
    desc=Column(String(128),nullable=False)
    checked=Column(Integer,nullable=False,default=0)
    sealed=Column(Integer,nullable=False,default=0)

    # 創建relationship關係圖,用於配置,此處的含義是可以使用此關係來處理查看此pipe下的頂點和邊的信息
    vertexs=relationship("Vertex")
    edges=relationship("Edge")

重新處理表結構,結果如下

運維流程系統

查詢所有入度爲0的頂點

SELECT  vertex.*  FROM  vertex  LEFT JOIN  edge  on    vertex.id=edge.head  WHERE  vertex.graph_id=1   AND  edge.head  is  NULL  

運維流程系統

採用做鏈接找到edge中的null的方式,找到入度爲0的頂點。
但這種方式找到的不適合進行驗證,因爲第一批入度爲0的頂點找到之後,還需要再次查詢,找到第二批頂點。其可以將所有的頂點,邊都先查詢一遍,然後再客戶端數據庫中進行相關的處理

5 函數創建

在service.py中進行創建此函數

def check_graph(graph:Graph):
    query=db.session.query(Vertex).filter(Vertex.graph_id==graph.id)
    vertexs=[vertex.id  for  vertex  in  query]  # 獲取頂點列表
    query=db.session.query(Edge).filter(Edge.graph_id==graph.id)
    edges=[(edge.tail,edge.head)  for  edge  in query ]  # 此處獲取邊的列表
    while True:
        vis=[]  #存放索引,
        for  i,v  in enumerate(vertexs):  # 此處需要對頂點表中的每一個頂點和邊表中的head進行匹配
            for _,h  in edges:
                if  h==v:  # 此處的v表示頂點的id,此處的h表示head及弧頭的數據,若相等,則表示其頂點有弧頭,則表示入度不爲0,此處判斷失敗
                    break
            else:  # 此處表示其頂點表中的和邊表中head的沒有匹配的情況,及入度爲0的頂點
                ejs=[]
                for  j,(t,_)  in enumerate(edges):  # 此處是處理弧尾的情況,
                    if t==v:  # 此處是弧尾和頂點相等的情況,則將其加入到對應的ejs中,弧尾相等。則表示其可以形成邊的關係
                        ejs.append(j)  # 增加其頂點到對應的邊關係中
                vis.append(i)  # 增加其邊的索引到列表彙總
                for j in reversed(ejs):  # 刪除列表中的邊對應的索引。及刪除和入度爲0的頂點對應的邊
                    edges.pop(j)
                break
        else: # 若遍歷所有都沒有找到入度爲0的頂點,表明其本身就有環
            return  False
        for  i  in vis:
            vertexs.pop(i)
        if len(vertexs)  +  len(edges) ==0:  #此處爲0,表示刪除完成,則爲DAG
            try:
                graph=db.session.query(Graph).filter(Graph.id==graph.id).first()
                if graph:
                    graph.checked=1 # 修改和更新狀態
                db.session.add(graph)
                db.session.commit()
            except  Exception as e:
                db.session.rollback()
                raise e

執行在app中進行。如下

運維流程系統

結果如下

運維流程系統

4 流程的啓動和inut 驗證和執行器

1 執行引擎

起點
開啓一個流程的時候,需要在界面中選擇一個checke爲1的DAG,選擇一個頂點,頂點的選擇,使用前面的左連接sql語句可以列出當前DAG中入度爲0的節點作爲初始頂點,供用戶選擇一個作爲起點


將起點信息寫入pipeline表,Track表,狀態State都是WAITING

pipeline 表示正在執行的節點
track記錄歷史信息,當前還沒有input 具體值的信息

2 執行引擎代碼如下

def start(graph:Graph,vertex:Vertex,params=None):
    # 判斷流程是否存在,且checked爲1的則通過檢驗
    g=db.session.query(Graph).filter(Graph.id==graph.id).filter(Graph.checked==1).first()
    if not g:
        return
    v=db.session.query(Vertex).filter((Vertex.id==vertex.id) & (Vertex.graph_id==graph.id)).first()
    if not v:
        return
    # 寫入pipeline 表
    p=Pipeline()
    p.current=v.id
    p.graph_id=g.id
    p.state=STATE_WAITING
    db.session.add(p)
    try:
        db.session.commit()
    except:
        db.session.rollback()
    t=Track()
    t.pipeline_id=p.id
    t.vertex_id=v.id
    t.state=STATE_WAITING
    db.session.add(t)
    try:
        db.session.commit()
    except:
        db.session.rollback()
    if g.sealed==0:
        g.sealed=1
        db.session.add(g)
    return  p

測試如下

運維流程系統

def  test_start():
    g=Graph()
    g.id=1
    v=Vertex()
    v.id=1
    p=start(g,v)
    if p:
        print (p)
        print (p.vertex.script)

test_start()

結果如下

運維流程系統

3 input 驗證

開啓一個流程後,起點可能會設置input,這時候就需要有一個界面,讓用戶填寫參數,這是一個交互的過程,也可以實現爲自動填寫參數

提取起點的input參數並進行驗證,驗證通過,將輸入值,保存到字典中,將值存入track表,將字典中的input值交給執行器完成,此處功能在mschedule項目中已經實現,此處不再累贅

4 執行器

使用Input獲得字典,對script字段中的腳本進行替換。若是空字典,就直接執行腳本,啓動線程,使用subprocess的Popen開啓子進程執行,返回的結果保存到track的output中,判斷成功失敗,如果成功,則置狀態爲成功,繼續,若失敗,則置狀態爲失敗,流程停止

### 執行器配置
from subprocess  import  Popen,PIPE
def execute(script,timeout=None):
    proc=Popen(script,shell=True,stdout=PIPE)
    code=proc.wait(timeout)
    txt=proc.stdout.read()
    return  code,txt

5 流轉

1 概述

設想一種自動化執行流程,先不考慮input的交互步驟,假設沒有input環節

1 用戶選擇了起點之後,如何開始執行腳本?

如果用戶提交了一個起點後,start函數開始執行,就一直執行到最後一個節點,這個函數才退出,可以,但其存在問題

也就是用戶提交起點後,起點腳本執行是需要時間的,這時候異步執行可能是一個好的方式,開啓線程,專門負責從數據庫的pipeline表中讀取所有WAITING的節點,執行script,獲取返回結果


2 如何流轉

腳本執行成功,需要流轉到下一個節點

2.1 終點
如果本節點的出度爲0,就是終點了,將pipeline,track中的字段置位完成

2.2 自動選擇
在腳本json中執行了next,把pipeline中current字段更新爲最新的頂點id,狀態爲等待,track表中的原來的頂點id的狀態修改爲成功,新增一條跟蹤頂點id記錄,狀態是等待。

2 異步執行

此處具體代碼參考如下

https://blog.51cto.com/11233559/2432516

#!/usr/bin/poython3.6
#conding:utf-8
from concurrent.futures  import  ThreadPoolExecutor,as_completed
import  random
import  threading

def test_fun(s,key):
    print ("enter~~~~~~~~{}  {}s key={}".format(threading.current_thread(),s,key))
    threading.Event().wait(s)
    return  "ok {}".format(threading.current_thread())

with ThreadPoolExecutor(max_workers=3)  as  executor:
    futures={executor.submit(test_fun,random.randint(1,8),i):i  for i in range(7)}

    for future in as_completed(futures):
        id=futures[future]
        try:
            print (id,future.result())
        except  Exception as e:
            print (id,'failed')

結果如下

運維流程系統

3 流轉代碼如下


from  concurrent.futures  import  ThreadPoolExecutor,as_completed

MAX_POOL_SIZE=5

executor=ThreadPoolExecutor(max_workers=MAX_POOL_SIZE)

def  iter_pipelines():# 此處可修改成yield  from
    query=db.session.query(Pipeline).filter(Pipeline.state==STATE_WAITING)
    pipelines= query.all()
    for pipeline  in  pipelines:
        yield  pipeline

#流轉
def   shift():
    futures={}
    for pipeline  in  iter_pipelines():
        s=json.loads(pipeline.vertex.script) # 腳本的處理
        script=s['script'] # 拿到腳本
        f=executor.submit(execute,script)  # 送入函數和對應的參數
        futures[f]=pipeline,s

        for  f  in as_completed(futures):
            p,s=futures[f]  # 遍歷相關對象

            try:
                code,txt=f.result()
                print (code,txt,p.current)
                if code==0:  # 基本正常,此處返回爲0表示正常情況
                    t=db.session.query(Track).filter((Track.pipeline_id==p.id) & (Track.vertex_id==p.current())).one()
                    t.state=STATE_SUCCEED
                    t.out=txt
                    db.session.add(t)
                    if 'next'  in s: # 是否存在下一跳
                        n=s['next'] # 獲取下一跳
                        if  type(n)==int:  # 此處對應的是執行下一個節點id對應的服務
                            pass
                        else:  # 此處是對應的next爲name的情況
                            pass
                        p.current=next  # 更新節點數據
                        p.state=STATE_WAITING # 下一個節點的狀態應該是正常
                        db.session.add(p)
                    else:  # 此處未進行處理,則需要通過外部點擊的方式完成
                        p.state=STATE_SUCCEED
                        db.session.add(p)
                else:
                    pass
            except  Exception as e:
                print (e,'!!!!!!!!!!!!!!')
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章