RFC-0215:結構化設定父項覆寫

RFC-0215:結構化設定父項覆寫
狀態已接受
區域
  • 元件架構
說明

允許父項元件在執行階段為子項提供設定。

問題
  • 96254
變更
作者
審查人員
提交日期 (年/月)2023-03-21
審查日期 (年/月)2023-04-26

摘要

允許父項元件覆寫其子項結構中的值。

提振精神

配合 RFC-0127 中的提議方向,父項元件應具備向子項提供設定值的權限。舉例來說,starnix_runner 可藉由直接將自己的設定值傳遞至啟動的 starnix_kernel,而不必建立設定目錄和動態優惠。

父項元件在啟動動態子項時應能傳遞設定值。舉例來說,在 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 之前,會在元件架構團隊成員和潛在客戶之間流通。

相關規定

  1. 父項一開始可能會為動態子項提供設定值,包括使用 RealmBuilder 例項化的設定值。您最終應該可以在 CML 中設定靜態子項,包括使用父項設定中的值。
  2. 元件作者可以改進內部/僅限測試/開發人員專用的設定欄位,而不必與父項元件協調。
  3. 父項和子項元件可以獨立更新。子項可以透過父項元件協調進行軟轉換,藉此改進設定結構定義。

設計

為了讓這項功能順利運作,我們必須定義:

各父項可變動的設定欄位

根據要求 (2),父項只能覆寫由子項元件標示為可變動的設定欄位。

元件作者會在任何設定欄位中新增 mutable_by 屬性,該屬性應允許接收覆寫值。這個屬性可接受字串清單,以因應未來的覆寫機制。最初可接受的字串為 "parent"。CML 範例:

{
    // ...
    config: {
        fields: {
            enable_new_feature: {
                type: "bool",
                mutable_by: [ "parent" ],
            },
        },
    }
}

系統會把可變動性指定為可能來源的清單,以便為新的覆寫來源擴充這個語法,包括開發人員覆寫資料庫 (如原先限定 RFC-0127 的範圍)。

日後我們可能採用的遭拒替代方案,便是依可變動性將設定欄位分組

字串鍵與偏移/普通

元件管理服務目前會使用已編譯設定結構定義中的欄位偏移值來解析封裝設定值,這種小型最佳化作業需要元件的資訊清單、設定值和已編譯的二進位檔,才能達成設定結構定義的確切版面配置。如果值提供者和已設定的元件分別以不同 (但相容) 的設定結構定義建構,則無法進行覆寫。

系統會改為檢查子項結構定義中鍵的字串相等性,並藉由檢查子項結構定義中的鍵與所提供覆寫值之間的字串相等情形,解析父項覆寫值,讓子項結構定義中的順序和欄位數量可以變更,而無須要求子項元件指定明確序數或父項元件來更新覆寫設定。

此方法的「拒絕的替代方案」是使用整數正規進行解析度,並要求子項元件明確選擇一般項目。

套件預設值

具有「可變動設定」欄位的元件仍需要在封裝設定中提供預設/基礎值。這會使新增父項可看見的新欄位進行軟轉換。

在「未來」中,我們可能會允許元件要求父項提供一些設定值。

沒有超額佈建

由父項提供的設定檔值檔案只能包含在子項元件設定結構定義中存在且可由父項變更的欄位值。避免設定超額佈建,即可為父項提供可預測的行為,而無需手動驗證子項的設定符合預期。

父項值的優先順序

元件設定「根據執行元件的環境調整元件執行個體的行為,意味著最具公信力的設定值應該是對元件執行個體情境最瞭解的編碼。

根據 RFC-0127,元件管理服務會解析每個設定欄位的值,建議按照以下順序排列每個來源:

  1. (未來) 來自開發人員覆寫服務的值
  2. 來自元件父項的值
  3. 從元件本身的套件

進行工程版本的元件開發人員可以瞭解系統中執行的所有動作,進而覆寫最權威的項目。父項可以瞭解提供給子項的背景資訊。最後,由於元件本身套件中的值只能對元件封裝方式的理解進行編碼,因此所有其他資訊都必須從外部來源提供。

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.configValue 類型。不過,fuchsia.component.config 目前依附於 fuchsia.component.decl,因此在軟轉換中反轉依附元件關係需要很長的時間。而是在目前的 API 級別將所有 fuchsia.component.config 移至 fuchsia.component.decl,淘汰並最終移除 fuchsia.component.config。合併兩個程式庫時,我們會在適當類型中加入 Config* 前置字串,例如 Value 會變成 ConfigValue

為這項提案新定義的 FIDL API 介面只能在 API 級別 HEAD 中使用,直到我們獲得此功能的經驗,以及樹狀結構外使用者能夠使用結構化設定為止。

除了變更元件宣告外,RealmBuilder 用戶端程式庫也需要更新,才能使用 Child 屬性傳遞設定覆寫值。

效能

此 RFC 提議讓元件管理服務使用字串相等來比對覆寫與指定欄位,這和目前用於解析封裝設定值的 O(1) 索引/偏移比較作業速度較慢。這會讓元件啟動作業需要少量的運算作業,但由於元件的開始時間通常在今日數百毫秒,因此不太可能造成任何可觀測的影響。就結構定義而言,元件啟動時間的架構負擔尚未成為任何使用者產品品質問題的重要考量。

人體工學

在這項功能第一次疊代時,父項元件需要知道要覆寫的設定欄位確切類型 (不是符合人體工學,因為我們最終可以提供這項功能)。例如,元件作者需要與數值設定欄位的確切整數寬度和正負號相符。我們日後可以定義排程類型解析規則來支援設定靜態子項,也可能會透過子項的設定結構定義產生程式碼,讓語言編譯器代表開發人員檢查欄位名稱和類型。

如果無法描述元件的設定介面的版本序列,結構定義發展就會需要人工作業和社交程序。這或許可以日後處理

部分元件可能有多個欄位具有相同的可變動限制,而為每個欄位重複相同的屬性,進而收取人體工學稅金。我們目前沒有任何用途符合此模式,因此專門協助解決問題的語法屬於「已拒絕的替代方案」,因此我們可能會選擇在日後重新檢視。

向前與向前相容性

元件作者在修改設定結構定義的「可變動」部分時,必須負責與其父項元件進行軟轉換。本節說明修改設定結構定義的安全演進程序。

沒有 mutable_by 屬性的設定欄位不需要特別注意版本管理,因為這些欄位的值只能透過元件本身的套件提供。

未來這些步驟可能會更符合人體工學的以 FIDL 為基礎的版本管理

新增由父項變更的可變動欄位,並為現有欄位新增可變動性

您不需要特別考量,因為所有設定欄位仍需要在元件專屬的封裝值檔案中納入基礎/預設值。只要欄位出現在元件的設定結構定義中,父項就可以提供該欄位的值。

移除按父項變更的設定鍵,移除可變動性修飾詞

從元件的結構定義中,安全移除「按父項變更」的設定欄位需要先與父項元件合作,以確保這些元件不再用於為即將移除的欄位傳遞任何覆寫設定。

舉例來說,如要從元件的設定介面移除 parent_provided 設定欄位:

  1. 元件作者傳達意圖,以便淘汰並移除 parent_provided
  2. 父項元件停止指定 parent_provided 的值
  3. 元件作者從設定結構定義中移除 parent_provided

重新命名「可變動」的欄位

重新命名欄位等同於同時新增和移除作業,因此通常不建議在單一步驟中執行。

變更可變動欄位的類型

變更欄位類型等同於同時新增和移除作業,因此通常不建議在單一步驟中執行。

變更「可變動」欄位的限制

增加欄位的 max_lenmax_size 限制一律安全。

只有在所有父項提供的值都位於新範圍內時,才能減少欄位的 max_lenmax_size 限制。

安全性考量

scrutiny 工具可針對建構的系統映像檔中元件的最終設定做出斷言。這是一項重要的安全機制,可在建構和組裝程序期間避免意外設定錯誤。

我們將擴充 scrutiny,使其拒絕具有政策強制執行值且可由父項變更的設定欄位。這可以確保在審查靜態驗證範圍外的父項元件,絕不會變更重要的安全性設定欄位。

隱私權注意事項

父項覆寫值可讓元件將執行階段資料做為設定值傳遞。雖然部分元件可能會選擇使用這項功能傳送使用者資料,但目前無法使用功能自動記錄記錄檔、指標或遙測中的結構化設定值。實作這項功能應該不會對隱私權造成任何影響。

測試

config_encoder 程式庫可用於解析元件管理員、scrutiny 和相關工具中的設定。其單元測試將擴大,確保佈建錯誤的父項覆寫值 (未知金鑰、錯誤類型、缺少可變動性) 會遭到拒絕。

我們將擴充結構化設定整合測試,確保父項元件可使用 Realm 通訊協定和 RealmBuilder 提供設定。

我們會展開 scrutiny 測試,確保會拒絕父項的可變動欄位,而這些欄位在政策檔案中具有靜態宣告的值。

說明文件

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。如果實作這個 API,我們或許就能使用 FIDL 可用性註解來協調設定結構定義演進。

使用字串金鑰解析封裝設定

採用此 RFC 中建議的方法,將使元件管理服務留下兩個用於解析設定值的 ID:

  1. 系統會使用編譯設定結構定義中的整數偏移解析封裝值
  2. 系統會使用設定結構定義中的字串鍵解析父項提供的值

下方的替代方法所述,建議您採用父項覆寫的字串索引鍵,而且在解析封裝值時,使用整數偏移/普通的潛在動機並不會帶來太大的好處。

我們應將封裝值移至編碼字串鍵,以減少元件管理服務語意中的片段化。這項變更應該幾乎不會向下游使用者顯示,但可簡化設定解析度的實作方式,方便日後偵錯。

模糊設定解析

我們日後會為元件管理服務的資訊清單剖析和元件解析度新增模糊功能。發生該情況時,我們會擴充模糊化工具以納入父項元件的設定值,並斷言遵循可變動性修飾符。

缺點、替代方案和未知

將有共用可變動限制的欄位分組

如果元件有多個欄位都具有相同的可變動性限制,我們可能會考慮允許元件作者將設定欄位和共用屬性分組。如果是非命名性的範例,我們可能會定義多個設定區段:

{
    // ...
    config: {
        // fields which can only be resolved from a component's package
    },
    parent_config: {
        // fields which are mutable by parent
    },
}

這個方向對人體工學來說可能很實用,但若我們找到了任何現有用途,並不會採取此行動。如果發現詳細程度在實務上會帶來重大稅務 就會重新審視這種方法

請注意,這種做法會讓元件作者更難定義含有多個可變動指定碼的設定欄位,例如「由父項變更並覆寫服務」。

覆寫 API 的整數序數

基於歷史原因,封裝設定值會在封裝值清單中使用偏移值解析至欄位。比起檢查字串相等,這提供的編碼更有效率,且執行階段負擔較少。

為了讓父項覆寫也能享有相同的好處,我們需要子項元件的作者為其欄位明確選擇序數,與 FIDL 資料表類似。父項元件作者需要針對這些序數指定覆寫值,或使用產生的程式庫提供人類清楚易讀的名稱。

此外,我們也需要設計機制來引導子元件作者避免重複使用整數序數,而妥善選擇的字串索引鍵,應不容易重複使用。

歸根究底,解析整數金鑰所帶來的二進位檔大小和執行階段負擔也大有助益,只需我們預期結構化設定的用量規模,就能微不足道。使用字串鍵可讓我們延遲產生的繫結,並將子元件作者從管理一般程序的複雜作業中分離出來,並且將系統效能的成本降至最低。