今天在 Ryu mailing list 中看到有人提出了一個問題:
RYU can’t get more than 21K flow entry to curl client
Dear All,
i'm using RYU v3.19 to test Noviflow switch in lab.
[ something i did ]
1. push 21,000 flow through RYU to Novi switch.
2. using curl to query those 21,000 flow.
during my test,
install/get 18000 entry is OK, but with 21,000 flow, ryu can't get all 21,000 entry.
tcpdump on RYU,
can see Novi send much MultiPart_Reply to RYU.
looks like ryu didn't return results to CURL client.
i got empty list, below.
(下略)
用簡短的說法就是他裝了一大堆的 Flow Entry 在他的 Switch 中,然後用 Ryu REST API 去取得
那一個 Switch 中的 Flow Entry,結果出來的內容卻是錯的。
我一開始想到的是之前聽 Rascov 說 ONOS 在有大量的 Flow Entry 時會發生錯誤,他主要問題是大量的 Query 導致網路流量被塞滿,然後導致 OpenFlow 的 Echo 機制出了問題,但是後來看了一下信件所附的 pcap 以及 debug message 檔案,發現並不是這麼一回事。
於是我就從 REST API 這邊開始去 trace 看問題點釋出在哪邊,順便學一下 ofctl 實作的方法。
ofctl 主要有幾個東西是我們需要知道的:
- waiter: 用於接收某一個指定的 Query(透過 xid 去區別)
- lock: 實際上是一個 hub.Event() 實體,用於等待一段時間(timeout,長度預設為一秒)
- msgs: 用於保存接收到的 reply
在呼叫 ofctl_v1_x.get_flow_stats 時,會將 waiter 帶入,要注意的是這一個 waiter 在這一支
應用程式中只會有一個,他所儲存的內容是一個 dict 資料結構,大致的內容如下:
waiters -> {dpid: waiter} : 每一個 switch 都會有對應的 waiter
waiter -> {xid: (lock, msgs)} : 每一個事件回應的 xid 都是在送出事件是就決定好了
因此可以用 xid 推論回送出的事件,並且找出他的回應應該要存在哪邊(msgs),以及決定該事件的 lock
這一個 lock 有兩個用途:
- 用於等待時間(timeout)
- 另一個是當這一個 lock 如果有被設定(lock.is_set())的話,則表示該事件有正常的結束回應(收到一個 flag 為 0 的 reply),否則
就將該 waiter 直接移除,隨後相同 xid 的 reply 將不會在被收到
get_flow_status 中會呼叫一個名為 send_stats_request 的 method,他的內容如下:
def send_stats_request(dp, stats, waiters, msgs):
dp.set_xid(stats)
waiters_per_dp = waiters.setdefault(dp.id, {})
lock = hub.Event()
waiters_per_dp[stats.xid] = (lock, msgs)
dp.send_msg(stats)
lock.wait(timeout=DEFAULT_TIMEOUT)
if not lock.is_set():
del waiters_per_dp[stats.xid]
參數說明如下:
- dp: 表示要送往的 Switch
- stats: 一個 OpenFlow message,在這邊給予 xid
- waiters: 同剛剛的說明,他會把 lock, msgs, xid 放到這裡面
- msgs: 用來保存接收到的訊息
再來他會將該設定的東西設定好,例如 waiters 裡面會需要有 dpid -> waiter -> xid -> (lock, msgs) 這樣的資訊,並且會給予要送出去的訊息一個 xid,再來就是建立 lock。
接者他就使用 lock.wait 去等待,最多等待一秒鐘。
問題來了 ,假設這一個 lock 等待了一秒鐘,但是 switch 要送的東西 還沒有送完 ,這樣會發生什麼事情?
剛剛有提到,當時間到了,如果 lock.is_set() 為否的話,waiter 將會被刪除,導致送回來的訊息不會在被接收以及儲存,
我們可以從原始郵件中的 pcap 檔案看到他執行超過一秒鐘,所以導致了這一個問題發生。
解決辦法
- 最簡單的解決辦法就是將 DEFAULT_TIMEOUT 調長,讓他有充分的時間可以把所有的資訊接收完畢,但是這樣會發生一個問題,就是當網路出現了問題導致他傳送相當的緩慢,舉例來說每次傳送間隔都是幾秒鐘,在這樣的情況下我們也許會捨棄這一次的查詢,以避免整個應用程式被我們卡住,但我們又把 Timeout
調長了,這樣一次的查詢就是好幾秒鐘這麼久,讓整個程式相當的沒有效率。 - 另一種解法就是,在每一次的 reply 時,把目前 lock 的 timeout 時間重設,但是 timeout 還是一樣的短,這樣一來查詢的時間就比較貼近查詢的量。
目前情況
在對方將 timeout 調長之後,問題就解決了,但是我會希望用第二種解法會比較好。
HI Sir,
yes!
after change timeout to 5s, i can get all flows.
Thanks for great help
Mark
後續 PATCH
後來我在 Ryu 的 ML 裡面有提到這件事情,並且補上了 patch XD
Patch 主要的內容:
diff --git a/ryu/lib/ofctl_v1_3.py b/ryu/lib/ofctl_v1_3.py
index 8490206..5b709f3 100644
--- a/ryu/lib/ofctl_v1_3.py
+++ b/ryu/lib/ofctl_v1_3.py
@@ -404,10 +404,18 @@ def send_stats_request(dp, stats, waiters, msgs):
dp.set_xid(stats)
waiters_per_dp = waiters.setdefault(dp.id, {})
lock = hub.Event()
+ previous_msg_len = len(msgs)
waiters_per_dp[stats.xid] = (lock, msgs)
dp.send_msg(stats)
lock.wait(timeout=DEFAULT_TIMEOUT)
+ current_msg_len = len(msgs)
+
+ while current_msg_len > previous_msg_len:
+ previous_msg_len = current_msg_len
+ lock.wait(timeout=DEFAULT_TIMEOUT)
+ current_msg_len = len(msgs)
+
if not lock.is_set():
del waiters_per_dp[stats.xid]
目前的作法就是把原先的 Hard timeout 改成 idle timeout 邏輯,在每一次有新的 reply 時就在繼續等待
,若等待後沒有任何更新,則有兩種可能:
- 它確實是更新完畢了。
- 它其實還是有東西要送過來,只是兩個 reply 之間太久了(可能是網路問題),導致 reply 過不來,這時候
timeout 就發揮它真正的效用,避免整個 Ryu App 被卡住。
目前(8/6 00:23)PATCH 才剛送出去,還沒有補上(畢竟他們可能在睡覺 XD),可能要等到早上才有結果。