RFC-0146:CML 中的結構化設定結構定義

RFC-0146:CML 中的結構化設定結構定義
狀態已接受
區域
  • 元件架構
說明

在 CML 中,針對結構化設定架構設計、實作策略和其他決策

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-10-18
審查日期 (年-月-日)2021-12-22

摘要

本文以 RFC-0127 為基礎,擷取 CML 中結構化設定結構定義的設計、實作策略和其他決策。這項提案將提供設定結構定義的 JSON 語法、一組用於在元件資訊清單中編碼設定結構定義的 FIDL 型別,以及將設定結構定義從 JSON 轉換為 FIDL 的程序。

請注意,「設定結構」是 RFC-0127 中定義的「設定定義檔案」具體實作項目。

提振精神

開發人員需要為元件宣告設定結構定義。此外,您也必須建構剖析、編譯設定結構定義,並將其傳送至元件管理服務的程序。本提案旨在解決這些問題。

設定結構定義是面向使用者的主要介面,用於結構化設定。根據 RFC-0098 設定的條件,這項功能需要 RFC 提案。

建議的資訊清單語法和 FIDL 結構會納入 Fuchsia SDK。如要控管這項功能的使用情形,資訊清單檔案中的設定結構定義會受到 cmc 中的功能標記限制。

利害關係人

主持人:pascallouis@google.com

審查人員:adamperry@google.com、jsankey@google.com、geb@google.com、pascallouis@google.com、 cpu@google.com

Consulted: aaronwood@google.con, yifeit@google.com, surajmalhotra@google.com, shayba@google.com

社交化:這項設計已與元件架構團隊分享,並成為元件架構設計討論的主題。

設計

範圍

本提案將下列項目視為適用範圍:

  • 支援 RFC-0127 指定的最低設定類型:布林值、整數、有長度限制的字串,以及這些資料類型的有長度限制向量
  • RFC-0127 指定的結構化設定哲學保持一致: 簡單、可靠、安全、可測試

本提案將下列項目視為未來工作,且不屬於本 RFC 的範圍:

CML 語法

我們將在元件資訊清單中導入新的頂層鍵,用於定義元件的結構化設定。對應值包含每個設定欄位的鍵。

結構化設定欄位需要型別系統。Fuchsia 團隊在開發 FIDL 的型別系統方面經驗豐富,我們定義與 FIDL 相容且秉持相同理念的型別系統,藉此運用這項經驗,例如版面配置與限制之間的區隔。目前,結構化設定鍵中可表示的型別集,是 FIDL 中可表示型別的子集。結構化設定和 FIDL 都會隨著時間演進,我們認為結構化設定類型在某些方面可能會比 FIDL 類型更具表現力,例如透過使用限制將數字限制在特定範圍內。

為求一致性,我們使用 CML 樣式和語法,並盡量對齊命名和分解方式,以利處理相同概念。舉例來說,FIDL 型別會以版面配置表示,可選擇性地參數化,也可選擇性地受限。建議的 CML 語法擴充功能也採用相同的分解方式 (型別具有選用屬性);原始版面配置 (例如 int32) 或更複雜的版面配置 (例如 vector) 具有相同的名稱;限制可以套用至型別,例如設定向量或字串的最大大小。

元件資訊清單的異動摘要如下:

{
    use: {
        ...,
    },
    ...
    config: {
        <key>: {
            type: "<type string>",
            <additional properties based on type>
        },
        ...
    }
}

config 是資訊清單中的頂層鍵,其值為 JSON 字典。字典的每個成員都是設定欄位,由鍵和型別組成。

設定鍵具有下列屬性:

  • 這些是元件設定結構定義中的專屬 ID
  • 用於系統組裝和處理覆寫項目
    • 系統組件會使用已編譯資訊清單中的設定鍵,建立設定值檔案
    • 設定鍵是穩定的 ID,可用於定義上層覆寫
  • 必須符合規則運算式 [a-z]([a-z0-9_]*[a-z0-9])?
    • 這個規則運算式與 FIDL、JSON 和潛在用戶端程式庫中的 ID 相容。
    • 如有需要,可將分隔符號編碼至鍵中。
    • 日後也可能擴大適用範圍。
  • 長度不得超過 64 個字元
    • 日後可能會擴大支援範圍。

設定欄位中的型別字串僅限下列值:

  • bool
  • uint8uint16uint32uint64
  • int8int16int32int64
  • string
  • vector

這個設計可支援 enumfloatstructarray 等類型,但超出本 RFC 的範圍。這些複雜型別的語法將在「未來工作」一節中討論。

bool 和整數沒有任何類型限制:

config: {
    enable_advanced_features: { type: "bool" },
    num_threads: { type: "uint64" },
}

string 必須具有 max_size 類型限制。max_size 會剖析為 uint32

config: {
    network_id: {
        type: "string",
        max_size: 100,
    }
}

vector 必須具有 max_count 型別限制和 element 型別引數。max_count 會剖析為 uint32element 只能是 bool、整數或 string

config: {
    tags: {
        type: "vector",
        max_count: 10,
        element: {
            type: "string",
            max_size: 20,
        }
    }
}

範例

請參考下列元件資訊清單,這些資訊清單已改用結構化設定。 這些範例會明確著重於資訊清單的 config 區段。

archivist

目前的設定會拆分成指令列引數與元件一併封裝的 JSON 設定檔。這個範例說明如何將所有這些設定來源彙整為結構化設定。

config: {
    // proxy the kernel logger
    enable_klog: { type: "bool" },

    // initializes syslog library with a log socket to itself
    consume_own_logs: { type: "bool" },

    // connects to the component event provider. This can be set to false when the
    // archivist won't consume events from the Component Framework v1 to remove log spam.
    enable_component_event_provider: { type: "bool" },

    ...

    // initializes logging to debuglog via fuchsia.boot.WriteOnlyLog
    log_to_debuglog: { type: "bool" },

    // number of threads the archivist has available to use.
    num_threads: { type: "uint32" }
}

偵測

samplerpersistencedetect 等程式會編譯成「啟動器」二進位檔,以節省空間。由於啟動器中每個程式都有自己的資訊清單,因此可以有不同的結構化設定。

假設 detect 程式目前的設定是使用指令列引數完成:

program: {
    ...
    args: [
         // The mode is passed over argv because it does not vary for this component manifest.
         // Launcher will use the mode to determine the program to run.
         "detect"
    ]
},
config: {
    // how often to scan Diagnostic data
    // unit: minutes
    //
    // NOTE: in detect's CLI parsing, this is optional with a default specified in code.
    // when using structured config, the default would be provided by a build template or
    // by product assembly.
    check_every: { type: "uint64" },

    // if true, minimum times will be ignored for testing purposes.
    // never check in code with this flag enabled.
    test_only: { type: "bool" },
}

遊戲主機

目前是使用指令列引數進行設定。這個範例說明結構化設定中需要字串向量等進階型別。

config: {
    // Add a tag to the allow list. Log entries with matching tags will be output to
    // the console. If no tags are specified, all log entries will be printed.
    allowed_log_tags: {
        type: "vector",
        max_count: 40,
        element: {
            type: "string",
            max_size: 40,
        }
    },

    // Add a tag to the deny list. Log entries with matching tags will be prevented
    // from being output to the console. This takes precedence over the allow list.
    denied_log_tags: {
        type: "vector",
        max_count: 40,
        element: {
            type: "string",
            max_size: 40,
        }
    },
}

FIDL 規格

上述定義的 CML 語法必須由 cmc 編譯為對等的 FIDL 物件,並由元件管理服務處理,用於實作後續階段的覆寫解析。

使用 FIDL 做為設定結構定義是內部選擇,可簡化 cmc 和元件管理服務的實作程序。FIDL 不是終端開發人員用於設定結構定義的介面。

ConfigSchema 物件包含:

  • 欄位排序清單 (ConfigField 物件)
  • 結構定義檢查碼:所有設定欄位的雜湊

ConfigField FIDL 物件由 keytype 組成。這兩個欄位的意義與 CML 語法中的意義相同:key 可唯一識別設定欄位,而 type 則是設定值必須遵守的型別。

FIDL 編碼最多允許 2^32 - 1 個設定欄位。

cmc 會以確定性順序排序設定欄位,但這個順序並未由本 RFC 指定。使用確定性順序可確保結構定義檢查碼、下游工具和執行階段設定解析度的一致性。如果未指定順序,日後可進行最佳化。

cmc 會使用每個欄位的鍵和值類型計算結構定義檢查碼。設定值檔案中也會有這個檢查碼。為避免版本不一致,元件管理服務會檢查結構定義和值檔案中的總和檢查碼是否完全相同。

library fuchsia.component.decl;

// Config keys can only consist of these many bytes
const CONFIG_KEY_MAX_SIZE uint32 = 64;

// The string identifier for a config field.
alias ConfigKey = string:CONFIG_KEY_MAX_SIZE;

// The checksum produced for a configuration interface.
// Two configuration interfaces are the same if their checksums are the same.
type ConfigChecksum = flexible union {
    // A SHA-256 hash produced over a component's config interface.
    1: sha256 array<uint8>:32
};

/// The schema of a component's configuration interface.
type ConfigSchema = table {
    // Ordered fields of the component's configuration interface.
    1: fields vector<ConfigField>:MAX;

    // Checksum produced over a component's configuration interface.
    2: checksum ConfigChecksum;
};

// Declares a single config field (key + type)
type ConfigField = table {
    // The identifier for this config field.
    // This key will be used to match overrides.
    1: key ConfigKey;

    // The type of config values. Config values are verified
    // against this layout at build time and run time.
    2: type ConfigType;
};

// The type of a config value
type ConfigType = struct {
    layout ConfigTypeLayout;
    parameters vector<LayoutParameter>;
    constraints vector<LayoutConstraint>;
};

// Defines valid type ids for config fields.
type ConfigTypeLayout = flexible enum {
    BOOL = 1;
    UINT8 = 2;
    UINT16 = 3;
    UINT32 = 4;
    UINT64 = 5;
    INT8 = 6;
    INT16 = 7;
    INT32 = 8;
    INT64 = 9;
    STRING = 10;
    VECTOR = 11;
};

// Parameters of a given type layout
type LayoutParameter = table {
    // For vectors, this is the type of the nested element.
    1: nested_type ConfigType;
};

// Constraints on a given type layout
type LayoutConstraint = table {
    // For strings, this is the maximum number of bytes allowed.
    // For vectors, this is the maximum number of elements allowed.
    1: max_size uint32;
};

Component 是 CML 資訊清單的 FIDL 對等項目,現在必須包含 ConfigSchema FIDL 物件。

// *** component.fidl ***

library fuchsia.component.decl;
// NOTE: as long as the two libraries are supported, this change will also be made to
// library fuchsia.sys2;

/// A component declaration.
///
/// This information is typically encoded in the component manifest (.cm file)
/// if it has one or may be generated at runtime by a component resolver for
/// those that don't.
type Component = table {
    /// ... previous fields ...

    /// The schema of a component's configuration interface.
    10: config ConfigSchema;
};

變更「cmc

我們會擴充 cmc,以使用 config 區段還原序列化 CML、驗證其內容,並將產生的結構定義納入已編譯的資訊清單。

我們會透過功能標記實作這項功能,並在 cmc推出。在我們實作其餘結構化設定時,只有明確許可清單上的樹狀結構內元件可以使用 config 節。

元件管理服務異動

我們將變更元件管理服務,在每個元件執行個體的 resolved/config 目錄下,向中樞呈現結構化設定結構定義。確切編碼至中樞命名空間的程序不穩定,且超出本 RFC 的範圍。

中樞的這項變更可讓我們建立整合測試,驗證設定結構定義是否順利通過元件解析管道。

變更「ffx component

ffx component show 會使用中樞輸出元件例項的相關資訊。隨著元件管理服務的變更,這個外掛程式現在可以輸出每個元件執行個體的設定結構定義。

$ ffx component show netstack
Moniker: /core/network/netstack
URL: fuchsia-pkg://fuchsia.com/network#meta/netstack.cm
Type: CML static component
Component State: Resolved
...
Configuration:
  log_packets [bool]
  verbosity [string:10]
  socket_stats_sampling_interval [uint32]
  opaque_iids [bool]
  tags [vector<string:10>:20]
Execution State: Running
...

請注意,上述 ffx component show 的指令輸出內容可能會有所變更。在實作實際值解析功能前,這項指令只會列印設定結構定義。

實作

這項設計的實作作業將分三個階段逐步完成。

  1. cm_rust 中新增類型,並在功能旗標 (Prototype) 後方剖析 cmc 中的設定段落
  2. 透過中樞公開設定、整合測試 (原型)
  3. ffx component show 項變更

效能

  • 我們會根據結構化設定,評估元件啟動時間的額外效能影響。我們已為此錯誤提出回報。
  • 這項設計使用 FIDL 資料表編碼結構定義,因此與宣告結構相比,剖析 ComponentDecl FIDL 物件時會增加一些負擔。我們預期與整體元件啟動時間相比,這項額外負荷微不足道。
  • 這項設計會將設定鍵儲存為 ConfigField FIDL 物件中的字串,因此會耗用額外的磁碟空間,且啟動元件時需要複製更多資料。
    • 這是非 ELF 執行器的必要條件,其中部分執行器需要字串鍵,才能以原生方式編碼設定值。
    • 這樣也比較容易透過中樞進行偵錯。
  • 對於 N 個設定鍵,我們預期設定值比對的成本為 O(N)。如果沒有覆寫,系統就不會檢查設定金鑰是否相等。
  • 我們不關心主機工具 (例如 cmc) 的效能。
  • 元件管理服務不會負責雜湊處理設定結構定義或設定值。cmc 會預先進行雜湊處理。元件管理服務只需要檢查結構定義和值檔案中的雜湊是否相等。
  • 元件管理服務會將設定結構定義儲存在 Hub 檔案系統中。這可能會對元件管理服務的記憶體用量造成額外影響,且影響程度不容忽視。
    • 如果中樞是採用提取式而非推送式,就能解決這個問題。已針對這項功能要求提出錯誤

安全性考量

作者未發現任何潛在疑慮。請注意,這項功能在 cmc 中受到允許清單的限制,可進一步降低安全風險。

隱私權注意事項

您可以在元件資訊清單中查看設定鍵。如果元件會公開發布,這些鍵就不應包含專屬資訊。

作者未發現任何其他潛在疑慮。

測試

我們將提供:

  • cmc 的單元測試,用於測試 config 節的驗證和編譯 (包括失敗案例)
  • 元件管理服務的單元測試,可使用結構化設定測試中樞的目錄結構
  • 元件管理服務的整合測試,可解析元件資訊清單,並驗證中樞是否顯示設定結構定義
  • ffx component show 的單元測試,可正確剖析中樞的結構化設定。

說明文件

隨著結構化設定日趨成熟,我們預計會新增下列文件:

  • 元件結構化設定結構定義的 CML 語法
  • 元件結構化設定結構定義的 FIDL 規格
  • 宣告設定結構定義的最佳做法:註解、命名慣例等
  • 範例/程式碼研究室:在元件的資訊清單中新增 config 區段、建構元件,並使用 ffx component show 驗證設定欄位

缺點、替代方案和未知事項

替代方案:config 區段指向 FIDL 檔案

資訊清單中的 config 區段可能會指向描述設定結構定義的 FIDL 來源檔案。RFC-0127 詳細討論了這個替代方案及其缺點。RFC-0127 的結論也適用於此。

替代方案:program 下方的「替代方案」config 部分

program: {
    config: {
        <key>: {
            type: "<type string>",
            <additional JSON fields based on type>
        },
    }
}
  • Pro:在 programconfig 之間建立關聯
  • 缺點:巢狀結構過多
  • 缺點:沒有 program 區塊的元件無法有結構化設定。日後,設定檔路徑可能包含沒有程式的元件。

替代方案:「fields」專區

config: {
    fields: {
        <key>: {
            type: "<type string>",
        }
        ...
    }
}
  • 優點:為日後擴充設定結構定義留下空間。目前尚不清楚是否需要這項未來擴充性。
  • 缺點:較不精簡

替代方案:欄位詳細程度

選項 B:

config: [
    {
        key: "<key>",
        type: "<type string>",
    }
    ...
]
  • 優點:使用 JSON 陣列而非 JSON 物件。與資訊清單的其他部分更一致:useexpose 等。
  • 缺點:這個選項在視覺上與用於定義設定欄位的結構體/表格/對應項語意不同。在 JSON 中,指向值的字串鍵值通常以對應/物件表示。
  • 缺點:較為冗長

選項 C:

config: {
    <key>: "<type string>"
    ...
}
  • 優點:更精簡
  • 缺點:日後無法擴充欄位。複雜型別和預設值可能需要這項設定。

選項 D:

config: [
    {
        <type string>: "<key>",
        ...
    },
],
  • 優點:更精簡。沒有樣板「type」或「key」關鍵字
  • 優點:與能力轉送語法一致
  • 缺點:重複的鍵需要明確檢查
  • 缺點:不清楚這項功能如何搭配向量的 element 型別引數運作

之後的作業

資訊清單分片中的結構化設定

我們將延後這項工作,直到能證明其必要性為止。我們也沒有處理合併衝突的好策略。如果合併衝突導致編譯停止,每個分片都必須防禦性地命名設定欄位。如果可以解決合併衝突,不同的分片可能會無意間共用相同的設定欄位。

設定結構定義中的預設值

設定欄位可支援預設值。這些預設值會以 JSON 型別說明。RFC-0127 假設預設值應屬於設定結構定義,但目前建構規則或子組件產生設定值檔案來提供預設值,似乎更合理。

config: {
    enable_advanced_features: {
        type: "bool",
        default: false,
    }
    tags: {
        type: "vector",
        max_count: 10,
        element: {
            type: "string",
            max_size: 20,
        }
        default: [
            "foo",
            "bar",
            "baz",
        ]
    }
}

我們將延後這項工作,直到證明子組件系統無法用於預設值為止。

複雜資料類型

我們預計在日後新增對 arrayenumfloatstruct 等複雜型別的支援。vector 應支援這些類型,且可能在可行時進行額外的驗證步驟。

config: {
    fsck: {
        type: "struct",
        fields: {
            check_on_mount: { type: "bool" },
            verify_hard_links: { type: "bool" },
            repair_errors: { type: "bool" },
        }
    },
    compression_type: {
        type: "enum",
        variants: {
            uncompressed: 0,
            zstd_chunked: 1,
        }
    },
    // Vectors can store complex structures
    coordinates: {
        type: "vector",
        max_count: 10,
        element: {
            type: "struct",
            fields: {
                x: { type: "int32" },
                y: { type: "int32" },
            }
        }
    },
}

設定欄位註解的工具

上述範例中的設定欄位含有 JSON 註解,可更詳細地說明設定欄位。這些註解可以處理,並新增至結構化設定的其他區域。 可以想像在 Rust 和 C++ 中產生的用戶端程式庫具有相同的說明。

這樣一來,開發人員編寫用戶端程式碼時,編輯器就會顯示這些說明做為提示。 系統組裝等工具也能提供更詳細的說明和錯誤文字。

這需要變更我們的 JSON 剖析程式庫,因為目前程式庫不會剖析 JSON 註解。

config: {
      /// Add a tag to the allow list. Log entries with matching tags will be output to
      /// the console. If no tags are specified, all log entries will be printed.
      allowed_log_tags: {
          type: "vector",
          max_count: 40,
          element: {
              type: "string",
              max_size: 40,
          }
      },

      /// Add a tag to the deny list. Log entries with matching tags will be prevented
      /// from being output to the console. This takes precedence over the allow list.
      denied_log_tags: {
          type: "vector",
          max_count: 40,
          element: {
              type: "string",
              max_size: 40,
          }
      },
}

max_sizemax_count 的最大支援值

資訊清單可能會想在向量和字串中使用 max_sizemax_count 屬性的支援值上限。您可以使用 MAX 字串,或直接省略該屬性。

config: {
    network_id: {
        type: "string",
        max_size: "MAX",
    }
}
config: {
    tags: {
        type: "vector",
        element: {
            type: "string"
        }
    }
}

既有技術與參考資料

元件資訊清單語法

FIDL 語言規格

JSON5 資料交換格式