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

已咨询: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 字典。该 字典是由键和类型组成的配置字段。

配置键具有以下属性:

  • 它们是组件配置架构中的唯一标识符
  • 它们用于系统汇编和处理替换项 <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
  • 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 部分。

档案管理员

当前配置拆分到命令行参数之间 以及与组件打包在一起的 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

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 会按确定顺序对配置字段进行排序,但该顺序未指定 。使用确定性顺序可确保架构校验和下游的架构保持一致 工具和运行时配置解析不指定顺序可为 优化。

架构校验和是通过 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 命令输出可能会发生变化。命令 在实现实际值的解析之前,将仅输出配置架构。

实现

此设计的实现将分三个增量阶段完成。

  1. cm_rust 添加新类型,在功能标志后面解析 cmc 中的配置节 (原型
  2. 使用 Hub、集成测试公开配置(原型
  3. 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>
        },
    }
}
  • 优点:在 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>",
        ...
    },
],
  • 优点:更简洁。无样板“类型”或“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",
        ]
    }
}

我们将把这项工作推迟到可以证明该子组装系统不可用于 默认值。

复杂数据类型

未来,我们预计会添加对复杂类型的支持,例如 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 数据交换格式