RFC-0071:OTA 停止

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

防止裝置跨版本界線進行 OTA 降級。

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

摘要

本文提出一項計畫,防止裝置跨版本界線安裝無線更新 (OTA)。

提振精神

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

如果系統更新堆疊中含有同等版本號碼,使用者就不會嘗試透過 OTA 降級至不支援裝置所含檔案系統映像檔的驅動程式庫版本。換句話說,這項功能可讓我們在裝置故障前,讓「向後 OTA」作業失敗。

這麼做的好處如下:

  • 對於會保存狀態的應用程式而言,這項功能非常實用。舉例來說,應用程式維護的 SQLite 資料庫結構定義可能會隨時間變更。
  • 具體來說,這對儲存空間團隊非常實用,因為他們過去必須投入大量時間,才能解決最終是由跨版本界線的 OTA 倒轉所造成的問題。
  • 這會強調 Fuchsia 不支援向後 OTA;這些 OTA 嚴格來說是盡力而為。

請注意,這項提案不會變更支援的 OTA 序列。只是明確支援這項功能。OTA 後備機制的主要目的是防止開發人員裝置進入無效狀態。對於正式版裝置,發布管理應主要強制執行 no-backwards-OTAs 不變量。

如果沒有這項提案,開發人員嘗試啟動裝置時,在不相容的界線進行反向 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 後備機制和跳板版本都是基本元素,我們必須安全地遷移 (例如儲存空間格式遷移)。本 RFC 不會詳細說明如何使用 OTA 後備和過渡版本。我們將提供範例,說明如何使用這些基本項目支援安全遷移。

考慮遷移儲存格式。我們可能採取的措施包括:

  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 檔案會增加複雜度,但這個方法有下列優點:

  • 並提供所有版本升級變更的記錄 (強制說明文件!)
  • 如果兩位使用者嘗試因不同原因調升紀元,就會產生合併衝突。

實作

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

如要套用變更,請按照下列步驟操作:

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

安全性

這不是安全防護功能。不過,這項功能可能會與安全防護功能互動,以改善開發人員的工作流程。舉例來說,假設復原保護機制拒絕啟動版本低於 N 的映像檔,如果我們在推出圖片版本 N 時增加紀元,開發人員就無法降級至無法啟動的版本,因為這些降級作業會在 OTA 後備機制中失敗。

此外,我們選擇將 epoch.json 嵌入系統更新程式二進位檔 (而非 config-data),讓 OTA 能夠抵禦 config-data 損毀。

隱私權和效能注意事項

不適用

測試

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

此外,OTA 端對端測試會確保後備值不會遞減,且格式有效。例如:

  • 如果建構版本 N 降低了 OTA 後備機制,我們就會在 CI 中從建構版本 N-1 升級至 N 時失敗。
  • 如果建構版本 N 在系統更新程式中產生無效的 epoch.json,我們將無法在 CI 中從 OTA 進行更新
  • 建構 NN'

說明文件

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

此外,我們還需要修改:

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

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

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

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

另一項策略是正式支援所有向後 OTA。如果不知道日後會有什麼變更,就無法編寫能因應這些變更的程式碼,因此這種做法並不實際。

另一項策略是明確禁止所有向後 OTA (即使是原本可行的 OTA 也一樣)。舉例來說,我們可以在每個新版本中自動調高後擋。我們決定不這麼做,因為實際上,部分開發人員確實會依賴這些舊版 OTA,我們不想影響這些開發人員。

另一種做法是直接整合 Fuchsia 平台版本控管 (請參閱 RFC-0002)。不過,這項做法有幾個模糊不清的問題。舉例來說,是否應禁止所有 API 級別的向後 OTA,還是應挑選特定級別?我們會中斷誰的作業?由於 Fuchsia 系統各部分 (例如檔案系統) 使用不同的版本 ID,因此這似乎是較簡單的選項。

既有技術和參考資料

如要進一步瞭解 OTA,請參閱 Android 說明文件。

特別銘謝

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