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 的涵盖范围内:
- 支持清单分片中的配置架构
- 支持复杂数据类型
- 支持配置架构中的默认值
- 支持覆盖政策
<ph type="x-smartling-placeholder">
- </ph>
- 根据 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 字典。该
字典是由键和类型组成的配置字段。
配置键具有以下属性:
- 它们是组件配置架构中的唯一标识符
- 它们用于系统汇编和处理替换项
<ph type="x-smartling-placeholder">
- </ph>
- 已编译清单中的配置键供系统汇编用来创建配置值文件
- 配置键是稳定的标识符,可用于定义父替换项
- 它们必须与正则表达式
[a-z]([a-z0-9_]*[a-z0-9])?
匹配 <ph type="x-smartling-placeholder">- </ph>
- 此正则表达式与 FIDL、JSON 和 客户端库。
- 如有必要,可为密钥中的分隔符留出空间。
- 将来也可以对其进行扩展。
- 长度不得超过 64 个字符
<ph type="x-smartling-placeholder">
- </ph>
- 将来可以进行扩展。
配置字段中的类型字符串仅限以下值之一:
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" }
}
detect
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
会按确定顺序对配置字段进行排序,但该顺序未指定
。使用确定性顺序可确保架构校验和下游的架构保持一致
工具和运行时配置解析不指定顺序可为
优化。
架构校验和是通过 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
节
结构化配置
对组件管理器的更改
我们将更改组件管理器,以便向 Hub 呈现结构化配置架构
位于每个组件实例的 resolved/config
目录下。发送到 hub 的
命名空间将不稳定,并且将不在此 RFC 范围内。
对 Hub 进行这项更改后,我们可以创建集成测试来验证相应配置 成功通过组件解析流水线。
对 ffx component
的更改
ffx component show
使用 hub 输出有关组件实例的信息。更改后
至组件管理器,此插件现在可以输出每个组件实例的配置架构。
$ 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 运行程序的要求,其中一些运行程序需要使用字符串键进行编码 配置值
- 这也使得通过 hub 进行调试变得更加轻松。
- 对于 N 个配置键,我们预计配置值匹配需要 O(N) 成本。配置键 检查是否相等。
- 我们不关心
cmc
等托管工具的性能。 - 组件管理器不负责对配置架构或配置进行哈希处理
值。
cmc
将提前进行哈希处理。组件管理器只需检查 架构和值文件中的哈希值。 - 组件管理器会将配置架构存储在中心文件系统中。这可能会
对组件管理器内存使用的额外影响不可忽略。
- 让 hub 基于拉取而非推送可以解决这一问题。答 已提交 bug。
安全注意事项
作者没有看到任何潜在疑虑。请注意,此功能在 cmc
下使用
许可名单,进一步降低安全风险。
隐私保护注意事项
配置键在组件清单中可见。这些键不应包含 专有信息。
作者没有看到任何其他潜在问题。
测试
我们将:
- 针对
cmc
的单元测试,用于测试config
节的验证和编译(包括失败) 案例) - 适用于组件管理器的单元测试,这些测试使用结构化配置测试 hub 的目录结构
- 组件管理器集成测试,用于解析组件清单并验证 hub 显示其配置架构
- 针对
ffx component show
进行单元测试,以正确解析 hub 中的结构化配置。
文档
随着结构化配置达到成熟阶段,我们预计会添加以下文档:
- 组件的结构化配置架构的 CML 语法
- 适用于组件结构化配置架构的 FIDL 规范
- 声明配置架构的最佳做法:注释、命名惯例等
- 示例/Codelab:向组件的清单中添加
config
部分,构建该组件, 使用ffx component show
验证配置字段
缺点、替代方案和未知问题
替代方案:config
部分指向 FIDL 文件
清单中的 config
部分可以指向
介绍配置架构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>",
...
},
],
- 优点:更简洁。无样板“类型”或“key”关键字
- 优点:与功能路由语法一致
- 缺点:重复的键需要显式检查
- 缺点:尚不清楚这如何与矢量的
element
类型参数搭配使用
未来工作
清单分片中的结构化配置
我们将推迟这项工作,直到我们证明确实有必要进行。我们也没有很好的策略 用于处理合并冲突。如果合并冲突导致编译暂停,则每个分片都需要为 config 字段。如果可以解决合并冲突,可能会将不同的分片 会无意中共用同一个配置字段。
配置架构中的默认值
可以在配置字段中支持默认值。我们将使用 JSON 类型。RFC-0127 假定默认值应该是配置的一部分 架构,但目前更适合让 build 规则或子组件提供默认值 方法是生成配置值文件
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"
}
}
}