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
諮詢對象:aaronwood@google.con、yifeit@google.com、surajmalhotra@google.com、shayba@google.com
社交化:這個設計已與元件架構團隊分享,並成為元件架構設計討論的主題。
設計
範圍
本提案將以下項目視為範圍內:
本提案將以下項目視為日後的工作,不在本 RFC 的範圍內:
- 支援資訊清單區塊中的設定配置文件
- 支援複雜資料類型
- 支援設定值配置中的預設值
- 支援覆寫政策
- 根據 RFC-0127,系統會為設定值檔案宣告覆寫政策
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 和潛在的用戶端程式庫中的識別符相容。
- 這可讓您在必要時在金鑰中編碼分隔符。
- 日後也可能會擴大服務範圍。
- 長度不得超過 64 個半形字元
- 日後可能會擴大支援。
設定欄位中的類型字串只能是下列任一值:
bool
uint8
、uint16
、uint32
、uint64
int8
、int16
、int32
、int64
string
vector
這項設計可支援 enum
、float
、struct
和 array
等類型,但這不在本 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
會解析為 uint32
。element
僅限於 bool
、整數或 string
:
config: {
tags: {
type: "vector",
max_count: 10,
element: {
type: "string",
max_size: 20,
}
}
}
範例
請參考下列已調整為使用結構化設定的元件資訊清單。這些範例會明確著重於資訊清單的 config
部分。
檔案管理員
目前的設定會分為 指令列引數和與元件一併封裝的 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" }
}
偵測
sampler
、persistence
和 detect
等程式會編譯為「啟動器」二進位檔,以節省空間。由於啟動器中所綁定的每個程式都有自己的資訊清單,因此可以採用不同的結構化設定。
請考慮 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 物件包含 key
和 type
。這兩個欄位的含義與 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
目錄底下的 hub。將編碼精確值納入中樞的命名空間會不穩定,且超出本 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
的指令輸出內容可能會有所變動。在實際值實作前,這項指令只會列印設定結構定義。
實作
這項設計的實作方式將分成三個階段完成。
成效
- 我們會將成效提升的影響納入評估基準,以便評估結構化設定對元件啟動時間的影響。我們已針對此問題回報錯誤。
- 此設計會使用 FIDL 資料表對結構定義進行編碼,因此在剖析
ComponentDecl
FIDL 物件時,相較於宣告結構體,會產生一些額外的額外負擔。我們預期這項額外負擔與整體元件啟動時間相比,可忽略不計。 - 這項設計會將設定鍵儲存為
ConfigField
FIDL 物件中的字串,這會消耗額外的磁碟空間,並在啟動元件時需要複製更多資料。- 這是非 ELF 執行程式的要求,其中部分執行程式需要字串索引鍵,才能原生編碼設定值。
- 這也能讓您更輕鬆地透過中樞進行偵錯。
- 對於 N 個設定值鍵,我們預期設定值比對的成本為 O(N)。在沒有覆寫值的情況下,系統不會檢查設定鍵是否相等。
- 我們不擔心
cmc
等主機工具的效能。 - 元件管理服務不會負責對設定值或設定值進行雜湊運算。
cmc
會事先執行雜湊運算。元件管理服務只需要檢查結構定義和值檔案中的雜湊值是否相等。 - 元件管理服務會將設定結構定義儲存在中樞檔案系統中。這可能會對元件管理服務的記憶體用量造成額外且不可忽視的影響。
- 讓中樞以「拉」取代「推」即可解決這個問題。我們已針對這項功能要求提出錯誤。
安全性考量
作者未發現任何潛在疑慮。請注意,這項功能會在 cmc
中使用許可清單進行控管,進一步降低安全性風險。
隱私權注意事項
設定鍵會顯示在元件資訊清單中。如果元件將公開發布,這些鍵不得包含專屬資訊。
作者未發現任何其他潛在問題。
測試
我們將提供:
cmc
的單元測試,可測試config
節的驗證和編譯作業 (包括失敗案例)- 針對元件管理服務的單元測試,可透過結構化設定測試中心的目錄結構
- 針對元件管理服務的整合測試,可解析元件資訊清單,並驗證 Hub 是否顯示其設定結構定義
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>
},
}
}
- 優點:在
program
和config
之間建立關聯 - 缺點:巢狀結構過多
- 缺點:沒有
program
區段的元件無法有結構化設定。日後,設定路由可能會涉及沒有程式的元件。
替代方案:fields
專區
config: {
fields: {
<key>: {
type: "<type string>",
}
...
}
}
- 優點:日後可擴充設定結構定義。我們不確定是否需要日後的擴充功能。
- 缺點:不夠精簡
替代方案:欄位的冗長程度
方法 B:
config: [
{
key: "<key>",
type: "<type string>",
}
...
]
- 優點:使用 JSON 陣列而非 JSON 物件。與資訊清單的其他部分保持一致:
use
、expose
等。 - 缺點:這個選項與用於定義設定欄位的結構/表格/對應項目語義在視覺上有所差異。在 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",
]
}
}
我們會暫緩這項工作,直到我們能夠證明子組件系統無法用於預設值。
複雜資料類型
日後,我們預計會新增對 array
、enum
、float
和 struct
等複雜類型的支援。這些類型應在 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_size
和 max_count
支援的最大值
清單可能會在向量和字串中使用 max_size
和 max_count
屬性的最大支援值。您可以使用 MAX
字串,或直接省略屬性來完成此操作。
config: {
network_id: {
type: "string",
max_size: "MAX",
}
}
config: {
tags: {
type: "vector",
element: {
type: "string"
}
}
}