RFC-0127:结构化配置 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 新的配置系统,用于解决一组常见的组件配置问题。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-07-08 |
审核日期(年-月-日) | 2021-09-22 |
摘要
此 RFC 提出了一种新的“结构化”可让组件 可轻松且一致地解决一组常见的组件配置问题, 解决使用组件框架时遇到的问题这些信息旨在补充 替换 Fuchsia 上的现有配置机制。
组件开发者可以在 则组件框架会将配置值 该组件。定义了初始配置值 进行组装。如果在组装时允许,配置值也可以 在系统由组件的父级运行或通过 FIDL 运行时设置 界面。
此 RFC 涵盖动机、预期用例和整体设计。未来 RFC 将定义开发者用于交互的实现和语法 与这个新系统相关联。
设计初衷
如果软件可以“配置”,将更加灵活且可重复使用;也就是说,如果 其行为的方方面面都可以受到外部控制,而不能通过 源代码Fuchsia 适用于 大型生产环境;都必须提供 并能够随着时间的推移安全地发展平台。
其他大型平台提供的基础架构可帮助开发者添加 配置(例如 Chromium),但是 Fuchsia 上的配置目前是手动流程,需要定制工作 既可以在运行时使用配置值, 为不同环境设置不同的配置值
目前在 Fuchsia 上用于配置的最常用工具读取文件 从 config-data 软件包中读取数据 面向配置的 FIDL API这些现有的工具曾用于解决 迄今为止的几个重要问题,但它们使用范围不广, 始终如一。
此 RFC 提出了一种新的“结构化”可让组件 可轻松且一致地解决一组常见的组件配置问题, 解决使用组件框架时遇到的问题这些信息旨在补充 替换现有的配置机制。
实现简单一致的组件配置有诸多好处 发送到 Fuchsia,例如:
- 可以提高平台组件的灵活性,以支持更广泛的 产品。
- 向组件框架 v2 的迁移可以简化为向组件框架 v2 的迁移,只需提供 取代了 CFv1 在启动子项时提供参数的功能 组件。
- 新的平台和产品功能可以更安全地部署到生产环境 来传递数据。
- 与开发、测试和维护可配置相关的费用 行为可以减少
利益相关方
此 RFC 的利益相关方是 Fuchsia 工程委员会, 范围扩大的平台团队(即组件框架和软件 而团队正致力于实现可改进的流程 (PDK 和安全)。
系统的潜在客户也很重要,但由于此 RFC 并非 提议一个通用配置系统,该系统不应满足所有要求 满足所有潜在客户期望的配置需求
教员:abarth
审核者:geb(组件框架)、 wittrock (SWD)、aaronwood(SWD 和 PDK)、ampearce(安全)
已咨询:ddorwin、hjfreyer、ejia、Thatguy、shayba、jamesr、ypomortsev Crjohns、surajmalhotra、Curtisgalloways、Adamperry
社交化:此设计的早期草稿或上述文档的 在组件框架、安全、软件交付、Cobalt、 和 PDK 团队。此外,还与潜在客户进行了多次讨论。
用例
常见用例:功能标志
向已部署的系统中添加新功能可能会带来风险;新设计 新代码偶尔会包含 在部署到生产环境后才会发现。许多其他 平台通过使用“功能标志”来降低此风险:布尔值 用于控制某项功能是否处于活跃状态的配置参数。 功能标志具有多项优势:
- 新软件版本的部署可以独立于新软件版本的激活 功能;在同一软件版本中添加的功能不一定要 同时启用
- 正确操作可以是 在启用该功能前经过全面测试。
- 每项功能都可以跨设备逐步启用,一种是使用版本 渠道和/或百分比形式的发布。
- 您可以根据需要安全快速地停用每项功能;停用 功能无需回滚到早期软件版本。
让我们以最近一项功能为例,该功能受益于 功能标志:引入频率估算 计时器。在提交的 CL 中,此功能立即生效, 在所有产品的所有版本中永久启用。
estimator/mod.rs
(无结构化配置)
match self.frequency_estimator.update(&sample) {
// use resulting frequency update
...
}
利用结构化配置,我们应该能声明功能标志 (位于 Timekeeper 的组件清单中),然后控制 Estimator:
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 结构体定义的库, 从配置以及在运行时填充此结构体所需的代码 组件的运行程序提供给组件的输入。在 然后,组件就可以导入该库,并使用结构体中的字段来控制 行为
请注意,组件开发者可以用大约 10 行代码限定位于某个标志后面的新功能 代码。他们可以用这 10 行代码换取什么?
最初,我们将在所有渠道中停用此功能。由于该组件 作者提供了默认值“false”,则该标志会保持关闭状态,直到 组装流程的其他输入明确设置不同的值。
在系统初始状态下无法启用此功能, 正式版本哪些配置键在 系统运行是一个政策问题,此 RFC 未指定政策。 不过,出于安全考虑,很有可能发生运行时变更, 允许在正式版(例如“用户版”)中使用,除非另有明确要求 并经过审核。
开发者可以在系统进行工程测试时测试该功能 发布。在系统执行更改操作时, 运行是一个政策问题,此 RFC 未指定政策。但是, 简化开发、测试和调试。很可能会发生运行时变更, 可以用于工程领域的大多数配置键(例如,“eng”)版本。 开发者可以使用
ffx
为本地设备启用此功能。 例如(使用概念语法):ffx target config set timekeeper.cml enable_frequency=true
。如果政策允许,开发者还可以启用 功能,使其在设备重启后持续有效。测试可以涵盖“已启用的功能”和“已停用功能”案例。 对于单元测试和组件级测试,这涉及手动构建和 注入 FIDL 配置结构体;对于集成测试, 配置,例如使用 Realm Builder。
构建和组装工具可以控制作为平台的一部分的功能 边界。制造和组装工具目前正在通过 DPI 和 SPAC 方面的工作。这项工作的结果 可以让平台维护人员控制每项平台功能 接触过的产品。根据政策和功能风险, 平台可以控制该功能的发布, 将该功能发布到产品中。对于更复杂的用例 即使在系统运行时,也允许更改功能标志, 生产环境(例如“user”)版本。这样,产品就可以启用 功能,例如设置配置值 实验发布系统或企业管理控制台。
标记状态会反映在设备指标中。对于使用 状态可以更改,则其值会包含在 额外的哈希,可用于将启用了功能的同类群组与 指标分析期间停用了功能的同类群组。
常见用例:产品/开发板/build 类型定制
Fuchsia 是一个通用型操作系统,可广泛应用于各种环境 不同的产品和设备类别。也就是说, 组件需要根据 或主板。
我们再看一个 Timekeeper 示例: UTC 维护算法需要知道 UTC 时间的 设备的振荡器,了解误差范围和权重的增长情况 连续样本进行计算。一些振荡器的准确性显著提高(并且 成本较高!),但目前还没有一种简单的方法可以表达 基于板的变体,因此振荡器误差目前硬编码为常量:
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;
与第一个用例一样,组件开发者可以引入这种可定制的 参数,并获得相同的实用属性。在本课中, 则清单不提供默认值(因为组件 并且不知道振荡器的好坏程度),因此组装 如果没有其他任何输入提供 值。
其他用例
前面部分讨论了 Cloud Storage 的两个常见用例, 结构化配置,但同一系统也可用于处理 其他简单的配置用例例如:
Inhibit-for-test 标志。有些组件会自然而然地表现出 使集成测试变得困难(例如,时间系统的 时间源故障后的一分钟冷却期)。仅供测试用的标志可用于 在集成期间或端到端测试期间阻止这些行为。
组件实例创建时配置。有时, 才能为组件确定适当的配置, 组件实例。可以定义配置键 让创建新的组件实例的父级提供配置值 创建过程。这都可以取代 launch args 的使用 支持根据不同需求定制一个组件的多个实例 角色(例如,为每个活跃账号启动一个 AccountHandler 实例)。
简单的 A/B 测试。打造最佳设计有时需要 同时尝试两个或多个选项,每个选项对应不同的一组 设备。可访问服务器端实验系统的商品在 使用此实验系统驱动配置值进行 A/B 测试 一个或多个组件
哲学
结构化配置的设计受以下 4 种一致的原则驱动:
- 简单。系统必须易于使用,并且设计得当 易于理解和分析的解决方案。简单易用,有利于推动客户采用 “可靠”和“安全”哲学。
- 可靠。可靠性简化了开发和调试工作, 系统用于对紫红色的运行至关重要的组件 设备。
- 安全。安全性直接切合 Fuchsia 的平台目标, 可让系统用于对设备 Fuchsia 设备。
- 可测试性。配置会向组件添加新的输入,因此 出现新问题的可能性。开发者必须能够对 组件对这些新输入的响应。
范围
结构化配置并非适用于所有 配置问题。基于产品构建的产品的 Fuchsia 非常庞大,它们带来的要求往往相互冲突 - 系统 尝试满足所有需求的组织可能非常复杂(因此 简单的理念并威胁其他三种理念)或简单 而且过于宽泛(因此无法保证存在, 稳定性、意义和可审核性,是保证数据 和安全理念)。
结构化配置旨在解决一系列常见的应用场景 。组件可能会使用基于文件的常规解决方案和基于 API 的解决方案 以解决其他配置问题。具体来说,结构化配置 也不打算解决以下问题:
任意大且复杂的配置数据。庞大而复杂的数据 难以审核和有选择地限制使用, 与安全理念相冲突。这些数据通常需要额外的 特定领域的解释和验证,引入了故障模式, 将不知道配置系统最后,复杂的大型数据 难以将多个来源组合在一起,这就限制了 并且能够在系统运行期间覆盖配置 。
- 需要庞大而复杂的配置数据的组件应读取此配置文件 从文件提取数据
频繁更改的配置数据。频繁更改的数据 多次传送到一个组件,而不是仅传送一次。 组件启动。这引入了新的故障模式, 这与简单可靠的原则相冲突, 哲学。测试起来也要困难得多。请注意, 经常会违背我们对“配置”的定义。
- 需要频繁更改配置数据的组件 而是通过 FIDL 协议接收这些数据。
其他组件(父组件除外)设置的配置数据 和管理组件)。结构化配置支持父级组件 为其创建和支持的组件设置配置 管理组件,用于设置设备的所有可变配置。 结构化配置不提供 任意组件,在特定的环境中设置配置子集, 组件。
- 需要由 中的其他任意组件设置的配置数据集的组件 系统应改为通过 FIDL 协议接收此数据,并限制 使用服务路由进行访问
由最终用户控制的配置数据。配置 由最终用户(而不是开发者或管理员)控制,则需要 界面。此界面不支持“其他 组件"也可能无法通过“更改配置数据” 经常"测试。
- 由最终用户控制的配置应包含在
fuchsia.settings
,或采用类似方法 使用用于分隔前端和后端组件的 FIDL API。
- 由最终用户控制的配置应包含在
“配置”的含义
组件会使用各种不同的输入。其中的大部分信息 可能会改变组件的行为,但只有部分输入 被视为“组件配置”而不是更通用的“系统状态” 或“输入数据”。
对于此 RFC,我们以“配置”为例作为输入, 组件实例用来根据其所在的背景定制操作。 (例如产品、开发板、build 类型、渠道、监管区域或 集成测试领域)。配置值在生命周期内保持不变 组件实例的形式,在一些设备上通常会保持不变。 配置值通常由开发者、维护人员或 管理员而不是最终用户
数据类型
结构化配置适用于中等数量的有界限和 为每个组件定义的配置键。限制 以这种方式进行配置,有利于推动配置充分记录, 可测试、可变性极小且易于审核。它还支持 自动组合来自多个来源的配置。这些明确定义 键值对创建了“结构”“结构化配置”中
我们不打算支持字节或任意长度的字符串,这是由于 “任意大的复杂配置数据”约束条件。一组 最初支持的数据类型将在后续工作中定义,但将包含 包括布尔值、整数、有界限长度的字符串和 数据类型(列表长度有界限,且列表中的所有条目均为 以原子方式提供)。
我们非常希望在未来支持枚举,但现在更为复杂 因为所有验证配置值的地方(无论是在组装过程中还是在 运行时)必须有权访问一组有效的枚举器。开发者工具 如果系统可以在枚举器名称之间进行转换,则会更符合人体工程学 和值,但这会进一步增加复杂性。
支持可组合列表(即条目可通过 多个配置来源),但这会增加 大幅降低复杂性。使用处理配置键的原子类型 意味着只需替换其值,但可组合列表将需要更复杂的 执行附加、插入或移除值或合并列表等操作 fragment。这些操作在组装和 并且会创建新的故障模式,例如无法插入商品 因为这样做会超出列表长度上限。因此,系统需要 区分条目顺序不会影响 使用方(因此应使用 并列出条目顺序非常重要的情形(以及 因此,必须显式地执行配置操纵以维护 订单)。
组件框架版本
结构化配置仅支持组件框架 v2 组件。时间是 涉及许多领域的大型项目,而且尚未准备好广泛采用 直到 2022 年初。如果支持两个不同的框架, 扩大范围,并将结束日期推迟到 组件框架 v1。
设计
概览
该小节简要介绍系统的整体设计。通过 以下小节将更详细地介绍此处引入的各个步骤。
设计总结如下图所示:
可配置数据的每个元素都是一个键值对。组件作者声明了 组件的配置键(以及可选的默认值) 组件清单,以及构建系统和组件框架 负责向 start 的值。
构建和组装流程会生成一个配置定义文件 包含配置键数据类型和名称,以及用于 value 文件,其中包含每个配置键的值和可变性。在 这两个文件放在同一个 软件包作为组件安装(或者,对于运行 在 pkgfs 可用之前)。请参阅下面的替代方案 3 并讨论未来的发展方向。
每次启动组件实例时,组件框架都会检查 是否存在配置定义文件。如果某个配置定义 组件框架将 包含父组件提供的任何值的配置值文件 以及由配置覆盖服务提供的任何值 并遵循配置值文件中可变性限制条件通过 组件框架会将这些组合值传递给新的组件实例 。
该组件框架提供了新的 FIDL 接口,可供开发者工具或 产品组件可用于查询配置值和定义新的 替换。使用此界面设置的新值将在 启动组件实例。
配置值的统计信息包含在检查中,因此 包含在快照中,以协助调试。配置值的哈希值 与组装时间不同的值会通过 Cobalt 报告, 可以对具有不同配置值的设备同类群组进行评估, 相互独立。
配置定义
组件作者可以定义配置键(每个键都有一个数据类型和 可选的默认值)。未来的工作将定义 确切的语法,但从概念上讲,可能如下所示:
{
program: {
...
},
config: {
enable_frequency: {
type: "boolean",
default: "false"
},
oscillator_error_std_dev_ppm: {
type: "uint8"
}
},
...
}
在初始实现中,所有键都定义在单个“平面”中,命名空间
但我们将配置键中的合法字符限制为
[-_a-z0-9]
。嵌套或分组配置键是否有价值
将来可能会得到支持,并使用英文句点分隔
正斜线分隔语法。
某些运行时(例如 Cast 和 Web)会生成 CFv2 ComponentDecls 而不是从组件清单中添加。最初,这些运行时 不支持结构化配置。
在清单中添加配置部分会导致组件构建规则 生成包含该配置的 FIDL 表定义的库,并 在运行时根据提供给 由其运行程序决定然后,该组件的实现可将 此库中,并使用表中的字段控制其行为。
组件清单描述了组件的需求和
组件必须执行。在此 RFC 之前,*_binary
构建规则
在构建 fuchsia_component
时依赖于清单的内容
规则的二进制文件的可选依赖项。对构建规则进行的一些更改
来避免循环依赖
在某些情况下,一个二进制文件会由多个组件使用;这些情况 需要进行一些重构才能使用结构化配置。一种方法是 确保所有组件都通过包含函数定义相同的配置, 一个常见 CML 分片。另一种方法是将组件并入 单一定义,并使用结构化配置来描述 之前使用不同的清单表达的行为。
构建、组装和发布
构建、组装和发布流程负责生成 每个可配置组件的名称系统会将这两个文件放在同一个文件包中 组件(将来会扩展这一功能以支持提供值) 通过其他软件包获取,请参阅此替代方案。
配置定义文件
此文件包含每个配置键的以下信息:
- FIDL 字段编号
- 字段名称
- 数据类型
这些信息都可以在组件清单中找到,因此该文件可能 在组件构建流程中生成的项目同时计算 并为配置定义文件中的所有信息添加哈希 用作配置版本 ID。
请注意,我们将配置定义描述为“文件”来简化
但实施过程中可能包含
已编译的组件清单(即*.cm
文件)
而不是将其保存为单独的文件
配置值文件
此文件包含每个配置键的以下信息:
此 RFC 未定义该文件的格式。
在成熟且可扩展的汇编系统中,多个不同的参与者可能希望 为一个或多个键指定或限制此信息。例如: 组件作者、开发板初启工程师、平台边界负责人、产品团队 集成商或安全审核人员实现这一目标所需的一些工具包括 目前正在通过 DPI 和 SPAC 投入工作 平台路线图条目,并且此 RFC 没有指定这些工具将如何 来生成配置值文件。
在此期间,虽然结构化配置仅由少数 组件,我们将在 代码库如果基于树构建的产品需要修改 配置平台组件时,将通过将 是平台软件包中的配置值文件。
组装流程必须验证配置值的内容 文件与对应的配置定义文件保持一致,即 它们包含一组相同的字段编号,且数据类型为 保持一致。
VBMeta
最好使配置在从命令行生成的正式版本之间 相同的图像。例如,使用 进行签名时,可以启用调试功能 开发密钥在为正式版签名时保持停用状态。使用相同的 可降低生产和交付之间发生意外差异的可能性 并且有可能减少我们维护的映像数量。
将来,我们打算允许一些配置值 在 vbmeta 中被替换VBMeta 是 Fuchsia 的 启动时验证实现,并包含 推出 Fuchsia 版本。由于 vbmeta 已签名,之前未签名的配置值 会被 vbmeta 覆盖。
当可以创建多个版本时,由 vbmeta 进行的配置会带来更多价值 这些版本可能会表现出明显不同的 实用行为。而这又需要多个基础架构组件 已经与结构化配置集成,因此我们排除 vbmeta 的主配置。
组件启动
每次启动组件实例时,组件管理器都会解析 配置定义文件和配置值文件,并使用 内容来确定哪些来源可提供配置值。 下面将更详细地介绍以上每个来源。组件管理器 将来自允许的来源的贡献结合在一起,以生成最终的 配置值
确定配置值后,组件管理器会将
将这些值传递给运行程序。运行程序会将配置值传递给
新启动的组件。在很多情况下
我们希望这将传递一个包含句柄的新 procarg
添加到包含 FIDL 表的 VMO。在某些情况下,可能需要将
将配置指定为 key=value
命令行参数。
组件可以信任框架将始终提供配置 每个配置键在开始时为其清单中声明的配置键的值, 则会失败并显示严重错误。组件不得定义 内部默认值,以适应缺失的配置 - 这样做可以 会导致运行时错误,更改原本打算修复的行为 组装时间。
包含发布版本中的值
最简单的情况是,配置值文件中提供的值 。如果配置值文件声明组件的 配置键可由 ChildDecl 更改,或可通过替换 始终如此,组件管理器无需查询 配置替换服务
此流程如下所示:
包含 ChildDecl 中的值
我们扩展 ChildDecl
以包含
配置键值对,有效地取代了 CFv1 功能,
命令行参数。ChildDecl 可以
由向组件集合添加新实例的父级提供;
使用 Realm Builder 或由
父级组件清单。
当 ChildDecl 中存在配置时,组件管理器会执行以下操作:
- 验证 ChildDecl 键是否存在于配置定义文件中 且数据类型正确。
- 验证配置中的 ChildDecl 是否可修改 ChildDecl 键 值文件。
- 使用 ChildDecl 值填充提供的键。
- 使用配置值文件填充其余键。
组件管理器会记录一条信息性错误,并返回失败信息(如果有) 键未找到、包含错误的数据类型,或者键不可由 ChildDecl。
该流程由调用 CreateChild 的父组件启动,如图所示 如下:
使用来自替换服务的值
当配置值文件声明一个或多个配置键 “通过覆盖更改可变”,组件管理器会向配置发出 FIDL 请求 替换服务以获取替换值。该请求包含 实例 ID 和句柄 添加到配置定义文件中响应包含( 空)一组要应用的替换配置值。
配置覆盖服务的典型实现是 “配置覆盖管理器”但配置替换项 服务作为功能进行路由,通过组件拓扑(类似于 存储容量,并像本例中那样使用字符串参数化), 拓扑的不同部分可以使用由 替换服务 API 的不同实现。
配置替换管理器会维护一个“已替换”数据库 配置键值对,可通过 FIDL 修改(如下所述)。每个条目 均在组件实例级别定义,并由 组件实例 ID(请参阅此替代方法,了解 额外的理由)。每个替换条目要么存储在磁盘上, 会在重启后持续存在,或在 当前重启。持久性在创建条目时指定。
收到配置替换请求时,配置替换 经理:
- 检查替换数据库中匹配的条目。
- 验证被替换的键是否存在于配置定义文件中 且数据类型正确。
- 返回匹配的键值对。
配置替换管理器会记录一条信息性错误,并删除 如果找不到键或数据类型不正确(例如 设置 配置覆盖)。
收到配置替换响应后,组件管理器会执行以下操作:
- 通过配置中的替换来验证被替换的键是否可变 值文件。
- 使用被替换的值填充被替换的键。
- 使用配置值文件填充其余键。
如果组件管理器未收到来自配置的有效响应 替换服务,则组件启动失败。
如果配置替换管理器实现了 配置替换服务,如下图所示:
请注意,配置键作为字符串存储在替换数据库中。通过 一组配置字段可能会随着组件的演变而经常更改, 但只要键名和数据类型,配置替换项就一直有效 保持不变作为优化,上次出现的配置版本 ID 和 字段编号也可以缓存在数据库中。
值选择摘要
将这些流程结合在一起后,组件管理器会为每个流程选择一个值 配置键,如下所示:
- 如果键因替换而可变,并且从 请使用此值。
- 否则,如果键可被 ChildDecl 更改,并且在 ChildDecl,请使用此值。
- 否则,使用配置值文件中的值。
配置 FIDL 接口和替换数据库
配置替换管理器提供了两项 FIDL 服务,可用于 与其替换数据库交互:
- 该服务可以:
<ph type="x-smartling-placeholder">
- </ph>
- 读取所有配置替换项。
- 创建和删除保留在内存中但不会保留的配置替换项 在配置替换管理器重启后保持不变。
- 该服务可以:
<ph type="x-smartling-placeholder">
- </ph>
- 读取所有配置替换项
- 删除所有配置覆盖
- 创建保存在内存中但不会持久保留的配置替换项 配置替换管理器重启后
- 创建存储在磁盘上并持续有效的配置覆盖 配置替换管理器重启
第一项服务不能对配置进行长期更改,并且 可能有助于自动端到端测试。这两项服务都属于敏感数据 其用量已列入许可名单。
我们将引入一个使用第二项服务的 ffx 插件, 在测试设备上查询和修改配置。一组配置 特定版本中可通过 FIDL 修改的密钥属于政策问题, 并且没有由此 RFC 定义,但我们认为,在 eng 版本中, 需要限制通过 FIDL 修改配置。
即使已根据配置验证替换数据库中的条目 定义文件,随着时间的推移,条目可能会失效, 组件已被移除或升级到包含不同 配置键。我们采用多种垃圾回收措施来解决这个问题:
- 替换数据库中的每个条目都有一个过期时间,过期后 将被删除。在生产系统中,我们会考虑设置到期时间 必填。
- FIDL 服务包含可轻松删除冗余条目的方法 包括删除组件实例的所有条目以及 整个数据库。
- 在未来的工作中,我们将调查接收来自软件的通知 在移除或升级软件包时提供,以实现相应的替换 可以删除或重新验证。
诊断
了解组件正在使用的配置对于调试非常重要 问题。将指标与运行不同操作系统的设备同类群组 对评估部分发布和 A/B 研究非常重要。
大多数设备上的大多数配置键都会使用组装期间设置的值。 对于此类应用,在发布应用时 可推断配置值。每当组件实例 则组件管理器会计算两个哈希值:一个“ChildDecl 配置”, 哈希"通过 ChildDecl 设置的所有配置键和值 “替换配置哈希”所有配置键和值 被替换设置。如果未设置任何字段,对应的哈希值将是 零。
如果正在使用的配置数量适中,则这些配置 哈希数据足以识别每个同类群组。如果大量 配置值(例如,开发者将任意值 网址)- 配置哈希可能不足以确定 但它们仍然表示搭载 与运行 与其对等方进行不同的配置
日志记录
组件管理器会记录每次组件计算的统计信息 但不记录原始配置值。配置覆盖 Manager 会记录所有 FIDL 请求以修改覆盖数据库。
检查并快照
组件管理器的检查数据包含有关 以帮助进行调试。其中包括 根据每个来源和配置哈希设置的配置值 。由于快照包含检查数据,因此 所有正在运行的组件实例的配置哈希都包含在 快照。
配置键对设备的运行和调试非常重要, 组件就可以选择包含这些配置 值。
钴蓝
组件管理器会将两个配置哈希发送到
start(通过运行程序)。我们扩展
fuchsia.metrics.MetricEventLoggerFactory
至
在创建新的 MetricEventLogger
时接受这些哈希。
Cobalt 将这些配置哈希与现有
SystemProfile
字段来定义运行组件的上下文,
允许分析来自具有不同配置的设备的指标
。标准阈值仍然适用,因此
当至少数量的设备共享相同配置时可用。
实现
我们将选择少量“抢先体验”作为客户端的组件 结构化配置的开发过程,而语法和工具 演变。
实施将分为三个阶段, 功能:
- 第 1 阶段:静态价值
<ph type="x-smartling-placeholder">
- </ph>
- 在此阶段后,您便可以创建配置定义 (可能使用的语法和工具可能并非最终版本),放置配置 值,并将这些值传送到位于 start 的值。
- 此阶段为抢先体验组件的行为提供了一种基本方式 因产品或版本类型而异
- 阶段 2:父值
<ph type="x-smartling-placeholder">
- </ph>
- 在此阶段之后,您还可以限制 封装的配置值,用于在 ChildDecl),并在启动时将这些值传递给组件实例。 配置的定义应通过组件清单和 使用接近最终语法的句点。
- 此阶段取消了对配置和用例进行集成测试的障碍 需要父级组件为其子级提供配置。
- 阶段 3:替换值
<ph type="x-smartling-placeholder">
- </ph>
- 在此阶段之后, 该接口会通过 FIDL 接口或使用该接口的 ffx 插件覆盖 在启动时将这些值传递给组件实例,以及 按 Cobalt 中的配置分离指标。
- 此阶段会完成此 RFC 中定义的工作,并解除对本地的阻止 开发者测试、端到端测试和进一步的产品集成。
性能
此 RFC 引入了一个新组件,会导致(适度)CPU、内存和存储空间 费用。所有这些模块都使用结构化的 以及配置键的数量
计算配置值会在启动 由于附加 FIDL,配置因被替换而可变的组件 调用和其他文件读取。我们会监控这笔费用,并优化 实现配置替换管理器(如有必要)。
安全
许多组件都可以使用结构化配置来控制 行为的不同方面能够修改配置的攻击者 等各种情况, 方法。例如:
- 启用调试输出以泄露用户信息。
- 将网络请求重定向到攻击者控制的服务器。
- 启用实验性功能,并利用 实施。
此设计包括若干旨在防止此类攻击的功能:
- 可配置数据的范围是有限的,每个元素都很好 因此更易于审核
- 版本经过签名后,系统会为每个配置键定义一个值, 值在经过验证的执行范围内。
- 已设置在系统运行时修改配置键的功能 当版本被签名时,系统会分别针对每个键和每个变更机制执行此操作。 验证执行涵盖了这些可变性。
- 可变性和默认值在组件一级定义,而不是在 组件实例级。具有不同属性的新组件实例 仅当 ChildDecl 可变或 替换值。
- 临时和永久更改配置的功能 作为单独的 FIDL 服务公开。
- 用于更改配置的 FIDL 服务由许可名单控制。
- 配置数据由经过审核的现有 FIDL 绑定解析 而不是按应用特有的逻辑
- 配置替换管理器会带来丰厚利润,因此我们将请求 安全审核。
隐私权
结构化配置不用于存储用户设置(此类 不同的稳定性、持久性、访问权限和时间需求) 值不得包含用户生成的数据。该要求会明确记录 。
对父级组件来说,将个人身份信息配置传递给 动态创建的子项(例如,硬件 UID 或网络地址) 新组件实例应使用的名称)。如果不小心,这类信息 通过 ChildDecl 配置哈希泄露到日志或指标中。这将是 在详细设计期间解决的问题,但有多个可用选项(例如, 例如,系统可能会在配置定义中标记敏感字段, 。
测试
验证多个配置值的能力非常重要 正确性。此设计支持在 所有阶段:
- 单元测试和组件测试可以手动构建 FIDL 配置结构体(即其下的组件运行程序提供的结构体) 正常操作),并将其传递给使用配置的方法。
- 集成测试可能会为每个组件实例提供配置
通过 Realm Builder(使用
ChildDecl
)进行构建。 - 端到端测试可能会通过主机设备设置其他配置 使用 FIDL 配置接口。您可能需要执行额外的操作才能 暂停正常的启动序列,以避免组件出现竞态条件 start 的值。
- 手动测试可以使用
ffx
命令方便地设置 使用配置 FIDL 接口进行配置。 - 未来工作将考虑对组件的 配置。
有几个不同的选项可用,以避免对 来自封闭集成测试的配置替换服务。例如: 你可以使用配置值文件来构建集成测试包 不允许替换和构建测试领域可以避免 配置覆盖服务。
将使用 标准最佳实践,包括单元测试和 组件管理器 - 配置替换管理器 - 运行程序互动。
文档
此 RFC 获得批准后,我们就会在 /docs/concepts
中发布一个文档。
介绍了 Fuchsia 上可用的配置机制及其
关系。
在语法稳定且结构化配置实现后 为进一步扩大应用范围,我们将发布开发者指南, 参考文档。
随着开发者开始使用结构化配置并发现 我们会为最佳实践和推荐风格编制文档。
考虑的备选方案
在配置替换管理器中实现配置合并
此 RFC 的早期修订版本放置了逻辑来合并不同的 组件替换管理器中的配置数据(然后命名为“配置”) 而不是组件管理器。
这种设计使得组件管理器的范围更小,但 组件管理器和配置管理器的范围 定义不太清晰:
- 组件管理器本来就需要充分了解配置 在不理解的情况下,知道何时调用配置管理器 足以合并值。
- 配置管理员将负责部分业务 除了 数据库维护
此设计还增加了 FIDL 调用。
在组件管理器中实现配置替换数据库
此 RFC 将负责维护配置替换 数据库:配置替换管理器。替代方案 应在组件管理器内执行该功能。
此替代方案消除了 FIDL 调用和一些故障模式, 会增加组件管理器的复杂性,并且会成为 第一次,组件管理器需要保留自己的数据,而不是简单地 分配存储空间供其他组件使用。这样使用存储空间 CF 会增加额外的安全问题并需要新的基础架构。
通过中央软件包提交配置
此 RFC 会将组件的配置值置于组件的 软件包。另一种方法是为所有的 组件,这类似于 config-data 软件包。
相较于行业解决方案,我们更倾向于分散式方法,主要有两个原因 集中式方法:
- 将来,Fuchsia 将需要运行 基础映像(例如,由于应用更新)。此配置 这些未知的组件无法在中央软件包中分发, 将需要不同的解决方案。
- 以原子方式提供二进制文件及其配置,让我们能够 特别是在 独立于基础映像进行更新。只有少数失败模式 会导致某个组件可用但其配置不可用 或者是属于某组件,且其配置不兼容。
正如 RFC 的正文中所述,我们的分散式方法目前 它们所应用到的组件所在的同一软件包中的配置值。 也就是说,在组装更改时更改组件配置 其软件包的根哈希。从长远来看,这是不可取的,因为它确实 不支持一个组织(例如 Fuchsia 平台维护人员) 发布和签署组件,以及其他组织(例如, 产品集成商)提供配置。
将来,我们认为每个软件包都将包含默认配置值 (由发布组织设置),并声明 值可能被 (例如,平台组件可以选择其哪个 配置“旋钮”可供产品集成商访问)。未来的工作 定义此“不同软件包”的性质- 选项包括元软件包、 辅助信息包或封装容器软件包
按组件网址和名称覆盖索引配置
此 RFC 将组件的 实例 ID。实例 ID 用于将 组件框架中的永久性资源,例如独立的永久性资源, 因此是索引配置的绝佳选择 替换。
不过,目前组件实例 ID 是在构建时手动分配的。 通过索引文件导入。这意味着实例 ID 因此结构 配置覆盖不适用于 由应用引入的组件集合或组件实例 而不是在基础映像中更新
作为替代方案,我们考虑了按组件覆盖索引编制配置 网址和名称。这样就避免了实例 ID 的限制,但 组件网址和名称可能会随着系统重构而改变 不仅会产生新的稳定性问题 组件框架永久性资源处理流程的不一致。
相反,我们希望通过将实例 ID 更改为 未来 RFC 中实例 ID 的设计。
在组件级别支持配置替换
此 RFC 将组件的 实例 ID,表示组件的每个实例 需要单独进行替换。
将来,您可能需要在组件级别支持替换项, 对组件实例级进行补充这对于 或组件被多次实例化时 无法预知实例
当前没有针对 我们可用于组件级替换的组件由于我们已经 尚未确定确实需要我们延迟实施组件级替换 包括该功能
上一个替代选项中引用的实例 ID 设计更改 除了组件实例之外,很有可能提供稳定的组件标识符 (例如,实例 ID 可以是 一个组件与实例名称的某种函数结合起来, Quirks 数据库来处理拓扑变更)。这样可以简化向 组件级替换。
使用 FIDL 定义配置键
此设计使用 JSON 定义组件清单中的配置键。
此信息的一部分用于构建 FIDL 表。另一种做法是
一直是在 .fidl
文件中定义配置键。
首先,请注意,FIDL 工具链是特意编写的, 由 IR 分隔的前端和后端 - 可以使用 FIDL 而无需以 FIDL 语言定义输入内容。
使用 .cml
(而非 .fidl
)主要由开发者决定
体验:
- 开发者可在组件清单中描述自己的需求 组件添加到框架中,并定义适合此框架的配置键 定义。与添加新文件相比,维护单个文件的工作量更小。
- 可用于定义配置的语法和数据类型是 完整 FIDL 语言的一小部分(请参阅范围)。 要求 FIDL 语法而仅支持一部分语法可能会让人感到困惑 而且令人沮丧
- 配置需要的信息无法在 FIDL 中表示 语言(当前表格字段的默认值,可能还有其他 限制)。这些信息可以存储在自定义 属性,但这与 FIDL 语言中的其他内容不一致 也让开发者感到困惑
JSON 配置定义使用 FIDL 中存在的概念的情况 我们将使用一致的语法。例如,数据类型名称为 保持一致。
cmc
已根据 JSON 组件清单构建 FIDL 表,并且
从清单解析配置所需的额外工作并不多。
支持组件之间的配置路由
组件框架中的许多资源都支持从一个组件路由 例如协议、目录和存储 功能。支持在 所以假设子级可以使用其配置中的某些配置值, 。
此初始设计不支持配置路由,以避免 引入新的版本控制挑战。如果我们支持在 一个组件,然后在打包到另一个位置的另一个组件中使用, (因为这些组件是在不同的代码库中定义的,或者 因为没有以单体式应用的形式交付到设备),我们无法再 可以保证编译到组件中的配置定义与 用于配置值的定义。我们将引入新的 ABI 但因为此接口在 我们不能使用 PDK 而不是 IDK, RFC-0002,用于管理版本兼容性。
组件途径和版本控制可能会作为 PDK 和 继续进行树组装工作,而我们推迟了配置路由, 直到更成熟。同时,组装工具 可用于在多个软件包中提供一致的配置值 。
我们针对这种跨组件兼容性推出了一个更受限的版本 通过支持父组件在对象上动态配置子组件, 创建过程。我们认为,这些情况近期不太可能造成问题 因为您必须选择启用此可变性,并且配置字段 通常由子级明确设计,以便使用父级。
支持对可重复使用的库进行透明配置。
许多组件是使用可重复使用的库构建的,这些库支持某种形式的 配置。此 RFC 中定义的结构化配置可用于 提供此配置,但仅以手动方式提供;每个组件使用 库将需要在它自己的清单中声明匹配的配置键 然后在初始化时将配置值路由到库中。答 运行程序也存在类似的情况,运行程序可以直接控制 它运行的组件使用的库中可配置的行为。
另一种方法是支持更“透明”的配置 库可以声明其配置密钥, 直接在运行时使用配置值组件只需声明 使用库让配置提供程序设置配置 该组件中的库实例的值。
与组件之间的路由一样(在上面的替代方案中讨论) 对于跨不同软件的配置密钥,需要获得更广泛的协议 工件,例如当外部树组件使用平台库时。 这可能会偏向于更正式的配置定义, 保证配置之间的向前和向后兼容性 版本。库的透明配置也会带来问题 如何向一个组件提供多组配置值的说明。
虽然透明的库配置是 从长远来看,我们将这项工作推迟到更简单、更“私密”的 此 RFC 中定义的配置系统是否正常运行。
支持在组件启动后更新配置
此设计仅在组件实例 。如果在组件启动后修改配置,该配置不会 直到下一次开始播放为止另一种方法是 通过 FIDL 定期配置为组件。
关注组件启动的决定与我们的 “配置”绑定到组件生命周期,也能简化 实现:
- 仅接收一次配置的组件实例可以初始化所有 配置驱动型资源一个组件 必须定期接收新配置 资源定期更新。
- 必须定期接收新配置的组件需要一些 通过定期或异步处理, 。
- 必须定期接收新配置的组件也必须记录
通过诊断连接发生的任何配置更改,例如
方法是创建新的
MetricEventLogger
。 - 必须定期接收新配置的组件具有额外的 必须处理的故障模式,例如 FIDL 终止 连接和超时。
目前,启动后更新配置的需求非常有限: 无法更改版本或通过 ChildDecl 提供的配置值 。FIDL 接口可在启动后引入更改,但 最初只会用在开发者工具中,这些工具能够轻松提供 在更改组件配置后自动重启组件。
将来,某些产品特定的组件可能会更倾向于实现 而无需重启。如果是这样,我们会考虑选择启用 让组件能够通过 FIDL 接收更新。