RFC-0215:結構化設定上層覆寫值 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 允許父項元件在執行階段向子項提供設定。 |
問題 | |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2023-03-21 |
審查日期 (年-月-日) | 2023-04-26 |
摘要
允許父項元件覆寫子項結構化設定中的值。
提振精神
根據 RFC-0127 中提出的方向,父項元件應能為子項提供設定值。舉例來說,starnix_runner
能夠直接將自身的設定值傳遞至啟動的 starnix_kernel
,而無需建立設定目錄和動態優惠,這對 starnix_runner
來說是一大福音。
父項元件應可在啟動動態子項時傳遞設定值。舉例來說,以 Rust 實作的父項應可編寫以下程式碼:
let overrides = vec![ConfigOverride {
key: Some("parent_provided".into()),
value: Some(ConfigValue::Single(ConfigSingleValue::String(
"foo".to_string(),
))),
..ConfigOverride::EMPTY
}];
connect_to_protocol::<RealmMarker>()
.unwrap()
.create_child(
&mut CollectionRef { name: "...".into() },
Child {
// name, url, startup, ...
config_overrides: Some(overrides),
..Child::EMPTY
},
CreateChildArgs::EMPTY,
)
.await
.unwrap()
.unwrap();
日後,父項元件應可在 CML 中設定靜態子項,並使用產生的程式庫為動態子項,以減少冗長說明,同時確保覆寫的類型正確無誤。
相關人員
講師:jamesr@google.com
審查者:
- geb@google.com (CF)
- ypomortsev@google.com (CF)
- markdittmer@google.com (安全性)
諮詢對象:lindkvist@google.com、hjfreyer@google.com
社會化:在提交為 RFC 之前,我們已在元件架構團隊成員和潛在客戶之間傳閱這項提案。
需求條件
- 一開始,父項可能會為動態子項提供設定值,包括使用 RealmBuilder 例項化的子項。最終應該可以在 CML 中設定靜態子項,包括使用父項設定中的值。
- 元件作者可以不與父項元件協調,即可改進內部/僅限測試/僅限開發人員的設定欄位。
- 父項和子項元件可以獨立更新。子項可以透過與父項元件協調軟性轉場,進而演進設定結構定義。
設計
為了讓這項功能運作,我們需要定義:
可由父項變更的設定欄位
根據第 2 項規定,父項只能覆寫子項元件標示為可變動的設定欄位。
元件作者會在任何可接受覆寫值的設定欄位中,新增 mutable_by
屬性。這個屬性會接受字串清單,以便日後覆寫機制。一開始,系統只會接受 "parent"
字串。CML 範例:
{
// ...
config: {
fields: {
enable_new_feature: {
type: "bool",
mutable_by: [ "parent" ],
},
},
}
}
變動性將會以可能來源的清單形式指定,方便為新覆寫來源擴充此語法,包括開發人員覆寫資料庫,因為該資料庫原本的範圍由 RFC-0127 定義。
我們曾考慮採用另一種方法,但後來放棄了,這個方法就是依變動性將設定欄位分組。
字串索引鍵與偏移/序號
元件管理服務目前會使用編譯設定結構定義中的欄位偏移值,解析已封裝的設定值,這是一種小型最佳化方式,需要元件的資訊清單、設定值和已編譯的二進位檔,才能同意設定結構的確切版面配置。如果值的提供者和已設定的元件是使用不同的 (但相容的) 設定配置結構建構,則無法進行覆寫。
相反地,系統會檢查子項結構定義中的鍵與提供的覆寫值是否相等,藉此解決父項覆寫值,讓子項結構定義中的欄位順序和數量能夠變更,而不需要子項元件指定明確的序號,或父項元件更新其覆寫值。
已遭到拒絕的替代做法是使用整數序數來解決問題,並要求子元件明確選擇序數。
套裝預設
具有可由父項變更的配置欄位的元件,仍必須在其封裝的配置中提供預設/基本值。這樣一來,新增可供父項檢視的欄位就會變成軟性轉換。
未來,我們可能會允許元件要求其父項提供一些設定值。
不允許過度佈建
父項元件提供的設定值檔案,只能包含子項元件設定架構中現有的欄位值,且可由父項元件變更。避免過度配置設定,可讓家長預測行為,而無須手動確認子女是否已依照預期設定。
父項值的優先順序
元件設定「會將元件執行個體的行為調整為執行所在的環境」,這表示最可靠的設定值應為那些對元件執行個體的環境提供最多知識的值。
根據 RFC-0127,元件管理服務會為每個設定欄位解析值,並依照以下順序優先使用各個來源:
- (未來) 來自開發人員覆寫服務的值
- 元件父項的值
- 元件本身套件的值
負責工程版本的元件開發人員可以瞭解系統上執行的所有內容,因此這些覆寫值是最可靠的。家長可以瞭解孩子使用的脈絡。最後,由於元件本身套件中的值只能編碼,讓系統瞭解元件如何封裝,因此所有其他資訊都必須由外部來源提供。
在 Realm.CreateChild
中提供值
我們將擴充 fuchsia.component.decl/Child
,以便在執行階段指定設定覆寫值:
library fuchsia.component.decl;
@available(added=HEAD)
type ConfigOverride = table {
1: key ConfigKey;
2: value ConfigValue;
};
type Child = table {
// ...
@available(added=HEAD)
6: config_overrides vector<ConfigOverride>:MAX;
};
雖然如果我們將這個欄位新增至 fuchsia.component.CreateChildArgs
,父項就能為動態子項設定設定,但這不會提供路徑,可針對 CML 中靜態定義的元件指定父項覆寫值。建議採用的方法可確保元件管理服務日後只會考量單一來源的父項值。
在執行階段提供值的父項元件必須與宣告的設定欄位類型完全相符。不會執行類型推論、轉換或整數升級。
實作
建議的設計已製作出原型。
fuchsia.component.decl
FIDL 程式庫需要能夠存取目前位於 fuchsia.component.config
中的 Value
類型。不過,fuchsia.component.config
目前依附於 fuchsia.component.decl
,因此要使用軟性轉換反轉依附元件關係需要很長的時間。我們會將所有 fuchsia.component.config
移至目前的 API 級別中的 fuchsia.component.decl
,並淘汰 fuchsia.component.config
,最後將其移除。在合併兩個程式庫時,我們會在適當類型中新增 Config*
前置字串,例如 Value
會變成 ConfigValue
。
在我們累積使用這項功能的經驗,且樹狀結構外使用者能夠使用結構化設定之前,這項提案的新定義 FIDL API 介面僅會提供 API 級別 HEAD
。
除了元件宣告的變更之外,您還需要更新 RealmBuilder
用戶端程式庫,以便在 Child
宣告中傳遞設定覆寫值。
成效
這份 RFC 建議元件管理服務應使用字串相等性,將覆寫值與其預期欄位進行比對,這項作業的速度會比目前用於解析已封裝設定值的 O(1) 索引/偏移比較慢。這會為元件啟動作業增加少量運算,但由於元件啟動時間通常以數百毫秒為單位,因此不太可能對運算造成任何可觀察到的影響。舉例來說,在元件啟動時間中,框架額外負擔並未對任何面向使用者的產品品質問題造成重大影響。
人體工學
在這個功能的第一次迭代中,父項元件需要知道要覆寫的設定欄位確切類型,這不符合人體工學,因為我們最終可以製作這項功能。舉例來說,元件作者必須確實比對數字設定欄位的整數寬度和符號。日後,我們可以定義較寬鬆的類型解析規則,支援設定靜態子項,也許還可以從子項的設定結構定義產生程式碼,讓語言編譯器代開發人員檢查欄位名稱和類型。
如果無法為元件的設定介面描述版本序列,結構定義的演進過程就會是手動與社交的過程。我們日後可能會解決這個問題。
部分元件可能會在最後提供多個欄位,這些欄位共用相同的可變動性限制,並且在每個欄位重複相同的屬性,這會造成使用者體驗的負擔。我們目前沒有任何符合此模式的用途,因此用來解決這個問題的語法是已遭拒絕的替代方案,我們日後可能會重新考慮這個方案。
回溯相容性和前向相容性
元件作者修改設定結構定義的變動父項部分時,必須負責協調軟性轉換與其父項元件。本節將說明如何安全地修改設定結構定義。
沒有 mutable_by
屬性的設定欄位不需要特別注意版本管理,因為這些欄位的值只能由元件本身的套件提供。
日後,這些步驟可能會透過以 Fidl 為基礎的版本管理,以更符合人體工學的方式進行調解。
新增可由父項變更的欄位,為現有欄位新增可變更性
您不必特別考量,因為所有設定欄位仍需要在元件專屬的已封裝值檔案中提供基本/預設值。一旦元件的設定結構定義中出現欄位,父項就能為該欄位提供值。
移除可變動的父項設定鍵,移除可變動修飾符
如要從元件的結構定義中安全移除可由父項變更的設定欄位,您必須先使用父項元件,確保這些元件不再傳遞要移除欄位的任何覆寫值。
舉例來說,如要從元件的設定介面中移除 parent_provided
設定欄位:
- 元件作者傳達意圖,以便淘汰並移除
parent_provided
- 父項元件停止指定
parent_provided
的值 - 元件作者從設定結構定義中移除
parent_provided
重新命名可由父項變更的欄位
重新命名欄位等同於同時新增及移除,因此通常不應在單一步驟中執行。
變更可由父項變更的欄位類型
變更欄位類型等同於同時新增及移除欄位,因此通常不應在單一步驟中執行。
變更可由父項變更的欄位限制
增加欄位的 max_len
或 max_size
限制一律安全無虞。
只有在所有父項提供的值都位於新範圍內時,才可安全地減少欄位的 max_len
或 max_size
限制。
安全性考量
scrutiny
工具可以斷言已建構系統映像檔中元件的最終設定。這是重要的安全防護機制,可避免在建構和組合程序中發生意外的錯誤設定。
我們將擴充 scrutiny
,讓它拒絕具有政策強制值且可由父項變更的設定欄位。這可確保安全性至關重要的設定欄位不會受到審查靜態驗證範圍以外的父項元件變異。
隱私權注意事項
父項覆寫可讓元件將執行階段資料做為設定值傳遞。雖然部分元件可能會選擇使用這項功能來傳送使用者資料,但目前沒有任何功能會自動在記錄檔、指標或遙測資料中記錄結構化設定值。實作這項功能不應對隱私權造成任何影響。
測試
config_encoder
程式庫用於在元件管理工具、scrutiny
和相關工具中解析設定。我們會擴充單元測試,確保系統會拒絕不正確佈建的父項覆寫值 (不明的鍵、錯誤的類型、缺少可變動性)。
我們會擴充結構化設定整合測試,確保父項元件可使用 Realm
通訊協定和 RealmBuilder
提供設定。
scrutiny
測試會擴充,以確保它會拒絕在政策檔案中具有靜態宣告值的 mutable-by-parent 欄位。
說明文件
CML 參考說明文件會更新,以符合更新後的設定結構定義語法。
我們會撰寫一份結構化設定結構定義演進指南。這篇文章將介紹新增及移除欄位的最佳做法。其中包含管理軟性轉場的指引。
我們將撰寫新的參考文件,說明結構化設定值來源及其優先順序。
我們會擴充現有的建構映像檔安全性屬性驗證說明文件,說明為何不允許使用父項可變動欄位來設定安全性相關資訊。
未來工作
產生的覆寫繫結
上述提案表示,父項元件的作者必須使用字串鍵和「動態型別」值,才能提供覆寫值。這會導致一些固定格式,並導致父項提供錯誤的鍵/值,或忘記更新產生覆寫值的程式碼路徑,當有可用的新欄位時。由於配置的設定值不正確,系統只會在啟動子項元件時,在執行階段顯示錯誤。
我們最終可透過允許父項設定元件的作者產生「父項覆寫」程式庫,改善開發人員體驗。父項元件的作者可以使用這些方法,減少輸入的字元,並讓編譯器檢查是否正確覆寫子項的設定:
let overrides = FooConfigOverrides {
parent_provided: Some("foo".to_string()),
..FooConfigOverrides::EMPTY
};
connect_to_protocol::<RealmMarker>()
.unwrap()
.create_child(
&mut CollectionRef { name: "...".into() },
Child {
// name, url, startup, ...
config_overrides: overrides.to_values(),
..Child::EMPTY
},
CreateChildArgs::EMPTY,
)
.await
.unwrap()
.unwrap();
在 CML 中設定子項
含有靜態子項的上層元件,最終應能在 CML 中宣告子項時提供設定值。例如:
{
children: [
{
name: "...",
url: "...",
config: {
parent_provided: "foo",
},
},
],
}
我們可能也要提供語法,讓父項將自己的設定值直接轉送至靜態子項。
我們需要決定,當提供的設定值已知時,scrutiny
是否會允許可由父項變更的欄位。
如果我們為 CML 建構這項功能,傳遞文字值時就需要進行設計,以便在 JSON5 對數字的鬆散型別與結構化設定的確切類型之間建立連結。我們需要為所有可能的 JSON5 輸入內容選擇 fuchsia.component.decl.ConfigValue
表示法,並定義規則,讓元件管理服務可將這些值用於可能有較嚴格限制的設定欄位。如果我們等待以 FIDL 為基礎的父項/子項關係表示法,就能避免進行大部分的設計工作,因為 fidlc
可以根據子項的設定結構定義,精確檢查類型。這項設計工作並非必要,因為父項可以將自己的設定值傳遞至子項。
父項必要設定值
部分元件可能會要求其父項提供特定設定值,而無法依賴已封裝的預設值。
這需要允許已封裝的值檔案略過值,並教導各種解析設定的程式庫如何處理這些值。
這項功能並非滿足目前用途的必要條件,但可能會是日後可追求的實用擴充功能。
以 FIDL 為基礎的結構定義和版本管理
元件架構團隊已探索將 FIDL 用於元件資訊清單的做法。如果實作了這個功能,我們就能使用 FIDL 可用性註解來協調設定結構定義的演進。
使用字串鍵來解析已封裝的設定
採用本 RFC 中提出的方法,元件管理服務將會保留兩個用於解析設定值的 ID:
- 已封裝的值會使用編譯設定結構定義中的整數偏移值進行解析
- 系統會使用設定架構中的字串鍵解析父項提供的值
如以下替代做法所述,在父項覆寫值中使用字串鍵有其必要,且使用整數偏移/序號的潛在動機,在解析已封裝值時並不會帶來太多好處。
我們應將已封裝的值移至編碼字串鍵,以減少元件管理服務語意中的碎片化。這項變更對後端使用者來說應該幾乎無感,但會簡化設定解析度的實作方式,並讓日後的偵錯作業更輕鬆。
模糊設定解析
日後,我們會為元件管理服務工具的資訊清單剖析和元件解析作業新增模糊測試工具。發生這種情況時,我們會擴充模糊測試器,納入來自父項元件的設定值,並斷言會尊重可變性修飾符。
缺點、替代方案和未知事項
將具有共用可變動性限制的欄位分組
如果元件含有多個欄位,且這些欄位都具有相同的可變動性限制,我們可能會考慮允許元件作者將設定欄位與共用屬性分組。以下為 (非規範性的) 範例,我們可能會定義多個設定區段:
{
// ...
config: {
// fields which can only be resolved from a component's package
},
parent_config: {
// fields which are mutable by parent
},
}
這項方向可能對人體工學有幫助,但不會受到我們已識別的任何現有用途所驅動。如果我們發現冗長的說明在實際應用中會造成重大負擔,就會重新考慮這項做法。
請注意,這種做法可能會讓元件作者更難定義具有多個可變動性指定符的設定欄位,例如「可由父項和覆寫服務變動」。
覆寫 API 的整數序數
基於歷史原因,封裝的設定值會使用封裝值清單中的偏移值,解析為其欄位。這比檢查字串相等性更有效率,也能減少執行階段開銷。
為了讓父項覆寫功能達到相同的效益,子項元件的作者必須明確選擇其欄位的序數,類似於 FIDL 表格。父項元件作者必須根據這些序數指定覆寫值,或使用產生的程式庫,以便提供人類可讀的名稱。
我們也需要設計機制,引導子項元件作者不要重複使用整數序號,而精心挑選的字串鍵應可降低重複使用時發生錯誤的風險。
歸根究柢,從整數解析鍵所獲得的二進位大小和執行階段額外負擔效益,在我們預期的結構化設定使用規模中微不足道。使用字串鍵可讓我們延遲產生的繫結,並讓子項元件作者不必處理複雜的序號管理作業,同時盡可能降低系統效能成本。