RFC-0071:OTA 停止

RFC-0071:OTA 後援
狀態已接受
區域
  • 系統
說明

防止裝置在版本界線之間進行 OTA 回溯。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-02-03
審查日期 (年-月-日)2021-02-24

摘要

本文件提出的計畫可防止裝置在版本界限內安裝無線更新 (OTA)。

提振精神

當儲存空間堆疊對檔案系統格式進行重大變更時,會回溯格式的主要版本號碼,以免在舊版系統上執行的驅動程式嘗試掛載及使用新格式的映像檔。

如果系統更新堆疊中含有等同的版本號碼,使用者就無法嘗試將 OTA 回溯至不支援裝置所含檔案系統映像檔的驅動程式庫版本。換句話說,這可讓我們在裝置變成磚塊之前,讓「回溯 OTA」作業失敗。

這麼做可帶來以下好處:

  • 對於任何會保留狀態的應用程式,這項功能都非常實用。舉例來說,應用程式會維護 sqlite 資料庫,而資料庫的結構定義可能會隨時間變更。
  • 具體來說,這對儲存空間團隊來說非常實用,因為他們過去必須花費大量時間,處理因跨版本邊界執行反向 OTA 而產生的問題。
  • 這會強化 Fuchsia 不支援回溯 OTA 的事實,因為這類 OTA 僅能盡力執行。

請注意,這項提案不會變更支援哪些 OTA 序列,以及哪些不支援。只是讓這項支援功能更明確。OTA 備援的主要目的,是避免開發人員的裝置進入無效狀態。對於實際生產裝置,應主要由版本管理系統強制執行「不回溯 OTA」不變量。

如果沒有這項建議,在開發人員嘗試啟動裝置時,嘗試在相容性不一致的界限上進行回溯 OTA 作業,就會導致問題 (例如,驅動程式庫可能不支援檔案系統格式)。有了這個提案,開發人員就能在執行 OTA 前發現這項問題 (且錯誤會更清楚顯示),進而提供更好的開發人員體驗。

背景

術語

OTA 是升級基礎作業系統的機制。Fuchsia 裝置可接收並安裝系統和應用程式軟體的 OTA 更新。

Stepping Stone 版本是指無法在 OTA 中略過的版本。舉例來說,假設有三個連續版本 A、B 和 C。傳統上,我們需要支援 A->BB->CA->C 的 OTA。如果我們將 B 宣告為踏腳石版本,這會移除 A->C 邊緣,因此 A 升級至 C 的唯一方法就是 OTA A->B,然後再升級至 B->C。在實際操作中,這項功能可用於風險較高的遷移作業,以及減少需要測試的推送 OTA 數量。

OTA 備援機制與踏腳石的關係

OTA 備援和階梯式版本都是我們必須進行安全遷移 (例如儲存格式遷移) 的基礎元素。至於如何使用 OTA 備援和階梯式版本,具體的應對策略超出本 RFC 的範圍。我們在此提供範例,說明如何使用這些基本元素來支援安全的遷移作業。

建議您考慮遷移儲存格式。我們可能會採取下列步驟:

  1. 新增支援新格式,但尚未啟用/遷移。提交 OTA 後援。
  2. 請稍候片刻。
  3. 使用上述其中一種遷移策略啟用新格式。

如果我們確實遷移裝置,可以採取兩個進一步的步驟來啟用清理作業:

  1. 推出包含 (3) 的踏板版本。
  2. 移除遷移程式碼,並停止支援舊格式。

透過這個過渡版本,我們可以假設裝置會經過含有遷移程式碼的版本,因此我們可以移除舊版格式的讀取支援功能。

在 (1) 中提高 OTA 備援機制,可確保裝置不會降級至不支援新格式的版本。

撞擊後擋板的政策

應視需要一次性調整後備計劃。絕大多數變更都不需要使用回溯升級功能。如果這項 RFC 獲得核准,應發布官方應對手冊文件,說明如何提升後盾的具體步驟。在此期間,我們將概略說明這項政策。

提出 CL 以提升後援時,作者應:

  • 提供 bugs.fuchsia.dev 問題的連結,說明為何需要升級,以及開發人員在絕對需要透過回溯點降級裝置時,該如何繼續操作 (例如,答案可能是 flash 或 pave)。
  • 取得 //src/sys/pkg/OWNERS 核准。

設計

讓我們引入 epoch.json 檔案,讓該檔案同時出現在更新套件和系統中。應為含有兩個字串索引鍵的 JSON 檔案:

  • 「version」:應包含 epoch.json 結構定義版本的單一字串值。實際上,系統在執行更新時不會檢查這個值,這個鍵只是為了讓您清楚知道 epoch.json 結構定義在正式版中是否有變更。
  • 「epoch」,應為 OTA 備援機制的單一整數值。如果更新套件的紀元 < 系統的紀元,我們應在準備階段使用 UNSUPPORTED_DOWNGRADE 讓 OTA 失敗。

例如,epoch.json 可能如下所示:

{
  "version": "1",
  "epoch": 5
}

為了安全地提升紀元,我們也要介紹一個 epoch_history 檔案,這個檔案會透過建構系統編譯成 epoch.jsonepoch_history 檔案的格式可能為:

0=Initial epoch (https://fxbug.dev/42144857)
1=Storage format migration (https://fxbug.dev/XXXXX)
...
N=Most recent change (https://fxbug.dev/YYYYY)

每次引入回溯不相容的變更時,都應手動升級 epoch_history 檔案。

雖然中介 epoch_history 檔案會增加一層複雜度,但這種做法仍有以下優點:

  • 它會提供所有版本升級變更的記錄 (強制說明文件)
  • 如果有兩個人基於不同原因嘗試提前 Epoch,就會產生合併衝突。

實作

變更將完全發生在平台 (具體來說是系統更新堆疊) 中。

為了實施變更,我們需要:

  • epoch_history 新增至 //src/sys/pkg/bin/system-updater。
    • 此外,請建立指令碼,將 epoch_history 轉換為 epoch.json
    • 請讓建構系統使用這個指令碼,將 epoch.json 新增至系統更新器的 out 目錄。
  • 修改 BUILD,讓 epoch.json 也能放入更新套件。
  • 系統更新器應在 Prepare 階段結束時檢查 epoch.json
    • 如果更新套件中沒有 epoch.json,或是反序列化時發生問題,請假設紀元是 0。我們刻意忽略錯誤,以便在 epoch.json 結構定義變更時仍可進行 OTA。
    • 如果系統更新器的 out 目錄中沒有 epoch.json,或是反序列化時發生問題,則會失敗,因為這不是預期的情況。建議您使用 include_str 巨集從 out 目錄讀取資料。
    • 如果更新套件中的 epoch 小於系統更新器中的 epoch,則會以 UNSUPPORTED_DOWNGRADE 為原因失敗準備。我們需要為 UNSUPPORTED_DOWNGRADE 建立新的 PrepareFailureReason

安全性

這並非安全防護功能。不過,它可能會與安全性功能互動,以改善開發人員的工作流程。舉例來說,復原保護功能會拒絕啟動 N 以下版本的映像檔。如果我們在發布映像檔版本 N 時增加了紀元,這將可防止開發人員降級無法啟動的版本,因為這些降級作業會在 OTA 備援機制中失敗。

此外,我們選擇將 epoch.json 嵌入系統更新器二進位檔 (而非設定資料),讓 OTA 能夠抵禦設定資料毀損的情況。

隱私權和效能考量

測試

我們可以使用 //src/sys/pkg 中的現有系統更新測試架構,該架構結合了單元和整合測試。

此外,OTA e2e 測試會確保備份不會遞減,且格式有效。例如:

  • 如果 N 版本降低 OTA 備援機制,我們就會在從 N-1 版本改為 N 的 CI 到 OTA 作業中失敗。
  • 如果建構 N 在系統更新器中產生無效的 epoch.json,我們將無法從 CI 進行 OTA 更新。
  • N 建構為 N'

說明文件

我們需要建立文件,說明更新 epoch_history 的政策。

此外,我們還需要修改:

缺點、替代方案和未知事項

實施這項提案的成本為何?

實作這項提案的主要成本是增加平台複雜度,因為我們會在平台中新增另一個版本 ID。

還有哪些策略可以解決同樣的問題?

另一種策略是正式支援所有回溯 OTA。這麼做並不實際,因為如果我們不知道變更內容,就無法編寫能因應未來變更的程式碼。

另一種策略是明確禁止所有回溯 OTA (即使在其他情況下也可能發生)。舉例來說,我們可以自動在每次新建構時提高後盾。我們決定不這麼做,因為在實際情況下,有些開發人員確實會依賴這些舊版 OTA,我們不希望這些開發人員因此無法正常運作。

另一種方法是直接整合 Fuchsia 平台版本資訊 (請參閱 RFC-0002)。不過,這項功能有幾個模糊的問題。舉例來說,是否應禁止 API 級別中的所有回溯 OTA,還是應挑選特定級別?我們會打破誰?由於 Fuchsia 先前曾為系統的不同部分使用不同的版本 ID (例如檔案系統有自己的版本 ID),因此這似乎是更簡單的做法。

既有技術與參考資料

如需瞭解 OTA 的更多資訊,請參閱 Android 說明。

特別銘謝

James Sullivan 協助撰寫動機和墊腳石部分。原始設計文件是由 Zach Kirschenbaum 撰寫,並由 Dan Johnson 審查。