RFC-0127:結構化設定

RFC-0127:結構化設定
狀態已接受
區域
  • 元件架構
說明

新的設定系統,可解決一組常見的元件設定問題。

問題
變更
作者
審查人員
提交日期 (年/月)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 Engineering Council」是「Fuchsia Engineering Council」,即元件平台團隊,其範圍會擴大 (即元件架構和軟體推送) 和處理流程中,找出可改善成效 (PDK 和安全性) 的團隊。

系統的潛在用戶端也很重要,但由於這個 RFC 並未建議通用設定系統,因此不應符合所有潛在用戶端的所有設定。

講師:abarth

審查人員:geb (元件架構)、wittrock (SWD)、Aaronwood (SWD 和 PDK)、ampearce (安全性)

顧問:ddorwin、hjfreyer、ejia、thisguy、shayba、jamesr、ypomortsev、crjohns、surajmalhotra、curtisgalloways、adamperry

社交化:本設計的早期草稿或上述文件已於元件架構、安全性、軟體推送、Cobalt 和 PDK 團隊中審查。我們還另外與潛在客戶進行了幾場討論,

用途

常見用途:功能旗標

將新功能新增至已部署的系統可能會造成風險,新的設計和新程式碼有時會包含錯誤或無效假設,直到部署至實際工作環境後才發現。還有許多其他平台採用「功能旗標」來降低這個風險:用來控制功能是否啟用的布林值設定參數。功能旗標有許多優點:

  • 新軟體版本的部署作業與啟用新功能可以分離;同一個軟體版本中新增的功能不必同時啟用。
  • 啟用功能之前,可以先進行全面測試來確認新功能的正確運作。
  • 每項功能都可以透過發布版本、推出百分比或兩者並用,逐步針對所有裝置啟用。
  • 如有需要,您可以安全快速停用每項功能;停用這項功能不需要復原至先前的軟體版本。

讓我們舉個例子來說明一項近期一項功能:一項功能是將從「預估頻率估算」到「計時器」一文。在提交的 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 結構定義的程式庫,以及從執行元件提供給元件的輸入內容在執行階段填入此 struct 所需的程式碼。實作元件之後,就可以匯入這個程式庫,並使用結構中的欄位控制其行為。

請注意,元件開發人員可以使用約 10 行程式碼,限制標記後方的新功能。他們換取這些 10 行的程式碼能獲得什麼好處?

  • 這項功能一開始會在所有地區停用。由於元件作者提供預設值「false」,因此標記會保持關閉,直到組合程序的其他輸入內容明確設定不同的值為止。

  • 一開始,系統在正式版環境中執行時,無法啟用這項功能。系統執行期間可以更改哪些設定金鑰,而此 RFC 未指定政策。不過,基於安全考量,除非有明確要求及審查,否則正式環境 (例如「使用者」) 版本很可能不允許執行階段異動。

  • 在系統執行工程版本時,開發人員可以測試這項功能。系統執行期間可以修改哪些設定金鑰,而此 RFC 未指定政策。不過,為了簡化開發、測試及偵錯,工程團隊的大多數設定鍵 (例如「eng」) 版本。 開發人員可使用 ffx 為本機裝置啟用這項功能。例如 (使用意義語法):ffx target config set timekeeper.cml enable_frequency=true。如果政策允許,開發人員也可以啟用這項功能,讓該功能在裝置重新開機後保持不變。

  • 測試可涵蓋「功能已啟用」和「已停用功能」的案例。 對於單元和元件層級測試,這包含手動建構及插入 FIDL 設定結構,對整合測試來說,您必須在啟動測試的元件時提供設定 (例如使用 Realm Builder)。

  • 建構和組合工具可在平台邊界內控管這項功能。建構與組合工具目前正在透過 DPISPAC 努力執行。透過這項工作的結果,平台維護人員可控管各項平台功能是否會向產品公開。視政策和功能的風險而定,平台可以控制該功能的發布作業,或將功能推出作業委派給產品。如果是更複雜的情況,平台可以在系統執行期間 (例如,「使用者」) 版本。這樣一來,產品就能在個別裝置上啟用這項功能,例如根據實驗推出系統或企業管理控制台進行設定。

  • 裝置指標會反映標記狀態。針對在系統執行時,標記狀態可以變更的發布版本,其值會納入額外的雜湊中,可用於在指標分析期間將已啟用功能的同類群組與已停用功能的同類群組分開。

常見用途:產品/主機板/建構類型自訂

Fuchsia 是一般用途的作業系統,可用於廣泛不同的產品和裝置類別。這表示有時平台元件需要根據所用產品或遊戲板的特性調整行為。

讓我們以時間 keeper 為例:UTC 維護演算法需要瞭解裝置輪廓器的準確度,瞭解錯誤界限的增長情形,以及加權成功樣本。某些 Oliator 會比其他輪廓更為精確 (而且成本高昂!),但目前沒有簡易的方法能表示板型變化,因此這個 OS 錯誤目前是以硬式編碼的方式寫入為常數:

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 ComponentDecls,而不是從元件資訊清單產生。這些執行階段一開始不支援結構化設定。

在資訊清單中加入設定區段,會使元件建構規則產生一個程式庫,其中包含該設定的 FIDL 資料表定義,以及從執行元件提供給元件的輸入內容在執行階段填入這個資料表所需的程式碼。實作元件之後,即可匯入這個程式庫,並使用資料表中的欄位控制其行為。

元件資訊清單會說明元件的需求和元件必須達成的合約。在這個 RFC 之前,*_binary 建構規則並未取決於資訊清單的內容,而 fuchsia_component 建構規則在二進位檔上有選用的依附元件。您必須對建構規則進行一些變更,以避免循環依附元件。

在某些情況下,單一二進位檔會由多個元件共用。在這種情況下,您必須進行一些重構,才能使用結構化設定。其中一種做法是透過加入常見的 CML 資料分割,確保所有元件定義相同的設定。另一種做法是將元件合併成單一定義,並使用結構化設定來描述先前使用不同資訊清單表示的行為差異。

建構、組裝及發布

建構、組合和發布程序負責為每個可設定元件產生兩個檔案。這兩個檔案都會放在與元件相同的套件中 (日後將會擴充此套件,以支援透過其他套件提供值,請參閱這個替代方案)。

設定定義檔案

此檔案包含每個設定金鑰的下列資訊:

  • FIDL 欄位編號
  • 欄位名稱
  • 資料類型

這項資訊全都會在元件資訊清單中提供,因此這個檔案可在元件建構程序期間產生。此外,您也可以計算設定檔定義檔案中的所有資訊,並將其納入雜湊,做為設定版本 ID 使用。

請注意,為了簡化用途的討論,我們將設定定義描述為「檔案」,但實作作業可能會納入已編譯的元件資訊清單 (例如*.cm 檔案),而不是個別檔案。

設定檔值檔案

此檔案包含每個設定金鑰的下列資訊:

  • FIDL 欄位編號
  • 設定值
  • ChildDecl (布林值) 可變動
  • 覆寫 (布林值) 可變動

檔案格式並非由此 RFC 定義。

在成熟且可擴充的組裝系統中,多位不同執行者可能會想為一或多個鍵指定或限制這項資訊。例如元件作者、董事會啟動工程師、平台邊界擁有者、產品整合商或安全性審查人員。啟用此功能需要的部分工具目前已透過 DPISPAC 平台藍圖項目執行,而此 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 和欄位編號。

值選取摘要

將這些流程放在一起,元件管理員會為每個設定鍵選取值,如下所示:

  1. 如果金鑰可透過覆寫設定可變動,且系統從設定覆寫服務傳回相符的覆寫值,請使用這個值。
  2. 否則,如果鍵可由 ChildDecl 變動,且在 ChildDecl 中提供值,則使用這個值。
  3. 否則,請使用設定檔的值。

設定 FIDL 介面並覆寫資料庫

設定覆寫管理員會公開兩個 FIDL 服務,可用來與其覆寫資料庫互動:

  1. 具備以下特性的服務:
    • 讀取所有設定覆寫。
    • 建立及刪除保存在記憶體中的設定覆寫,但在設定覆寫管理員重新啟動後不會保留。
  2. 具備以下特性的服務:
    • 讀取所有設定覆寫值
    • 刪除所有設定覆寫值
    • 建立保存在記憶體中的設定覆寫,但在設定覆寫管理員重新啟動後不會保留
    • 建立儲存在磁碟的設定覆寫,並在設定覆寫管理員重新啟動後保留下來

第一項服務無法對設定進行長期設定變更,對於自動化端對端測試來說可能很實用。這兩項服務都屬於機密服務,且已加入許可清單。

我們將引入使用第二項服務的 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 套件設計類似。

我們優先採用分散式方法,主要原因有二:

  1. 未來 Fuchsia 必須執行基本映像檔不知道的元件 (例如應用程式更新後的結果)。這些不明元件的設定無法發布至中央套件,因此需要不同的解決方案。
  2. 以不可分割的形式提供二進位檔和其設定,讓我們能夠更明確地說明其一致性,尤其是在套件單獨更新的情況下,不像基本映像檔可以更新。導致特定元件無法使用但其設定無法使用,或是元件與其設定不相容的失敗模式較少。

如 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 主要是由開發人員體驗所主導:

  1. 元件資訊清單是開發人員向架構說明元件需求,並定義符合此定義的設定金鑰。維護單一檔案比新增檔案更少。
  2. 定義設定的語法和資料類型是完整 FIDL 語言的一小部分 (請參閱範圍)。如果只需要支援子集,卻需要 FIDL 語法,可能會令人混淆並感到困擾。
  3. 設定需要不能以 FIDL 語言表示的資訊 (目前資料表欄位的預設值,且未來可能會受到其他限制)。這項資訊可以儲存在自訂屬性中,但此資訊與其他 FIDL 語言不一致,且開發人員會感到困惑。

若 JSON 設定定義使用 FIDL 語言中的概念,我們會使用一致的語法。舉例來說,資料類型名稱會保持一致。

cmc 已經根據 JSON 元件資訊清單建構 FIDL 資料表,從資訊清單中剖析設定所需的額外工作並不容易。

支援元件之間的設定轉送

元件架構中的許多資源都支援從一個元件執行個體轉送至另一個元件執行個體,例如通訊協定、目錄和儲存空間功能。支援在元件之間轉送設定是自然的,因此,假設子項可以使用其父項的部分設定值。

這項初始設計不支援設定轉送功能,以免導入新的版本管理挑戰。如果我們支援在一個元件中定義設定,並將該設定用於封裝不同時間的另一個元件 (原因可能是這些元件是在不同的存放區定義,或是並未以單體式的形式提供給裝置),我們就無法確保將編譯至元件的設定定義與用於設定值的定義相符。我們會在這兩個元件之間引進新的 ABI,但因為這個介面會在 PDK 中表示,而非 IDK,所以無法使用 RFC-0002 中定義的程序來管理版本相容性。

隨著 PDK 和樹狀結構組合工作持續進行,我們會延遲元件之間的設定轉送作業,直到更成熟為止,因此元件表面和版本管理功能的討論可能較強。在此期間,如有需要,您可以使用組合工具,在多個套件中提供一致的設定值。

我們已支援父項在建立時動態設定子項元件,針對這個跨元件相容性問題,推出了更有限的版本。我們認為這些情況不太可能在短期內造成問題,因為必須選擇加入這個可變動性,且設定欄位通常是由子項明確設計,以供父項使用。

支援可重複使用的程式庫公開設定。

許多元件都是使用可重複使用的程式庫建構,而這些程式庫支援某些形式的設定。此 RFC 中定義的結構化設定可用於提供這項設定,但必須以手動方式執行。使用程式庫的每個元件都必須在自己的資訊清單中宣告相符的設定金鑰,然後在初始化時將設定值轉送至程式庫。執行者也有類似的情況,也就是執行器可以直接控制在其執行元件所使用的程式庫中可設定的行為。

另一個做法是支援更「透明」的程式庫設定,而程式庫可在其中宣告其設定金鑰,並可直接在執行階段使用設定值。元件只需要宣告程式庫的使用方式,讓設定提供者在該元件中為程式庫的執行個體設定設定值。

與元件之間轉送一樣 (如上述替代方法所述),這可能會涉及不同軟體構件的設定金鑰,例如在樹狀結構元件外使用平台程式庫時。這可能偏好較正式的設定定義,該定義可以更明確地保證設定版本之間的前向和回溯相容性。程式庫透明設定也會引起有關如何將多組設定值提供給元件的問題。

雖然長期來看,程式庫的透明設定是很有用的功能,但我們會將這項工作延後到此 RFC 中定義的更「私密」設定系統正常運作為止。

元件啟動後支援設定更新

這項設計只會在元件執行個體啟動時提供元件設定。若是在元件啟動後修改設定,則必須等到下次啟動才會觸及元件。另一種做法是透過 FIDL 定期將設定提交至元件。

聚焦元件的決定開始與「設定」繫結至元件生命週期的定義一致,但同時也能簡化元件作者的實作:

  1. 僅接收設定一次的元件執行個體,在元件啟動期間,可以初始化其所有設定導向的資源。必須定期接收新設定的元件也需要定期分配或收集資源,因為設定變更,我們對此至關重要。
  2. 如果元件必須定期接收新設定,就會需要定期或非同步處理作業,否則可能就不需要這麼做。
  3. 必須定期接收新設定的元件也必須透過診斷連線,記錄設定的任何變更,例如建立新的 MetricEventLogger
  4. 必須定期接收新設定的元件會有其他必須處理的故障模式,例如終止 FIDL 連線和逾時。

目前,設定在開始後需要更新的設定非常有限:在元件啟動後,透過版本或 ChildDecl 提供的設定值將無法變更。FIDL 介面可能會在啟動後導入變更,但一開始只會用於開發人員工具,可輕鬆在變更設定後自動重新啟動元件。

日後,某些產品專屬元件可能會偏好實作上述複雜性,而非重新啟動。如果有,我們會考慮是否允許元件透過 FIDL 接收更新。

先前的圖片和參考資料