RFC-0127:結構化設定 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 新的設定系統,可解決一組常見的元件設定問題。 |
問題 | |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2021-07-08 |
審查日期 (年-月-日) | 2021-09-22 |
摘要
這份 RFC 提出了新的「結構化」設定系統,讓元件開發人員可以輕鬆且一致地使用元件架構解決一組常見的元件設定問題。其目的是補充,而非取代 Fuchsia 上的現有設定機制。
元件開發人員可以在元件資訊清單中宣告元件的設定鍵,然後在每個例項啟動時,元件架構會將設定值傳送至元件。初始設定值是在組合時定義。如果在組合期間允許,系統在元件的父項或 FIDL 介面執行時,也可以設定設定值。
本 RFC 涵蓋動機、預期用途和整體設計。日後的 RFC 將定義實作方式,以及開發人員用來與這項新系統互動的語法。
提振精神
如果軟體可以「設定」,就會更具彈性且可重複使用。也就是說,如果軟體行為的某些層面可以由外部控制,而非由原始碼固定,Fuchsia 可用於大規模正式環境中的各種產品。設定是實現這項彈性,以及隨著時間安全地改進平台的必要條件。
其他大型平台提供基礎架構,可協助開發人員在軟體中加入設定 (例如 Chromium),但 Fuchsia 目前的設定程序為手動作業,因此需要特別處理,才能在執行階段使用設定值,並為不同環境提供這些設定值。
Fuchsia 上最常見的設定工具目前會從 config-data 套件讀取檔案,並從以設定為導向的 FIDL API 讀取資料。這些現有工具至今已用於解決多項重要問題,但並未廣泛或一致地使用。
這份 RFC 提出了新的「結構化」設定系統,讓元件開發人員可以使用元件架構,輕鬆且一致地解決一組常見的元件設定問題。這項機制旨在補充現有的設定機制,而非取代之。
啟用簡單且一致的元件設定可為 Fuchsia 帶來多項好處,例如:
- 平台元件可提供更靈活的支援,以支援更多產品。
- 只要提供替代方案,即可簡化遷移至元件架構第 2 版的程序,以便在啟動子項元件時提供引數。
- 透過功能旗標,您可以更安全地將新平台和產品功能部署至實際環境。
- 您可以降低與開發、測試和維護可設定行為相關的成本。
相關人員
此 RFC 的相關人員為 Fuchsia 工程委員會,以及其範圍擴大的元件平台團隊 (即元件架構和軟體提交),以及負責改善程序的團隊 (PDK 和安全性)。
系統的潛在客戶也很重要,但由於本 RFC 並未提出通用設定系統,因此不應期望能滿足所有潛在客戶的設定需求。
講師:abarth
審查者:geb (元件架構)、wittrock (SWD)、aaronwood (SWD 和 PDK)、ampearce (安全性)
諮詢對象:ddorwin、hjfreyer、ejia、thatguy、shayba、jamesr、ypomortsev、crjohns、surajmalhotra、curtsgalloways、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 並未指定政策。不過,基於安全考量,除非明確要求並經過審查,否則正式版 (例如「user」) 版本很可能不會允許執行階段變異。
開發人員可以在系統執行工程版本時測試功能。系統執行期間可變更哪些設定鍵是政策問題,而本 RFC 並未指定政策。不過,為了簡化開發、測試和偵錯作業,工程師可能會允許在執行階段變異工程中的大部分設定鍵 (例如「eng」) 版本。開發人員可以使用
ffx
為本機裝置啟用這項功能。例如 (使用概念語法):ffx target config set timekeeper.cml enable_frequency=true
。如果政策允許,開發人員也可以啟用這項功能,讓其在裝置電源週期中持續運作。測試可涵蓋「已啟用功能」和「已停用功能」的情況。對於單元和元件層級測試,這項作業涉及手動建構及插入 FIDL 設定結構體;對於整合測試,則涉及在測試中的元件啟動時提供設定,例如使用 Realm Builder。
建構和組合工具可控制平台邊界中的功能。目前透過 DPI 和 SPAC 的努力,正在進行建構和組合工具。這項工作的結果可讓平台維護人員控管每項平台功能是否向產品公開。視政策和功能的風險而定,平台可以控制功能的推出作業,或將功能的推出作業委派給產品。在更複雜的情況下,平台可以在系統執行時啟用功能旗標的變異,甚至在實際環境中也能執行 (例如 「user」) 版本。這可讓產品在個別裝置上啟用這項功能,例如根據實驗推行系統或企業管理主控台設定設定值。
標記狀態會反映在裝置指標中。如果標記狀態可在系統執行期間變異,則該版本的值會納入額外雜湊,可用於在指標分析期間,將已啟用功能的同類群組與已停用功能的同類群組區隔開來。
常見用途:產品/電路板/建構類型的客製化
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 的目的,我們將「設定」視為元件執行個體用來根據啟動時的環境 (例如產品、板卡、建構類型、管道、法規區域或整合測試領域) 調整作業的輸入內容。設定值在元件執行個體的生命週期中保持不變,通常在某些裝置組合中保持不變。設定值通常由開發人員、維護人員或管理員設定,而非使用者。
資料類型
結構化設定適用於每個元件有數量適中且定義良好的設定鍵。以這種方式限制設定的範圍和大小,可鼓勵使用者建立完善記錄、可測試、變動性極低且易於稽核的設定。也能自動組合來自多個來源的設定。這些明確定義的鍵/值組合會在「結構化設定」中建立「結構」。
由於上述「任意大型且複雜的設定資料」限制,我們不打算支援位元組或任意長度的字串。最初支援的資料類型集合將在後續工作中定義,但至少會包含布林值、整數、長度受限的字串,以及這些資料類型的清單 (清單長度受限,且清單中的所有項目都會以原子方式提供)。
雖然未來支援列舉是相當理想的做法,但這項做法較為複雜,因為所有驗證設定值的位置 (無論是在組合期間還是在執行階段) 都必須能夠存取有效的列舉器集合。如果系統能夠在枚舉器名稱和值之間進行轉譯,開發人員工具就會更符合人體工學,但這會增加複雜性。
日後可能會支援可組合清單 (即可由多個設定來源提供項目的清單),但這會大幅增加複雜度。使用原子型別操控設定鍵,代表只需取代其值即可,但可組合清單需要更複雜的作業,例如附加、插入或移除值,或合併清單片段。這些作業需要在組合和執行階段同時支援,並會建立新的失敗模式,例如插入項目失敗,因為這樣做會超過清單的最大長度。系統需要區分兩種清單:項目順序不會影響使用者的清單 (因此應使用某種標準順序執行設定雜湊),以及項目順序會影響使用者的清單 (因此必須明確維持該順序)。
元件架構版本
結構化設定僅支援元件架構 v2 元件。這是一項涉及許多領域的大型專案,直到 2022 年初才會廣泛採用。支援兩種不同的架構會大幅增加範圍,並將結束日期推遲至元件架構 v1 預期淘汰的時間。
設計
總覽
本子節將簡要介紹系統的整體設計。以下各小節將進一步說明本文介紹的每個步驟。
設計摘要如下圖所示:
可設定資料的每個元素都是鍵/值組合。元件作者會在元件資訊清單中宣告元件的設定鍵 (以及選用的預設值),而建構系統和元件架構會負責在啟動時將設定值傳送至元件。
建構和組合程序會產生設定定義檔案,其中包含設定鍵資料類型和名稱,以及設定值檔案,其中包含每個設定鍵的值和可變動性。在初始實作中,這兩個檔案都會與元件放置在同一個套件中 (或為在 pkgfs 可用之前執行的元件提供獨立的啟動檔案系統檔案)。請參閱下方的替代方案 3,瞭解原因和未來方向的討論。
每次啟動元件執行個體時,元件架構會檢查設定定義檔案是否存在。如果有設定定義檔案,元件架構會將設定值檔案中的靜態值,與父項元件執行個體提供的任何值,以及設定覆寫服務提供的任何值結合,並遵守設定值檔案中的可變動性限制。元件架構會在啟動時,使用最適合執行階段的任何技巧,將這些合併值傳遞至新的元件執行個體。
元件架構會公開新的 FIDL 介面,開發人員工具或產品元件可使用此介面查詢設定值,並定義新的覆寫值。下次元件執行個體啟動時,系統會採用使用此介面設定的新值。
設定值的統計資料會納入檢查作業,因此也會納入快照,以利偵錯。透過 Cobalt 為每個元件回報設定值的雜湊,這些值與組合時間不同,因此可獨立評估具有不同設定值的裝置同類群組。
設定定義
元件作者可以在元件資訊清單中定義設定鍵 (每個鍵都會附帶資料類型,並可選擇附帶預設值)。日後的作業將會定義確切的語法,但概念上可能會像這樣:
{
program: {
...
},
config: {
enable_frequency: {
type: "boolean",
default: "false"
},
oscillator_error_std_dev_ppm: {
type: "uint8"
}
},
...
}
在初始實作中,所有鍵都會在每個元件的單一「平面」命名空間中定義,但我們會將設定鍵中合法的字元限制為 [-_a-z0-9]
。如果巢狀或分組設定鍵在未來仍有價值,則可使用以點或斜線分隔的語法支援及參照這些鍵。
部分執行階段 (例如投放和網頁) 會自動產生 CFv2 元件宣告,而非從元件資訊清單產生。這些執行階段一開始不會支援結構化設定。
在資訊清單中加入設定部分,會導致元件建構規則產生程式庫,其中包含該設定的 FIDL 資料表定義,以及在執行階段從執行元件提供給元件的輸入內容填入此資料表所需的程式碼。元件的實作項目就能匯入這個程式庫,並使用表格中的欄位來控制其行為。
元件資訊清單會說明元件的需求,以及元件必須遵守的合約。在本 RFC 之前,*_binary
建構規則不依賴資訊清單的內容,而 fuchsia_component
建構規則則可選擇依賴二進位檔。您必須對建構規則進行部分變更,才能避免循環依附。
在某些情況下,單一二進位檔會由多個元件使用;在這些情況下,您必須進行一些重構,才能使用結構化設定。其中一個選項是透過納入通用 CML 分片,確保所有元件定義相同的設定。另一個做法是將元件合併為單一定義,並使用結構化設定來說明先前使用不同資訊清單表示的行為差異。
建構、組合和發布
建構、組合和發布程序負責為每個可設定元件產生兩個檔案。這兩個檔案會與元件放置在相同的套件中 (日後會擴充支援透過其他套件提供值,請參閱這個替代方案)。
設定定義檔
這個檔案包含每個設定鍵的下列資訊:
- FIDL 欄位編號
- 欄位名稱
- 資料類型
這些資訊全都會在元件資訊清單中提供,因此這個檔案可能會在元件建構程序期間產生。建議您也計算並在設定定義檔案中加入所有資訊的雜湊值,以便做為設定版本 ID 使用。
請注意,我們將設定定義描述為「檔案」,以便簡化討論其用途,但實作可能會在編譯的元件資訊清單中加入這項資訊 (即*.cm
檔案),而非單獨檔案。
設定值檔案
這個檔案包含每個設定鍵的下列資訊:
本 RFC 未定義檔案格式。
在成熟且可擴充的組合系統中,多個不同的角色可能會希望為一或多個鍵指定或限制這項資訊。例如:元件作者、電路板啟動工程師、平台邊界擁有者、產品整合者或安全性審查人員。目前透過 DPI 和 SPAC 平台路線圖項目,正在開發啟用此功能所需的部分工具,而本 RFC 並未指定如何使用這些工具產生設定值檔案。
在此期間,雖然結構化設定只會由少數元件用於少數金鑰,但我們會在原始碼存放區中手動維護這些檔案。如果從樹狀結構建構的產品需要修改平台元件的設定,則會透過取代平台套件中的設定值檔案來執行此操作。
組合程序必須驗證設定值檔案的內容是否與對應的設定定義檔案一致,也就是說,兩者必須包含相同的欄位編號組合,且資料類型一致。
VBMeta
建議您在使用相同映像檔產生的正式版本時,變更設定。舉例來說,您可以使用開發金鑰簽署時啟用偵錯功能,並在正式版簽署時停用。使用相同的映像檔可降低產品和開發人員版本之間出現非預期差異的可能性,並有可能減少我們維護的映像檔數量。
日後,我們打算透過允許在 vbmeta 中覆寫部分設定值,來支援這項功能。VBMeta 是 Fuchsia 驗證開機程序實作項目使用的核心資料結構,其中包含 Fuchsia 版本中所附軟體的中繼資料。由於 vbmeta 已簽署,因此由 vbmeta 覆寫的設定值會由經過驗證的執行作業涵蓋。
當您可以從同一個映像檔建立多個版本,且這些版本可能會顯示有意義的不同和實用的行為時,透過 vbmeta 設定可增加價值。這反過來需要幾個基礎架構元件已與結構化設定整合,因此我們會將由 vbmeta 設定的初始最小範圍排除在外。
元件啟動
每次啟動元件執行個體時,元件管理員都會解析設定定義檔案和設定值檔案,並使用其內容判斷哪些來源可能會提供設定值。下文將詳細說明每個來源。元件管理員會結合來自允許來源的貢獻內容,產生最終的設定值組合。
確定設定值後,元件管理員會將這些值傳遞給執行元件。執行程式會以最常見的方式,將設定值傳遞至新啟動的元件。在許多情況下,我們預期這會將包含句柄的新 procarg 傳遞至包含 FIDL 資料表的 VMO。在某些情況下,您可能需要將設定做為 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 接收更新的選擇加入功能。