| 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->B、B->C 和 A->C 的 OTA。如果我們將 B 宣告為過渡版本,這會移除 A->C 邊緣,因此 A 升級至 C 的唯一方法是透過 OTA A->B,然後 B->C。在實務上,這項功能有助於處理高風險的遷移作業,並減少需要測試的 OTA 前向更新次數。
OTA 後擋與墊腳石的關係
OTA 後備機制和跳板版本都是基本元素,我們必須安全地遷移 (例如儲存空間格式遷移)。本 RFC 不會詳細說明如何使用 OTA 後備和過渡版本。我們將提供範例,說明如何使用這些基本項目支援安全遷移。
考慮遷移儲存格式。我們可能採取的措施包括:
- 新增對新格式的支援,但暫時不要啟用/遷移。調高 OTA 截止日期。
- 稍待片刻。
- 使用上述任一遷移策略啟用新格式。
如果我們確實遷移了裝置,可以採取以下兩個步驟來啟用清除作業:
- 發布包含 (3) 的過渡版本。
- 移除遷移程式碼和舊版格式的支援。
透過這個過渡版本,我們可以假設裝置已完成含有遷移程式碼的建構作業,因此可以移除舊格式的讀取支援。
在 (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.json。epoch_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 進行更新 - 建構
N至N'。
說明文件
我們需要建立文件,說明更新 epoch_history 的政策。
此外,我們還需要修改:
- 更新套件說明文件。
- OTA 說明文件 (尚未發布至 fuchsia.dev)。
缺點、替代方案和未知事項
實施這項提案的成本為何?
實作這項提案的主要成本是平台複雜度增加,因為我們要在平台中新增另一個版本 ID。
還有哪些策略可以解決相同問題?
另一項策略是正式支援所有向後 OTA。如果不知道日後會有什麼變更,就無法編寫能因應這些變更的程式碼,因此這種做法並不實際。
另一項策略是明確禁止所有向後 OTA (即使是原本可行的 OTA 也一樣)。舉例來說,我們可以在每個新版本中自動調高後擋。我們決定不這麼做,因為實際上,部分開發人員確實會依賴這些舊版 OTA,我們不想影響這些開發人員。
另一種做法是直接整合 Fuchsia 平台版本控管 (請參閱 RFC-0002)。不過,這項做法有幾個模糊不清的問題。舉例來說,是否應禁止所有 API 級別的向後 OTA,還是應挑選特定級別?我們會中斷誰的作業?由於 Fuchsia 系統各部分 (例如檔案系統) 使用不同的版本 ID,因此這似乎是較簡單的選項。
既有技術和參考資料
如要進一步瞭解 OTA,請參閱 Android 說明文件。
特別銘謝
James Sullivan 貢獻了動機和墊腳石部分。Zach Kirschenbaum 撰寫了原始設計文件,並由 Dan Johnson 審查。