| RFC-0127:結構化設定 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 新的設定系統,可解決一組常見的元件設定問題。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-07-08 |
| 審查日期 (年-月-日) | 2021-09-22 |
摘要
這項 RFC 提議採用新的「結構化」設定系統,讓元件開發人員使用元件架構,輕鬆一致地解決一組常見的元件設定問題。這項機制旨在補充而非取代 Fuchsia 現有的設定機制。
元件開發人員可以在元件資訊清單中宣告元件的設定鍵,然後元件架構會在每個例項啟動時,將設定值傳送至元件。初始設定值是在組裝時定義。如果允許在組裝時設定,元件的父項或透過 FIDL 介面,也可以在系統執行時設定設定值。
這份 RFC 涵蓋動機、預期用途和整體設計。日後的 RFC 將定義實作方式,以及開發人員與這個新系統互動時使用的語法。
提振精神
如果軟體可以「設定」,也就是說,軟體行為的各個層面可從外部控制,而非由原始碼固定,那麼軟體會更具彈性,也更容易重複使用。Fuchsia 適用於大規模生產環境中的各種產品;設定對於啟用這項彈性,以及隨著時間安全地演進平台而言,都至關重要。
其他大型平台提供的基礎架構可協助開發人員在軟體 (例如 Chromium) 中新增設定,但目前在 Fuchsia 上設定時,需要手動作業,因此需要進行客製化工作,才能在執行階段使用設定值,並為不同環境提供這些設定值。
目前,Fuchsia 最常見的設定工具是從 config-data 套件讀取檔案,以及從以設定為導向的 FIDL API 讀取資料。這些現有工具至今已用於解決多項重要問題,但使用範圍不廣,且不夠一致。
這項 RFC 提議採用新的「結構化」設定系統,讓元件開發人員使用元件架構,輕鬆一致地解決一組常見的元件設定問題。這項功能是為了補充現有的設定機制,而非取代。
啟用簡單且一致的元件設定可為 Fuchsia 帶來多項優點,例如:
- 平台元件可以變得更靈活,支援更多產品。
- 遷移至元件架構 v2 時,如果能取代 CFv1 在啟動子項元件時提供引數的功能,就能簡化遷移作業。
- 使用功能標記,可更安全地將新平台和產品功能部署至正式環境。
- 可降低開發、測試及維護可設定行為的相關成本。
利害關係人
這項 RFC 的利害關係人包括 Fuchsia 工程委員會,以及範圍擴大的元件平台團隊 (即 元件架構和軟體交付),以及可改善流程的團隊 (PDK 和安全性)。
系統的潛在用戶也很重要,但由於本 RFC 並未提議通用設定系統,因此不應期望能滿足所有潛在用戶的所有設定需求。
主持人:abarth
審查人員:geb (元件架構)、 wittrock (SWD)、aaronwood (SWD 和 PDK)、ampearce (安全性)
諮詢對象:ddorwin、hjfreyer、ejia、thatguy、shayba、jamesr、ypomortsev、 crjohns、surajmalhotra、curtisgalloways、adamperry
社交化:這個設計或前述文件的早期草案,已在元件架構、安全性、軟體交付、Cobalt 和 PDK 團隊中審查。並與潛在客戶進行多次額外討論。
用途
常見用途:功能旗標
為已部署的系統新增功能可能會有風險;新設計和新程式碼有時會包含錯誤或無效的假設,這些問題要到部署至正式環境後才會發現。許多其他平台會使用「功能標記」來降低這項風險,也就是用來控管功能是否啟用的布林值設定參數。功能旗標有幾項優點:
- 新軟體版本的部署作業可與新功能的啟用作業分開進行;在同一軟體版本中新增的功能不必同時啟用。
- 啟用新功能前,可以先徹底測試功能是否正常運作。
- 您可以透過發布管道、百分比推出或兩者搭配使用,在裝置上逐步啟用各項功能。
- 如有需要,可以安全快速地停用各項功能,而且停用功能時不需要復原至先前的軟體版本。
舉例來說,在 Timekeeper 中導入頻率估算功能時,如果使用功能標記,就能帶來許多好處。Timekeeper在提交的 CL 中,這項功能會立即且永久啟用,適用於所有產品的所有版本。
estimator/mod.rs (沒有結構化設定)
match self.frequency_estimator.update(&sample) {
// use resulting frequency update
...
}
如果使用結構化設定,我們就能在 Timekeeper 的元件資訊清單中宣告功能標記,然後根據這個標記限制頻率估算工具的使用:
timekeeper.cml
{
...
config: {
enable_frequency: {
type: "boolean",
default: "false"
}
},
...
}
main.rs
import config_timekeeper as config;
...
let config: config::Struct = config::parse();
// Pass config through to each new Estimator
estimator/mod.rs
if self.config.enable_frequency {
match self.frequency_estimator.update(&sample) {
// Use resulting frequency update
...
}
}
在本範例中,在資訊清單中宣告設定區段,會導致建構系統產生程式庫,其中包含該設定的 FIDL 結構體定義,以及在執行階段從執行元件提供給元件的輸入內容填入這個結構體的必要程式碼。接著,元件的實作項目可以匯入這個程式庫,並使用結構體中的欄位來控管其行為。
請注意,元件開發人員可以透過大約 10 行程式碼,將新功能設為旗標。他們會獲得什麼來交換這 10 行程式碼?
這項功能最初會在所有地區停用。由於元件作者提供「false」的預設值,因此在組裝程序明確設定其他值之前,旗標會保持關閉狀態。
系統在正式版中執行時,一開始無法啟用這項功能。系統執行時可變動哪些設定鍵是政策問題,而本 RFC 並未指定政策。不過,基於安全考量,除非明確要求並經過審查,否則正式版 (例如「user」) 發布內容很可能不允許任何執行階段變動。
開發人員可以在系統執行工程版本時測試這項功能。系統執行時可變動哪些設定鍵是政策問題,而本 RFC 並未指定政策。不過,為了簡化開發、測試和偵錯作業,工程團隊可能會允許大多數設定鍵在執行階段發生變異 (例如 「eng」) 版本。 開發人員可以使用
ffx為本機裝置啟用這項功能。 舉例來說 (使用名義語法):ffx target config set timekeeper.cml enable_frequency=true。如果政策允許,開發人員也可以啟用這項功能,讓功能在裝置電源週期中保持啟用。測試可涵蓋「已啟用功能」和「已停用功能」案例。 如果是單元和元件層級的測試,這項作業需要手動建構及注入 FIDL 設定結構體;如果是整合測試,則需要提供設定,因為受測元件會啟動,例如使用 Realm Builder。
建構和組裝工具可做為平台界線的一部分,控管這項功能。目前正在透過 DPI 和 SPAC 努力建構和組裝工具。這項工作的成果可讓平台維護人員控管是否要向產品公開各項平台功能。視政策和功能風險而定,平台可能會控管功能推出作業,或將功能推出作業委派給產品。在更複雜的情況下,平台可能會在系統執行期間 (即使是在正式環境中) 啟用功能旗標的突變 (例如「使用者」版本。這樣一來,產品就能在個別裝置上啟用這項功能,例如根據實驗推出系統或企業管理主控台設定設定值。
裝置指標會反映標記狀態。如果系統執行時可變更發布版本的旗標狀態,系統會將該值納入額外的雜湊,用於在指標分析期間,將啟用功能的同類群組與停用功能的同類群組分開。
常見用途:調整產品/主機板/建構類型
Fuchsia 是一般用途的作業系統,適用於各種產品和裝置類別。也就是說,平台元件有時需要根據運作的產品或主機板特性調整行為。
我們以 Timekeeper 為例:UTC 維護演算法需要瞭解裝置振盪器的準確度,才能掌握誤差範圍的成長情況,並為後續樣本加權。有些振盪器的準確度遠高於其他振盪器 (價格也較高!),但目前沒有簡單的方法可表示板載變異,因此振盪器錯誤目前會硬式編碼為常數:
estimator/mod.rs (沒有結構化設定)
const OSCILLATOR_ERROR_STD_DEV_PPM: u64 = 15;
kalman_filter.rs (沒有結構化設定)
static ref OSCILLATOR_ERROR_VARIANCE: f64 =
(OSCILLATOR_ERROR_STD_DEV_PPM as f64 / MILLION as f64).powi(2);
...
self.covariance_00 += monotonic_step.powf(2.0) * *OSCILLATOR_ERROR_VARIANCE;
透過結構化設定,我們可以在 Timekeeper 的資訊清單中宣告整數設定鍵,然後使用產品或主機板提供的值:
timekeeper.cml
{
...
config: {
oscillator_error_std_dev_ppm: {
type: "uint8"
}
},
...
}
main.rs
import config_timekeeper as config;
...
let config: config::Table = config::parse();
// Pass config through to each new KalmanFilter
estimator/mod.rs
// Delete hard-coded constant.
kalman_filter.rs
let oscillator_error_variance: f64 =
(config.oscillator_error_std_dev_ppm as f64 / MILLION as f64).powi(2);
...
self.covariance_00 += monotonic_step.powf(2.0) * oscillator_error_variance;
如同第一個用途,元件開發人員可以導入這個可自訂的參數,程式碼約 10 行,並接收相同的實用屬性。在這個用途中,資訊清單不會提供預設值 (因為元件作者不知道振盪器可能的使用情況是好是壞),因此如果沒有其他輸入提供值,組裝程序就會失敗,並顯示資訊豐富的錯誤訊息。
其他使用情況
前幾節討論了我們認為結構化設定的兩種常見用途,但這個系統也可用於處理其他簡單的設定用途。例如:
禁止測試的旗標。某些元件自然會展現行為,導致整合測試難以進行 (例如時間系統在時間來源故障後,會有 5 分鐘的冷卻時間)。整合或端對端測試期間,您可以使用僅供測試的標記來禁止這些行為。
元件執行個體建立時間設定。有時必須等到要建立元件執行個體時,才能判斷元件的適當設定。您可以定義設定鍵,讓父項在建立新的元件執行個體時,提供設定值。這項功能可取代 CFv1 中的啟動引數,並支援為不同角色量身打造元件的多個執行個體 (例如,為每個有效帳戶啟動一個 AccountHandler 執行個體)。
簡單的 A/B 測試。有時,您可能需要同時測試兩個以上的選項,並在不同裝置上進行實驗,才能找出最佳設計。如果產品可存取伺服器端實驗系統,就能使用該系統為一或多個元件設定值,進而執行 A/B 測試。
哲學
結構化設定的設計理念有四個共同原則:
- 簡單。系統必須簡單易用,且設計方式簡單易懂,方便分析。簡潔的設計有助於推廣採用,並符合「可靠」和「安全」的理念。
- 穩定可靠:可靠性可簡化開發和偵錯作業,並讓系統用於對 Fuchsia 裝置運作至關重要的元件。
- 安全。安全性直接符合 Fuchsia 的平台目標,並允許系統用於對 Fuchsia 裝置安全至關重要的元件。
- 可測試。設定會為元件新增輸入內容,因此可能會產生新問題。開發人員必須能夠完整測試元件對這些新輸入內容的回應。
範圍
結構化設定並非適用於所有設定問題的通用解決方案。以 Fuchsia 為基礎建構的產品,其配置需求範圍廣泛,且往往相互衝突。如果系統試圖滿足所有需求,不是會變得非常複雜 (因此違反簡單哲學,並威脅其他三項哲學),就是會變得簡單且過於一般 (因此無法保證資料的存在、穩定性、意義和可稽核性,而這些是可靠和安全哲學的必要條件)。
結構化設定的設計宗旨,是輕鬆解決一組常見用途的問題。元件可能會使用一般檔案式和 API 式解決方案,解決其他設定問題。具體來說,結構化設定並非用來解決下列問題:
任意大型且複雜的設定資料。一般工具難以稽核大型複雜資料,也無法選擇性地限制存取權,這與安全防護理念相衝突。這類資料通常需要額外的網域專屬解讀和驗證,因此會產生設定系統無法察覺的故障模式。最後,從多個來源合併龐大複雜的資料並不容易,這會限制組裝工具的實用性,以及在系統執行時覆寫設定的能力。
- 如果元件需要大型且複雜的設定資料,則應改為從檔案讀取這類資料。
經常變更的設定資料。如果資料經常變更,就必須多次傳送至元件,而不是只在元件啟動時傳送一次。這會導入新的失敗模式,並在元件上產生額外的複雜度,與可靠且簡單的哲學相衝突。測試難度也大幅提升。請注意,經常變更的資料不符合下方的「設定」定義。
- 需要頻繁變更設定資料的元件,應透過 FIDL 通訊協定接收這項資料。
由其他元件設定的設定資料 (父項元件和管理員元件除外)。結構化設定支援父項元件設定,可為建立的元件設定,並支援管理元件設定裝置的所有可變更設定。結構化設定無法提供存取控制項,讓任意元件在特定元件中設定設定子集。
- 系統中需要由其他任意元件設定的設定資料集,應透過 FIDL 通訊協定接收這項資料,並使用服務路由限制存取權。
由使用者控管的設定資料。如果設定是由使用者 (而非開發人員或管理員) 控制,則需要使用者介面。這個使用者介面未通過「其他元件設定的設定資料集」測試,也可能未通過「經常變更的設定資料」測試。
- 由使用者控制的設定應納入
fuchsia.settings,或使用類似方法,透過 FIDL API 分隔前端和後端元件。
- 由使用者控制的設定應納入
「設定」的意義
元件會使用各種不同的輸入內容。這些輸入內容大多可能會改變元件的行為,但只有部分輸入內容應視為「元件設定」,而非更一般的「系統狀態」或「輸入資料」。
就本 RFC 而言,「設定」是指元件執行個體用來調整作業的輸入內容,以配合啟動元件的環境 (例如產品、主機板、建構類型、管道、法規區域或整合測試領域)。設定值在元件執行個體的生命週期內保持不變,且通常在某些裝置上保持不變。設定值通常是由開發人員、維護人員或管理員設定,而不是由使用者設定。
資料類型
結構化設定適用於每個元件有適量且明確定義的設定鍵。以這種方式限制設定的範圍和大小,可鼓勵您建立文件齊全、可測試、變動性極低且容易稽核的設定。此外,這項功能還可自動組合多個來源的設定。這些定義明確的鍵/值組合會在「結構化設定」中建立「結構」。
由於上述「任意大小和複雜的設定資料」限制,我們不打算支援位元組或任意長度的字串。我們會在後續工作中定義一開始支援的資料類型集,但至少會包含布林值、整數、有長度限制的字串,以及這些資料類型的清單 (清單長度有上限,且清單中的所有項目都是以原子方式提供)。
未來非常需要支援列舉,但由於驗證設定值的所有位置 (組裝期間和執行階段) 都必須存取一組有效的列舉值,因此這項功能較為複雜。如果系統可以在列舉值名稱和值之間進行轉換,開發人員工具會更符合人體工學,但這會增加複雜度。
日後或許會支援可組合的清單 (也就是可透過多個設定來源提供項目的清單),但這會大幅增加複雜度。使用原子型別操控設定鍵時,只要取代其值即可,但可組合清單需要更複雜的作業,例如附加、插入或移除值,或是合併清單片段。這些作業在組裝和執行階段都需要支援,而且會產生新的失敗模式,例如因插入項目會超過清單長度上限而導致插入失敗。系統必須區分清單,判斷項目順序是否會影響消費者 (因此應使用某個標準順序執行設定雜湊),以及項目順序是否重要 (因此設定操作必須明確維持該順序)。
元件架構版本
結構化設定僅支援元件架構第 2 版元件。這項大型專案涵蓋許多領域,預計 2022 年初才會準備就緒,供大眾採用。支援兩種不同的架構會大幅增加範圍,並將結束日期延後至元件架構 v1 預計淘汰日期之後。
設計
總覽
本小節將簡要介紹系統的整體設計。以下各小節將詳細說明這裡介紹的每個步驟。
設計摘要如下圖所示:
可設定資料的每個元素都是鍵/值組合。元件作者會在元件資訊清單中,為元件宣告設定鍵 (和選用的預設值),而建構系統和元件架構會共同負責在啟動時,將設定值傳遞至元件。
建構和組裝程序會產生設定定義檔,其中包含設定鍵資料型別和名稱,以及設定值檔案,其中包含每個設定鍵的值和可變性。在初始實作中,這兩個檔案都放在與元件相同的套件中 (或放在元件的個別啟動檔案系統檔案中,這些元件會在 pkgfs 可用前執行)。如要瞭解基本原理和未來發展方向的討論內容,請參閱下方的替代方案 3。
每次啟動元件執行個體時,元件架構都會檢查設定定義檔案是否存在。如果存在設定定義檔案,元件架構會將設定值檔案中的靜態值,與父項元件例項提供的任何值,以及設定覆寫服務提供的任何值合併,並遵守設定值檔案中的可變動性限制。啟動時,元件架構會使用最適合執行階段的技術,將這些合併值傳遞至新的元件執行個體。
元件架構會公開新的 FIDL 介面,開發人員工具或產品元件可使用這個介面查詢設定值,以及定義新的覆寫項目。下次啟動元件執行個體時,系統會採用透過這個介面設定的新值。
檢查作業會納入設定值的統計資料,因此快照也會納入這些資料,有助於偵錯。系統會透過 Cobalt 針對每個元件回報與組裝時間不同的設定值雜湊,以便獨立評估設定值不同的裝置群組。
設定定義
元件作者可以在元件資訊清單中定義設定鍵 (每個鍵都有資料類型,且可選擇性地設定預設值)。確切的語法將在日後定義,但概念上可能如下所示:
{
program: {
...
},
config: {
enable_frequency: {
type: "boolean",
default: "false"
},
oscillator_error_std_dev_ppm: {
type: "uint8"
}
},
...
}
在初始實作中,所有鍵都會在每個元件的單一「扁平」命名空間中定義,但我們會將設定鍵中的合法字元限制為 [-_a-z0-9]。如果日後認為巢狀或分組設定鍵很有價值,系統可能會支援這些鍵,並使用以半形句號或斜線分隔的語法參照這些鍵。
部分執行階段 (例如 Cast 和網頁) 會自動產生 CFv2 ComponentDecl,而非透過元件資訊清單產生。這些執行階段一開始不會支援結構化設定。
在資訊清單中加入設定區段,會導致元件建構規則產生程式庫,其中包含該設定的 FIDL 表格定義,以及在執行階段從執行元件提供給元件的輸入內容填入這個表格所需的程式碼。接著,元件的實作項目可以匯入這個程式庫,並使用表格中的欄位控制其行為。
元件資訊清單會說明元件的需求,以及元件必須滿足的合約。在此 RFC 之前,*_binary 建構規則不依附於資訊清單內容,而 fuchsia_component 建構規則則可選擇依附於二進位檔。為避免循環依附元件,必須對建構規則進行部分變更。
在某些情況下,多個元件會使用單一二進位檔;在這些情況下,必須進行一些重構,才能使用結構化設定。其中一個方法是納入通用的 CML 分片,確保所有元件定義相同的設定。另一種做法是將元件合併為單一定義,並使用結構化設定來描述行為差異,這些差異先前是使用不同的資訊清單表示。
建構、組裝及發布
建構、組裝和發布程序會負責為每個可設定的元件產生兩個檔案。這兩個檔案會放在與元件相同的套件中 (日後會擴充支援透過不同套件提供值,請參閱這個替代方案)。
設定定義檔
這個檔案包含每個設定鍵的下列資訊:
- FIDL 欄位號碼
- 欄位名稱
- 資料類型
這項資訊全都會顯示在元件資訊清單中,因此這個檔案可能會在元件建構程序中產生。建議您也一併計算並納入設定定義檔中所有資訊的雜湊,做為設定版本 ID。
請注意,為簡化使用方式的討論,我們將設定定義描述為「檔案」,但實作方式可能是在已編譯的元件資訊清單 (即 *.cm 檔案),而不是另存為個別檔案。
設定值檔案
這個檔案包含每個設定鍵的下列資訊:
這個 RFC 並未定義檔案格式。
在成熟且可擴充的組裝系統中,多個不同的參與者可能會希望為一或多個鍵指定或限制這項資訊。例如:元件作者、電路板啟動工程師、平台邊界擁有者、產品整合人員或安全審查員。啟用這項功能所需的某些工具目前正在透過 DPI 和 SPAC 平台路線圖項目進行開發,而這份 RFC 並未指定如何使用這些工具產生設定值檔案。
在此期間,由於只有少數元件會使用結構化設定,且只會使用少數金鑰,我們會手動維護來源程式碼存放區中的這些檔案。如果以樹狀結構建構的產品需要修改平台元件的設定,可以替換平台套件中的設定值檔案。
組裝程序必須驗證設定值檔案的內容是否與對應的設定定義檔案一致,也就是說,兩者包含的欄位編號組合相同,且資料類型一致。
VBMeta
建議您針對從相同映像檔產生的正式版本,採用不同的設定。舉例來說,使用開發金鑰簽署時可以啟用偵錯功能,但使用正式版金鑰簽署時則停用。使用相同圖片可降低正式版和開發版之間出現非預期差異的可能性,並減少我們維護的圖片數量。
我們打算在未來支援這項功能,允許在 vbmeta 中覆寫部分設定值。VBMeta 是 Fuchsia 驗證開機程序實作使用的中央資料結構,包含 Fuchsia 版本中軟體的中繼資料。由於 vbmeta 已簽署,因此 vbmeta 覆寫的設定值會受到驗證執行作業的保護。
如果可從同一張映像檔建立多個版本,且這些版本可展現有意義的不同實用行為,則透過 vbmeta 進行設定會增加價值。這會要求多個基礎架構元件已與結構化設定整合,因此我們從初始最小範圍中排除 vbmeta 的設定。
元件開始
每次啟動元件執行個體時,元件管理員都會解析設定定義檔和設定值檔,並使用這些檔案的內容,判斷多個來源中,哪些來源可提供設定值。下文將詳細說明各個來源。元件管理工具會合併允許來源的貢獻內容,產生最終的設定值。
決定設定值後,元件管理員會將這些值傳遞至執行元件。執行器會以最符合執行階段慣例的方式,將設定值傳遞至新啟動的元件。在許多情況下,我們預期這會傳遞新的 procarg,其中包含 VMO 的控制代碼,而 VMO 則包含 FIDL 表格。在某些情況下,可能需要將設定做為 key=value 命令行引數傳遞。
元件可以信任架構,因為架構一律會在啟動時,為資訊清單中宣告的每個設定鍵提供設定值,如果沒有,元件應會因嚴重錯誤而失敗。元件絕不應定義內部預設值,以因應缺少設定的情況,否則可能會導致執行階段錯誤,進而改變原本要在組裝時修正的行為。
使用發行版本中的值
最簡單的情況是使用設定值檔案中提供的值。如果設定值檔案指出元件的任何設定鍵都無法由 ChildDecl 變更,或無法由覆寫變更,則一律會是這種情況,且元件管理員不需要查詢下文討論的設定覆寫服務。
流程如下圖所示:
使用 ChildDecl 中的值
我們擴充 ChildDecl,納入設定鍵/值配對向量,有效取代 CFv1 在啟動新元件執行個體時提供指令列引數的功能。ChildDecl 可能由父項提供,方法是將新例項新增至元件集合、由測試使用領域建構工具建構測試環境,或由父項元件資訊清單的作者提供。
如果 ChildDecl 中有設定,元件管理員會執行下列動作:
- 驗證設定定義檔中是否有 ChildDecl 鍵,以及資料型別是否正確。
- 驗證 ChildDecl 鍵是否可由設定值檔案中的 ChildDecl 變更。
- 使用 ChildDecl 值填入提供的鍵。
- 使用設定值檔案填入其餘鍵。
如果找不到任何鍵、包含錯誤的資料類型,或無法由 ChildDecl 變更,元件管理員就會記錄資訊豐富的錯誤,並傳回失敗。
這個流程是由父項元件呼叫 CreateChild 所啟動,如下圖所示:
使用覆寫服務的值
如果設定值檔案指出一或多個設定鍵「可透過覆寫變更」,元件管理員會向設定覆寫服務發出 FIDL 要求,以取得覆寫值。這項要求包含新元件執行個體的元件執行個體 ID,以及設定定義檔案的控制代碼。回覆包含一組 (可能為空白) 要套用的覆寫設定值。
設定覆寫服務的典型實作方式是新的「設定覆寫管理員」元件,但設定覆寫服務會透過元件拓撲 (類似於儲存空間功能,並以字串做為參數) 路由為能力,因此拓撲的不同部分可能會使用由覆寫服務 API 的不同實作方式提供的設定覆寫。
設定覆寫管理工具會維護「覆寫」設定鍵/值組合的資料庫,可透過 FIDL 編輯,詳情如下。這個資料庫中的每個項目都是在元件執行個體層級定義,並以元件執行個體 ID 做為索引 (如需其他理由,請參閱這個替代方案)。每個覆寫項目都會儲存在磁碟上,以便在電源週期之間持續存在,或保留在記憶體中,直到目前的電源週期結束為止。建立項目時會指定持續性。
收到設定覆寫要求後,設定覆寫管理員會執行下列操作:
- 檢查覆寫資料庫中是否有相符的項目。
- 確認設定定義檔中是否有覆寫的鍵,以及資料型別是否正確。
- 傳回相符的鍵/值組合。
如果找不到金鑰或資料類型不正確 (設定覆寫後下載新元件版本時,可能會發生這些情況),設定覆寫管理員會記錄資訊錯誤並刪除資料庫項目。
收到設定覆寫回應時,元件管理員會執行下列動作:
- 驗證設定值檔案中的覆寫是否可變更覆寫的鍵。
- 使用覆寫的值填入覆寫的鍵。
- 使用設定值檔案填入其餘鍵。
如果元件管理員未從設定覆寫服務收到有效回應,元件啟動就會失敗。
如果設定覆寫管理員實作設定覆寫服務,則流程如下圖所示:
請注意,設定鍵會以字串形式儲存在覆寫資料庫中。隨著元件演進,設定欄位組合可能會經常變更,但只要鍵名和資料型別不變,設定覆寫就會維持有效。為進行最佳化,資料庫中也可以快取上次看到的設定版本 ID 和欄位編號。
值選取摘要
將這些流程整合在一起後,元件管理工具會為每個設定鍵選取值,如下所示:
- 如果金鑰可透過覆寫變更,且設定覆寫服務傳回相符的覆寫,請使用這個值。
- 否則,如果鍵可由 ChildDecl 變動,且 ChildDecl 中提供值,請使用這個值。
- 否則,請使用設定值檔案中的值。
設定 FIDL 介面和覆寫資料庫
設定覆寫管理工具會公開兩項 FIDL 服務,可用於與覆寫資料庫互動:
- 可執行下列動作的服務:
- 讀取所有設定覆寫。
- 建立及刪除儲存在記憶體中的設定覆寫,但這些覆寫不會在設定覆寫管理員重新啟動後保留。
- 可執行下列動作的服務:
- 讀取所有設定覆寫
- 刪除所有設定覆寫
- 建立儲存在記憶體中的設定覆寫,但不會在設定覆寫管理工具重新啟動後保留
- 建立儲存在磁碟上的設定覆寫,並在設定覆寫管理員重新啟動後保留
第一個服務無法對設定進行長期變更,但可能適用於自動化端對端測試。這兩項服務都屬於敏感服務,因此使用權限會列入允許清單。
我們將推出使用第二項服務的 ffx 外掛程式,讓開發人員查詢及修改測試裝置上的設定。在特定版本中,可透過 FIDL 編輯的設定鍵集是政策問題,並非由本 RFC 定義,但我們認為在工程建構中,幾乎不需要限制透過 FIDL 編輯設定。
即使系統在建立時會根據設定定義檔案驗證覆寫資料庫中的項目,但隨著元件移除或升級至含有不同設定鍵的新版本,項目可能會隨著時間變得無效。我們採用多種垃圾收集措施來解決這個問題:
- 覆寫資料庫中的每個項目都可能設有到期時間,到期後就會遭到刪除。在實際運作系統中,我們將考慮強制設定有效期限。
- FIDL 服務提供多種方法,可輕鬆刪除多餘項目,包括刪除元件執行個體的所有項目,以及刪除整個資料庫。
- 在日後的工作中,我們會調查在套件移除或升級時,接收軟體交付通知的情形,以便刪除或重新驗證對應的覆寫項目。
診斷
瞭解元件使用的設定,對偵錯問題非常重要。將指標與執行不同設定的裝置群組分開,有助於評估部分推出和 A/B 研究。
大多數裝置的大多數設定鍵都會使用組裝期間設定的值。 對於這些項目,只要知道發布版本 (或應用程式可更新時的套件版本),就能推斷設定值。每次啟動元件執行個體時,元件管理工具都會計算兩個雜湊值:一個是「ChildDecl 設定雜湊值」,用於所有由 ChildDecl 設定的設定鍵和值;另一個是「覆寫設定雜湊值」,用於所有由覆寫設定的設定鍵和值。如果沒有設定任何欄位,對應的雜湊值會是零。
如果使用的不同設定數量適中,這些設定雜湊就足以識別每個同類群組。如果可能出現大量設定值 (例如開發人員在測試期間設定任意網址),設定雜湊可能不足以判斷設定值,但仍會指出執行設定與組件不同的裝置,以及執行設定與同類裝置不同的裝置。
記錄
元件管理員會記錄元件設定的每次計算統計資料,但不會記錄原始設定值。設定覆寫管理員會記錄所有 FIDL 要求,以修改覆寫資料庫。
檢查與快照
元件管理員的檢查資料包含每個元件執行個體的設定統計資料,有助於偵錯。包括從各個來源設定的設定值數量,以及上述設定雜湊。由於快照包含檢查資料,因此所有執行中元件例項的設定雜湊都會納入快照。
如果設定鍵對元件的運作和偵錯作業很重要,元件可以選擇將這些設定值納入自己的檢查資料。
鈷豔藍
元件管理工具會在啟動時 (透過執行元件) 將這兩個設定雜湊傳送至元件執行個體。我們擴充 fuchsia.metrics.MetricEventLoggerFactory,以便在建立新的 MetricEventLogger 時接受這些雜湊。
Cobalt 會搭配現有的 SystemProfile 欄位使用這些設定雜湊,定義元件執行的環境,以便分別分析不同設定的裝置指標。系統仍會套用標準門檻,因此只有在一定數量的裝置共用相同設定時,指標才會顯示。
實作
在開發結構化設定期間,我們會選取少數「搶先存取」元件做為用戶端,同時演進語法和工具。
導入作業將分為三個階段,逐步提供更多能力:
- 第 1 階段:靜態價值
- 這個階段結束後,您就能建立設定定義 (可能使用非最終的語法和工具)、將設定值放在套件中,並在啟動時將這些值傳遞至元件執行個體。
- 這個階段提供基本方法,讓搶先體驗元件的行為在不同產品或建構類型之間有所差異。
- 第 2 階段:父項值
- 在這個階段之後,您還可以限制封裝設定值的可變動性、在 ChildDecl 中指定設定值,以及在啟動時將這些值傳遞至元件執行個體。設定定義應透過元件資訊清單,並使用接近最終的語法。
- 這個階段可解除設定整合測試的封鎖,以及需要父項元件為子項提供設定的用途。
- 第 3 階段:覆寫值
- 在這個階段之後,您也可以透過 FIDL 介面或使用該介面的 ffx 外掛程式,設定及讀取設定覆寫,在啟動時將這些值傳送至元件執行個體,並在 Cobalt 中依設定隔離指標。
- 這個階段會完成本 RFC 中定義的工作,並解除本機開發人員測試、端對端測試和後續產品整合的封鎖。
效能
這項 RFC 導入了新元件,會產生 (適中) CPU、記憶體和儲存空間成本。這些都會隨著使用結構化設定的元件數量和設定鍵數量而擴展。
計算設定值會導致啟動元件時出現短暫延遲,因為這些元件的設定可透過額外的 FIDL 呼叫和額外檔案讀取作業覆寫。我們會監控這項費用,並視需要最佳化設定覆寫管理工具的實作方式。
安全性
許多元件可能會使用結構化設定,控管其行為的許多不同層面。攻擊者如果能修改設定,可能會以各種方式危害裝置安全。例如:
- 啟用偵錯輸出,以洩漏使用者資訊。
- 將網路要求重新導向至攻擊者控制的伺服器。
- 啟用實驗功能,並利用實作中的安全漏洞。
這項設計包含多項功能,可防範這類攻擊:
- 可設定的資料範圍有限,且每個元素都有明確定義,因此更容易稽核。
- 簽署發布版本時,系統會為每個設定鍵定義值,這些值會受到驗證執行作業的保護。
- 系統執行時修改設定鍵的能力,是在簽署版本時設定,每個鍵和每個突變機制分別設定。這些可變性都受到經過驗證的執行作業涵蓋。
- 可變動性和預設值是在元件層級定義,而非元件執行個體層級。只有在為設定鍵設定「可變動 (透過 ChildDecl)」或「可變動 (透過覆寫)」時,才能建立具有不同設定的元件新例項。
- 變更暫時性和永久性設定的功能會以個別的 FIDL 服務公開。
- 變更設定的 FIDL 服務由允許清單控管。
- 設定資料是由現有且經過充分審查的 FIDL 繫結剖析,而非應用程式專屬邏輯。
- 設定覆寫管理員將成為有利可圖的目標,因此我們會要求對其導入作業進行安全審查。
隱私權
結構化設定並非用於儲存使用者設定 (這些設定有不同的穩定性、持續性、存取權和時間需求),因此設定值絕不應包含使用者產生的資料。開發人員指南會清楚說明這項規定。
父項元件可能會將 PII 設定傳遞至動態建立的子項 (例如新元件執行個體應使用的硬體 UID 或網路位址),這可能很有用。如果未妥善處理,這項資訊可能會透過 ChildDecl 設定雜湊值洩漏到記錄或指標中。詳細設計時會處理這個問題,但有幾個選項可用 (例如,可在設定定義中標示敏感欄位,然後在納入雜湊之前先加鹽)。
測試
測試多個設定值的能力對於驗證正確性非常重要。這項設計可在所有階段測試不同的設定值:
- 單元測試和元件測試可能會手動建構 FIDL 設定結構體 (即元件執行元件在正常運作下提供的結構體),並將此結構體傳遞至會耗用設定的方法。
- 建構測試領域時,整合測試可能會透過領域建構工具 (使用
ChildDecl) 提供每個元件執行個體的設定。 - 端對端測試可能會使用設定 FIDL 介面,從主機裝置設定額外設定。可能需要額外作業,才能暫停正常啟動序列,避免元件啟動時發生競爭情況。
- 手動測試可能會使用
ffx指令,透過設定 FIDL 介面輕鬆設定其他設定。 - 日後會考慮自動對元件的設定進行 模糊測試。
您可以透過多種方式,避免在密封整合測試中對設定覆寫服務產生隱含依附元件。舉例來說,整合測試套件可使用不允許覆寫的設定值檔案建構,而測試領域的建構作業可避免將設定覆寫服務路徑導向。
結構化設定的實作本身會使用標準最佳做法進行測試,包括單元測試,以及元件管理員 - 設定覆寫管理員 - 執行元件互動的整合測試。
說明文件
這項 RFC 獲得核准後,我們會在 /docs/concepts發布文件,說明 Fuchsia 提供的設定機制及其關係。
待語法穩定後,且結構化設定實作準備好更廣泛採用時,我們就會發布開發人員指南和參考文件。
開發人員開始使用結構化設定並發現適用模式後,我們會編寫最佳做法和建議樣式的說明文件。
考慮的替代方案
在設定覆寫管理工具中實作設定合併
這項 RFC 的先前修訂版本將合併不同設定資料集的邏輯,放在元件覆寫管理員 (當時稱為設定管理員) 內,而非元件管理員內。
這種設計會縮小元件管理員的範圍,但元件管理員和設定管理員的範圍定義會較不清楚:
- 元件管理工具需要充分瞭解設定,才能知道何時要叫用設定管理工具,但瞭解程度不足以合併值。
- 除了維護資料庫之外,設定管理員還負責部分業務邏輯,向元件呈現設定。
此外,設計也會增加需要 FIDL 呼叫的情況。
在元件管理工具中實作設定覆寫資料庫
這項 RFC 將維護設定覆寫資料庫的責任,交給新的元件:設定覆寫管理員。另一種做法是在元件管理工具中執行這項功能。
這個替代方案會消除 FIDL 呼叫和某些失敗模式,但會增加元件管理工具的複雜度,而且元件管理工具必須首次保存自己的資料,而不是單純為其他元件分配儲存空間。這種儲存空間使用方式會引發其他安全性問題,且需要在 CF 中建立新的基礎架構。
透過中央套件提供設定
這項 RFC 會將元件的設定值放在元件的套件中。另一種做法是將所有元件的所有設定都放在單一設定套件中,類似於 CFv1 中的 config-data 套件設計。
相較於集中式方法,我們偏好去中心化方法的主要原因有兩個:
- 日後 Fuchsia 需要執行的元件,可能不是基本映像檔已知的元件 (例如應用程式更新的結果)。這些不明元件的設定無法在中央套件中發布,因此需要不同的解決方案。
- 以原子方式提供二進位檔及其設定,可讓我們對兩者的一致性做出更強烈的聲明,特別是當套件可能獨立於基本映像檔更新時。導致元件可用但設定無法使用,或元件與設定不相容的失敗模式較少。
如 RFC 主體所述,我們目前採用分散式方法,將設定值放在與其適用的元件相同的套件中。也就是說,在組裝時變更元件的設定,會導致其套件的根雜湊值變更。從長遠來看,這並不可取,因為這不支援一個機構 (例如 Fuchsia 平台維護人員) 發布及簽署元件,以及另一個機構 (例如產品整合人員) 提供設定。
我們預期日後每個套件都會包含發布機構設定的預設設定值,並聲明可透過其他機構發布的套件覆寫哪些值子集 (例如,平台元件可選擇哪些設定「旋鈕」可供產品整合人員存取)。日後將定義這個「不同套件」的性質,選項包括中繼套件、附屬套件或包裝函式庫。
依元件網址和路徑名稱覆寫索引設定
這項 RFC 會依元件的例項 ID 覆寫索引設定。執行個體 ID 用於為元件架構中的其他永久性資源建立索引,例如獨立的永久性儲存空間,因此是為設定覆寫建立索引的顯而易見選擇。
不過,目前元件執行個體 ID 是在建構時透過索引檔案手動指派。也就是說,元件集合內的元件執行個體,或因應用程式更新而導入的元件執行個體 (而非基礎映像檔),都無法使用執行個體 ID,因此無法覆寫結構化設定。
我們也考慮過依元件網址和路徑名稱,覆寫索引設定。這樣做可以避免執行個體 ID 的限制,但由於系統經過重構,元件網址和路徑名稱可能會變更,因此這個替代方案除了會造成元件架構持續性資源的處理方式不一致,還會引發新的穩定性問題。
我們偏好在未來的 RFC 中,透過變更執行個體 ID 的設計,解決執行個體 ID 的限制。
支援在元件層級覆寫設定
這項 RFC 會依據元件的例項 ID 編排設定覆寫的索引,也就是說,每個元件例項都必須個別覆寫。
日後,除了元件執行個體層級,可能也需要支援元件層級的覆寫。如果元件會多次例項化,或元件例項事先未知,這項功能就特別實用。
目前沒有明確定義的標準和穩定 ID,可用於元件層級的覆寫。由於我們尚未發現元件層級覆寫的具體需求,因此會延後加入這項功能。
先前替代方案中參照的例項 ID 設計變更,除了元件執行個體 ID 之外,也可能提供穩定的元件 ID (例如,例項 ID 可能是元件的自我認證 ID,並與例項的別名函式結合,透過一些奇特資料庫處理拓撲變更)。這樣一來,日後就能輕鬆新增元件層級的覆寫。
使用 FIDL 定義設定鍵
這項設計會使用 JSON 在元件資訊清單中定義設定鍵。其中部分資訊會用於建構 FIDL 表格。另一種做法是在 .fidl 檔案中定義設定鍵。
首先,我們注意到 FIDL 工具鍊刻意由前端和後端組成,並以 IR 分隔,因此您可以使用 FIDL 技術,不必以 FIDL 語言定義輸入內容。
決定使用 .cml 而非 .fidl,主要是考量開發人員體驗:
- 開發人員會在元件資訊清單中向架構說明元件需求,而定義設定鍵則符合這項定義。維護單一檔案比新增檔案更省事。
- 可用於定義設定的語法和資料型別,是完整 FIDL 語言的一小部分 (請參閱範圍)。如果要求使用 FIDL 語法,但只支援部分語法,可能會造成混淆和挫敗感。
- 設定需要 FIDL 語言無法表示的資訊 (目前為資料表欄位的預設值,未來可能會有其他限制)。這項資訊可以儲存在自訂屬性中,但這會與 FIDL 語言的其餘部分不一致,並讓開發人員感到困惑。
如果 JSON 設定定義使用 FIDL 語言中的概念,我們會採用一致的語法。舉例來說,資料型別名稱會保持一致。
cmc 已從 JSON 元件資訊清單建構 FIDL 表格,因此從資訊清單剖析設定所需的額外工作量不大。
支援元件之間的設定轉送
元件架構中的許多資源都支援從一個元件例項到另一個元件例項的路徑,例如通訊協定、目錄和儲存功能。支援元件間的設定路徑是很自然的事,這樣一來,子項就能使用父項的某些設定值。
為避免產生新的版本控管問題,這個初始設計不支援設定路徑。如果我們支援在一個元件中定義設定,並在不同時間點封裝的另一個元件中使用該設定 (因為這些元件是在不同存放區中定義,或因為這些元件並未以單一形式傳送至裝置),我們就無法再保證編譯至元件的設定定義,與用於設定值的定義相符。我們會在這兩個元件之間導入新的 ABI,但由於這個介面會以 PDK 而非 IDK 表示,因此我們無法使用 RFC-0002 中定義的程序來管理版本相容性。
隨著 PDK 和樹狀結構外的組裝作業持續進行,我們可能會進一步討論元件介面和版本控管,並延後元件間的設定路徑,直到這些作業更加成熟為止。在此期間,如有需要,組裝工具可用於在多個套件中提供一致的設定值。
我們支援在建立時動態設定子項元件的父項,藉此推出更有限制的跨元件相容性問題版本。我們認為這些情況近期不太可能造成問題,因為這項可變動性必須選擇啟用,且設定欄位通常會由子項明確設計,供父項使用。
支援可重複使用程式庫的透明設定。
許多元件都是使用可重複使用的程式庫建構而成,這些程式庫支援某種形式的設定。如本 RFC 所定義的結構化設定可用於提供這項設定,但只能手動操作;使用程式庫的每個元件都必須在自己的資訊清單中宣告相符的設定金鑰,然後在初始化時將設定值傳送至程式庫。執行器也有類似情況,執行元件可能會直接控制元件所用程式庫中可設定的行為。
另一種做法是支援程式庫的「透明」設定,讓程式庫可宣告設定鍵,並在執行階段直接取用設定值。元件只需要宣告程式庫的使用情形,即可讓設定供應商為該元件中的程式庫執行個體設定設定值。
如同元件之間的路由 (如上所述),這會涉及不同軟體構件之間更廣泛的設定鍵協議,例如樹狀結構外的元件使用平台程式庫時。這可能更適合正式的設定定義,可為設定版本之間的前後相容性提供更強大的保證。程式庫的透明設定也會引發疑問:如何將多組設定值傳送至元件。
雖然從長遠來看,程式庫的透明設定是理想功能,但我們會先延後這項工作,等到 RFC 中定義的簡單且「私密」設定系統運作後,再進行這項工作。
支援在元件啟動後更新設定
這個設計只會在啟動元件執行個體時提供元件設定。如果元件啟動後修改設定,則必須等到下次啟動,設定才會傳送至元件。另一種做法是透過 FIDL 定期將設定傳送至元件。
決定著重於元件啟動,符合我們對「設定」的定義,也就是與元件生命週期繫結,同時簡化元件作者的實作:
- 只接收一次設定的元件執行個體可以在元件啟動期間,初始化所有設定驅動的資源。必須定期接收新設定的元件也需要定期分配或收集資源,因為設定變更會導致這種情況。
- 必須定期接收新設定的元件需要定期或非同步處理,否則可能不需要。
- 必須定期接收新設定的元件也必須透過診斷連線記錄設定的任何變更,例如建立新的
MetricEventLogger。 - 必須定期接收新設定的元件有其他必須處理的故障模式,例如終止 FIDL 連線和逾時。
目前,啟動後需要更新設定的情況非常有限:在元件啟動後,透過版本或 ChildDecl 提供的設定值就無法變更。FIDL 介面可能會在啟動後進行變更,但一開始只會用於開發人員工具,這些工具可輕鬆提供方法,在變更元件設定後自動重新啟動元件。
未來,部分產品專屬元件可能會偏好實作上述複雜度,而非重新啟動。如果是,我們會考慮讓元件選擇透過 FIDL 接收更新。