| RFC-0127:結構化設定 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 新的設定系統,可解決一組常見的元件設定問題。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-07-08 |
| 評論日期 (年-月-日) | 2021-09-22 |
摘要
這項 RFC 提案提出新的「結構化」設定系統,可讓元件開發人員使用元件架構,輕鬆且一致地解決一組常見的元件設定問題。這項機制旨在補充 Fuchsia 現有的設定機制,而非取代。
元件開發人員可以在元件資訊清單中宣告元件的設定鍵,然後元件架構會在每個執行個體啟動時,將設定值傳送至元件。初始設定值是在組裝時定義。如果在組裝時允許,系統執行期間,元件的父項或透過 FIDL 介面,也可以設定設定值。
本 RFC 涵蓋動機、預期用途和整體設計。未來的 RFC 將定義開發人員與這個新系統互動時使用的實作和語法。
提振精神
如果軟體可以「設定」,也就是說,如果軟體的行為可以從外部控制,而不是由原始碼固定,那麼軟體就會更具彈性,也更容易重複使用。Fuchsia 適用於大規模正式環境中的各種產品,因此必須進行設定,才能實現這種彈性,並隨著時間安全地發展平台。
其他大型平台提供的基礎架構可協助開發人員在軟體中新增設定 (例如 Chromium),但 Fuchsia 的設定目前是手動程序,需要自訂工作,才能在執行階段使用設定值,並為不同環境提供這些設定值。
目前 Fuchsia 最常見的設定工具是從 config-data 套件讀取檔案,以及從設定導向的 FIDL API 讀取資料。這些現有工具已用於解決多個重要問題,但並未廣泛或一致地使用。
這項 RFC 提案提出新的「結構化」設定系統,可讓元件開發人員使用元件架構,輕鬆且一致地解決一組常見的元件設定問題。這項功能旨在補充現有的設定機制,而非取代。
啟用簡單且一致的元件設定,可為 Fuchsia 帶來多項好處,例如:
- 平台元件可以更靈活,支援更多產品。
- 在啟動子元件時,提供 CFv1 供應引數功能的替代方案,可簡化遷移至元件架構 v2 的程序。
- 使用功能旗標,可以更安全地將新的平台和產品功能部署至正式環境。
- 可降低開發、測試及維護可設定行為的相關成本。
相關人員
這項 RFC 的利害關係人包括 Fuchsia 工程委員會、範圍擴大的元件平台團隊 (即 元件架構和軟體推送),以及負責處理可改善流程的團隊 (PDK 和安全性)。
系統的潛在用戶端也很重要,但由於這項 RFC 並未提出通用設定系統,因此不應預期能滿足所有潛在用戶端的所有設定需求。
Facilitator: abarth
審查者:geb (元件架構)、wittrock (SWD)、aaronwood (SWD 和 PDK)、ampearce (安全性)
諮詢對象:ddorwin、hjfreyer、ejia、thatguy、shayba、jamesr、ypomortsev、crjohns、surajmalhotra、curtisgalloways、adamperry
社交化:這項設計的早期草稿或前述文件,已由元件架構、安全性、軟體推送、Cobalt 和 PDK 團隊審查。與潛在客戶進行多次討論。
用途
常見用途:功能旗標
為已部署的系統新增功能可能會有風險;新的設計和程式碼有時會包含錯誤或無效的假設,直到部署至正式環境後才發現。許多其他平台會使用「功能旗標」來降低這項風險:布林值設定參數,用於控制功能是否啟用。功能旗標有幾項優點:
- 部署新軟體版本時,可以不必啟用新功能;在同一軟體版本中新增的功能,不一定要同時啟用。
- 在啟用新功能之前,可以徹底測試新功能是否正常運作。
- 您可以透過發布管道、百分比推出,或兩者並用,逐步在各裝置上啟用各項功能。
- 如有需要,您可以安全快速地停用各項功能,不必復原至先前的軟體版本。
我們以最近一項功能為例,說明功能旗標的優點: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 未指定政策。不過,基於安全考量,除非明確要求並經過審查,否則正式版 (例如「使用者」) 版本很可能不允許任何執行階段變動。
開發人員可以在系統執行工程版本時測試這項功能。系統執行時可變更哪些設定金鑰,屬於政策問題,本 RFC 未指定政策。不過,為了簡化開發、測試和偵錯作業,工程 (例如「eng」) 版本中的大多數設定鍵可能允許執行階段變動。開發人員可以使用
ffx為本機裝置啟用這項功能。例如 (使用概念語法):ffx target config set timekeeper.cml enable_frequency=true。如果政策允許,開發人員也可以啟用這項功能,讓功能在裝置電源週期中持續運作。測試可涵蓋「功能啟用」和「功能停用」兩種情況。如果是單元和元件層級測試,這涉及手動建構及插入 FIDL 設定 struct;如果是整合測試,則涉及在測試中的元件啟動時提供設定,例如使用 Realm Builder。
建構和組裝工具可控制這項功能,做為平台邊界的一部分。建構和組裝工具目前正在透過 DPI 和 SPAC 進行。這項工作的成果可讓平台維護人員控管是否要向產品公開各項平台功能。視政策和功能風險而定,平台可以控管功能的推出,或將功能的推出委派給產品。在更複雜的情況下,平台可在系統執行時 (即使在正式版 (例如「使用者」) 版本中) 啟用功能旗標。這樣一來,產品就能在個別裝置上啟用這項功能,例如根據實驗推出系統或企業管理控制台設定配置值。
標記狀態會反映在裝置指標中。如果版本在系統執行時可變更標記狀態,其值會包含在額外的雜湊中,用於在指標分析期間區分啟用和停用功能的同類群組。
常見用途:產品/主機板/建構類型客製化
Fuchsia 是一般用途作業系統,可用於各種產品和裝置類別。這表示平台元件有時需要根據其運作的產品或主機板特性,調整其行為。
我們再舉一個 Timekeeper 的例子:世界標準時間維護演算法需要知道裝置振盪器的準確度,才能瞭解誤差範圍的增長,並對連續樣本進行加權。有些振盪器比其他振盪器更準確 (也更昂貴!),但目前沒有簡單的方法可以表示板式變化,因此振盪器誤差目前硬式編碼為常數:
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 年初才能廣泛採用。支援兩種不同的架構會大幅增加範圍,並將結束日期延後到元件架構第 1 版預計淘汰日期之後。
設計
總覽
本子節將簡要介紹系統的整體設計。以下子節將詳細說明此處介紹的每個步驟。
設計摘要如下圖所示:
可設定資料的每個元素都是鍵/值組合。元件作者會在元件資訊清單中宣告元件的設定鍵 (以及選用的預設值),建構系統和元件架構則負責在啟動時將設定值傳送至元件。
建構和組裝程序會產生設定定義檔案,其中包含設定索引鍵資料類型和名稱,以及設定值檔案,其中包含每個設定索引鍵的值和可變動性。在初始實作中,這兩個檔案都會放在與元件相同的套件中 (或在 pkgfs 可用之前執行的元件,則會放在個別的啟動檔案系統檔案中)。請參閱下方的替代方案 3,瞭解基本原理和未來方向。
每次啟動元件執行個體時,元件架構都會檢查是否存在設定定義檔案。如果設定定義檔案存在,元件架構會將設定值檔案中的靜態值,與上層元件執行個體提供的任何值,以及設定覆寫服務提供的任何值合併,並遵循設定值檔案中的可變動性限制。元件架構會在啟動時,使用最適合執行階段的技術,將這些合併值傳遞至新的元件執行個體。
元件架構會公開新的 FIDL 介面,開發人員工具或產品元件可使用該介面查詢設定值,並定義新的覆寫值。下次元件執行個體啟動時,系統會採用透過這個介面設定的新值。
設定值的統計資料會納入檢查,因此也會納入快照,以利偵錯。元件會透過 Cobalt 回報與組裝時間不同的設定值雜湊,以便獨立評估具有不同設定值的裝置群組。
設定定義
元件作者可以在元件資訊清單中定義設定鍵 (每個鍵都有資料類型,且可選擇是否設定預設值)。日後的工作會定義確切語法,但概念上可能如下所示:
{
program: {
...
},
config: {
enable_frequency: {
type: "boolean",
default: "false"
},
oscillator_error_std_dev_ppm: {
type: "uint8"
}
},
...
}
在初始實作中,所有金鑰都是在每個元件的單一「平面」命名空間中定義,但我們將設定金鑰中的合法字元限制為 [-_a-z0-9]。如果日後認為巢狀或分組設定鍵有價值,則可使用點分隔或斜線分隔語法支援及參照。
部分執行階段 (例如 cast 和 web) 會自動產生 CFv2 ComponentDecls,而不是從元件資訊清單產生。這些執行階段一開始不支援結構化設定。
如果在資訊清單中加入設定區段,元件建構規則會產生一個程式庫,其中包含該設定的 FIDL 資料表定義,以及在執行階段根據執行元件提供給元件的輸入內容,填入這個資料表所需的程式碼。接著,元件的實作項目可以匯入這個程式庫,並使用資料表中的欄位來控制其行為。
元件資訊清單會說明元件的需求,以及元件必須履行的合約。在這項 RFC 之前,*_binary 建構規則不
依賴資訊清單的內容,而 fuchsia_component 建構
規則則對二進位檔有選用依附元件。您必須對建構規則進行一些變更,以免發生循環相依。
在某些情況下,多個元件會使用單一二進位檔;這些情況需要進行一些重構,才能使用結構化設定。其中一個方法是加入通用 CML shard,確保所有元件都定義相同的設定。另一個做法是將元件合併為單一定義,並使用結構化設定,描述先前使用不同資訊清單表示的行為差異。
建構、組裝和發布
建構、組裝和發布程序會為每個可設定元件產生兩個檔案。這兩個檔案都放在與元件相同的套件中 (日後將擴充為支援透過不同套件提供值,請參閱這個替代方案)。
設定定義檔案
這個檔案包含每個設定金鑰的下列資訊:
- 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 定義,但我們認為在 eng 版本中,幾乎不需要限制透過 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 平台維護者) 發布及簽署元件,並由另一個機構 (例如產品整合商) 提供設定。
我們預期未來每個套件都會包含發布機構設定的預設設定值,並宣告哪些值可由其他機構發布的不同套件覆寫 (例如,平台元件可以選擇哪些設定「旋鈕」可供產品整合商存取)。日後的工作將定義這個「不同套件」的性質,選項包括中繼套件、Sidecar 套件或包裝函式套件。
依元件網址和路徑名稱覆寫索引設定
這項 RFC 會依元件的執行個體 ID 為設定覆寫建立索引。執行個體 ID 用於為元件架構中的其他永久資源建立索引,例如獨立永久儲存空間,因此很適合用於為設定覆寫建立索引。
不過,目前元件執行個體 ID 是在建構時透過索引檔案手動指派。這表示執行個體 ID 和結構化設定覆寫,不適用於元件集合中的元件執行個體,或由應用程式更新 (而非基本映像檔) 導入的元件執行個體。
我們考慮過的替代方案是,依元件網址和路徑名稱建立設定覆寫索引。這樣做可避免執行個體 ID 的限制,但元件網址和路徑名稱可能會隨著系統重構而變更,因此這個替代方案除了會造成元件架構永久資源處理不一致,還會引發新的穩定性問題。
我們傾向在日後的 RFC 中,透過變更執行個體 ID 的設計,解決執行個體 ID 的限制。
支援在元件層級覆寫設定
這項 RFC 會依元件的執行個體 ID 為設定覆寫建立索引,也就是說,元件的每個執行個體都必須分別覆寫。
未來,除了元件執行個體層級外,可能也需要在元件層級支援覆寫。如果元件例項化多次,或事先不知道元件例項,這項功能就特別實用。
目前沒有明確定義的標準和穩定 ID,可用於元件層級覆寫。由於我們尚未發現元件層級覆寫的具體需求,因此暫不加入這項功能。
前述替代方案中提及的執行個體 ID 設計變更,除了元件執行個體 ID 外,也可能提供穩定的元件 ID (例如,執行個體 ID 可以是元件的自我認證 ID,並透過某些 quirks 資料庫與執行個體路徑名稱的某些函式結合,以處理拓撲變更)。這樣一來,日後新增元件層級覆寫設定時會更簡單。
使用 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 接收更新。