RFC-0215:结构化配置父替换值

RFC-0215:结构化配置父级替换
状态已接受
领域
  • 组件框架
说明

允许父组件在运行时为其子组件提供配置。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)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();

将来,父级组件应该可以配置 静态子文件使用生成的 库来降低详细程度 确保替换项类型正确无误。

利益相关方

教员: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. 来自组件自身软件包的值

从事工程 build 的组件开发者可能了解 这样会使系统上的所有对象被覆盖 权威性的内容。父级可以理解它为其子级提供的上下文。 最后,由于组件自身包中的值只能对 组件的打包方式,则其他所有信息都必须 外部来源提供的数据

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 库需要能够访问 Value 类型,当前位于 fuchsia.component.config 中。不过, fuchsia.component.config 目前依赖于 fuchsia.component.decl,因此 反转与软硬件的依赖关系需要很长时间 过渡效果。而是将 fuchsia.component.config 全部移至 当前 API 级别的 fuchsia.component.decl,弃用并最终弃用 移除 fuchsia.component.config。在合并这两个库时,我们将添加 为相应类型添加 Config* 前缀,例如Value 会变为 ConfigValue

此提案的新定义的 FIDL API Surface 将仅在 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 库用于解析组件中的配置 Manager、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 的 FIDL, 父级/子级关系的表示形式,因为 fidlc 能够 根据子级的配置架构精确检查类型。这项设计工作 父级发布商自己的配置值可以传递给 子节点。

必需的父级配置值

有些组件可能需要要求其父级提供特定的 配置值,而不必依赖打包的默认值。

这就需要允许打包的值文件省略值,并指导 这些库解析了如何处理它们的配置

这并非满足当前应用场景的必要条件,但对于 未来发展方向。

基于 FIDL 的架构和版本控制

组件框架团队探索了如何将 FIDL 用于组件 清单。如果实现 FIDL,我们或许可以使用 FIDL 可用性 注解来协调配置架构演变。

使用字符串键解析打包的配置

如果采用此 RFC 中提议的方法,将会使组件管理器只留下两个 用于解析配置值的标识符:

  1. 打包的值将使用编译后的整数偏移进行解析 配置架构
  2. 将使用配置中的字符串键解析父级提供的值 架构

正如下文的替代方案中所述,我们有充分的理由 首选使用字符串键作为父级替换值,以及使用 整数偏移/序数在解析打包值时用处不大。

我们应该将打包的值移至对字符串键进行编码,以减少碎片化 与组件管理器语义相关。此更改应该基本上 但会简化配置解析和 以便日后更轻松地进行调试

模糊配置解析

将来,我们将添加模糊测试工具,以便组件管理器的清单解析和 组件分辨率。如果这样做,我们将会 扩展模糊测试工具,以包含来自父组件的配置值,并 断言遵循可变性修饰符。

缺点、替代方案和未知问题

对具有共同可变性限制条件的字段进行分组

如果某个组件有许多字段都具有相同的可变性约束, 我们可以考虑允许组件作者对配置字段进行分组 以及具有共同属性对于(非规范性)样本,我们可以定义 多个配置部分:

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

这种方向对于人体工学设计可能很有用,但不适用于 已经确定的现有应用场景如果我们发现 在实践中,详细程度是一项高昂的成本

请注意,这种方法可能会使组件作者更难以定义 具有多个可变性说明符的配置字段,例如“由 父级和替换服务”。

替换 API 的整数序数

由于历史原因,打包的配置值会被解析为 字段。这提供了一个 编码效率更高,运行时开销更少,与检查字符串相比 是否相等。

若要为父级替换项实现同样的好处,我们需要 子组件的其字段明确选择序数,类似于 FIDL 表。父组件的作者需要指定其替换项 值来表示这些序数,也可以使用生成的 库提供简单易懂的名称。

我们还需要设计相应的机制来引导子组件作者离开 可避免重复使用整数序号,而精心选择的字符串键应位于 降低易用 bug 再利用的风险。

最终,通过解析这些文档,我们可以提高二进制文件大小和运行时开销, 根据我们的预期,使用整数的键可以忽略不计 结构化配置我们可以使用字符串键延迟生成的绑定, 使子组件作者无需费心管理 序数,且系统性能开销极低。