RFC-0242: Configuration Capabilities

RFC-0242: Configuration Capabilities
StatusAccepted
Areas
  • Component Framework
Description

Adding Configuration Capabilities to the Component Framework

Gerrit change
Authors
Reviewers
Date submitted (year-month-day)2024-02-05
Date reviewed (year-month-day)2024-04-11

Summary

Outline the use of configuration capabilities in the component framework and the syntax that will be added to CML files to support them.

It should be noted that this document supercedes parts of the earlier Structured Configuration RFCs, which are mentioned in the prior arts section.

Motivation

Components in Fuchsia use structured configuration to get configuration values. Today configuration values are mostly loaded out of a component's package. It is possible for a parent component to imperatively set configuration values when launching a dynamic component, but most components in Fuchsia are static.

There are multiple motivations for adding this feature:

  • Allowing a static parent to configure a child's config.
  • Routing a single config value to multiple components.
  • Providing configuration values from a CML file.

The first customer for Configuration Capabilities is system assembly, as this feature will make it possible to remove technical debt. The technical debt exists because there is currently no way to provide configuration to a static component besides editing that component's package. System assembly currently opens fuchsia packages, edits their configuration value files, and then re-packages them. This is technical debt as it changes the hash value of the package and the config value file is technically a "private" API of the component that is not easily evolvable.

For out of tree packages that cannot be rebuilt, configuration capabilities provides a way for those components to be configured. There is currently no way to edit those component's configurations within a static topology.

This feature will likely be useful in many future configuration use cases.

Stakeholders

Facilitator: neelsa@google.com

Reviewers:

  • System Assembly: aaronwood@google.com
  • Component Framework: geb@google.com
  • Security: markdittmer@google.com
  • Driver Framework: surajmalhotra@google.com
  • Component Framework: wittrock@google.com

Socialization: This issue has been discussed with the Component Framework team before writing the RFC.

Requirements

Configuration Capabilities allows CML authors to set the configuration values of static components. They allow for the routing of configuration values through the topology so that multiple components can use the same value. Routing values through the topology allows indirect ancestors to specify values, where the existing feature only allows direct parents to specify values.

Configuration Capabilities should follow existing Component Framework features when possible to be consistent with the rest of the system.

Design

The bulk of the design is CML syntax for declaring and routing a configuration capability. A configuration capability has both its type and value defined in CML. A configuration capability is routed to a component that wishes to use it. The component using the configuration capability will be able to see the value when the component is started by using the existing structured configuration libraries.

Below is an example for defining a configuration capability:

// Configuration capabilities can be defined in any component.
// When a configuration capability is defined, a value must be set for it.
capabilities: [
  {
    // Define the name of the capability.
    config: "fuchsia.netstack.UseNetstack3",
    // Define the type of the configuration.
    type: "bool",
    // When defining a configuration capability, there *must* be a value.
    // If the route for the configuration capability ends at this definition, the
    // value of the capability will always be this value.
    value: "true"
  },
  // The below shows a string config.
  {
      config: "fuchsia.config.MyString",
      type: "string",
      max_size: 100,
      value: "test",
  },
  // The below shows a vector config.
  {
      config: "fuchsia.config.MyUint8Vector",
      type: "vector",
      element: { type: "uint8" },
      max_count: 100,
      value: [1, 2, 3 ],
  },
]

The type, element, max_count, max_size fields on the config capability have identical meanings to their use in the config stanza. All values that are supported in the config stanza are supported in the capabilities block.

The type field supports the following:

  • bool
  • uint8, uint16, uint32, uint64
  • int8, int16, int32, int64
  • string
  • vector

The using block supports the same type, element, max_count, and max_size fields as the capabilities block.

Below is example syntax for using a configuration capability:

// Using a configuration capability means that this configuration will show
// up in the component's structured config at runtime with the specified `key`
// name.
// NOTE: Using a configuration capability will override the value in the Config
// Value File (CVF) and the value obtained from the "mutability" setting.
use: [
  {
    config: "fuchsia.netstack.LaunchProcess",
    // This is the name that the component will see in its Structured Config
    // struct. If the `use` is optional this must match a name in the "config"
    // block of the component's manifest.
    key: "can_launch_process",
    // The using field needs the type information so that the component's
    // Config Value File format can be created.
    type: "bool",
    // From is identical to other capabilities.
    from: "parent",
  },
  // The below shows a vector config.
  {
    config: "fuchsia.netstack.MyUint8Vector",
    key: "my_vector",
    type: "vector",
    element: { type: "uint8" },
    max_count: 100,
    from: "parent",
  },
  // The below shows a string config.
  {
    config: "fuchsia.netstack.MyString",
    key: "my_string",
    type: "string",
    max_size: 256,
    from: "parent",
  },
]

Below is example syntax for routing a configuration capability:

// Expose and Offer behave the same as any other capability.
expose: [
  {
    config: "fuchsia.netstack.UseNetstack3",
    as: "fuchsia.mycomponent.UseVersion3",
    from: "self",
  },
]
offer: [
  {
    config: "fuchsia.config.MyString",
    as: "fuchsia.mycomponent.ProcessName",
    from: "self",
    to: "#mychild",
   },
]

Optional Capabilities

If a component is using a configuration capability with the availability set to optional, and that capability is being routed from void, then the component will receive the value from the Configuration Value File (CVF). This feature exists to support soft migrations for updating configurations.

Deprecation of the 'config' block

Before configuration capabilities, the config fields were declared in the config stanza in the CML. This information is now being replaced by the information in the use stanza. This means that the Structured Configuration schema that is generated for a component will be a union of the field declarations in the use block and the config stanza.

Once all structured config clients have been migrated to configuration capabilities, support for the config block in CML will be removed.

Deprecation of the 'mutability: parent' feature

Before configuration capabilities, a config value could be declared as mutability: parent, which would allow the parent of that component to change the value.

Users of this feature will be migrated to use a configuration capability from their parent. Once all users have been migrated, this feature will be removed.

Implementation

The implementation of these CML features is relatively straight forward and can be done in tree in a few CLs. Clients can migrate to using configuration capabilities at any time as this does not affect existing structured configuration features.

We will need to ensure Scrutiny understands the configuration capabilities and can verify that a specific product has a given configuration.

Performance

Starting a component may become slightly slower due to Component Framework needing to perform routing on the configuration capabilities a component uses. This could be significant if the capabilities are coming from a component that is unresolved and needs to downloaded. This should be negligible for resolved components.

Ergonomics

The ergonomics for this new CML capability are identical to existing capabilities. It is a bit verbose to add and route a configuration capability, but the syntax was chosen to match existing capability syntax as much as possible. Where possible, the syntax was also chosen to match the existing config block syntax. When the two syntax's conflicted, the existing capability syntax was preferred. Matching existing syntax reduces cognitive effort by users familiar with the component framework.

The CF team may decide to keep the config block as syntactic sugar for declaring a configuration capability, or add additional syntactic sugar if we need better ergonomics.

Backwards Compatibility

This is a new feature. It is backwards compatible with existing structured configuration features.

It is worth noting that routing a configuration capability to a component will always take precedence over the value in the CVF file. If a component has an optional configuration capability route and the route is not present, then the value in the CVF file will be used.

Security considerations

This feature should not impact security. Scrutiny and other tools to investigate CML will work on this new capability type.

It could be argued that setting capabilities in this way will be more visible than the existing strategy of editing values within a component's package.

If a component is using a required configuration capability and one is not routed to it, the component will be unable to start. This should not be a security problem because the parent of a component is trusted, and these routes can be statically verified to be correct.

Privacy considerations

This proposal should have no impact on privacy.

Testing

This feature will need unit and integration tests. The general testing areas are:

  • Parsing the new CML syntax.
  • Routing the new capability through the topology.
  • Attempting to start a component with missing configuration capabilities.
  • Attempting to start a component with a configuration capability of the wrong type.
  • Starting a dynamic component with a configuration capability.

The feature will need the following tests added to Scrutiny:

  • Testing that Scrutiny can resolve values for configuration capabilities.
  • Testing precedence/override logic for different sources of config values.
  • Testing interactions between configuration capabilities, the config block, and mutability.

None of these tests require new test infrastructure.

Documentation

Fuchsia.dev will need to be updated to have documentation on configuration capabilities. The existing capability docs will be used as a template for this new documentation.

Drawbacks, alternatives, and unknowns

Alternative: Implement a configuration override service

The original Structured Configuration RFC proposed that structured config is directly sourced from a component's package. The RFC specifically calls out that it is not attempting to address "configuration data that is set by other components (except parent components and admin components)". One alternative is to continue to not support a capability based configuration scheme.

One of the drawbacks to Configuration Capabilities instead of the override service is that Configuration Capabilities is more verbose and more complicated. Implementing something globally is always going to be simpler initially. However, Fuchsia has found that capability based systems end up being more composable and understandable in the long run. It is very helpful to be able to say that a specific configuration is only used by a subset of the component topology, or to use two different values within two different components. Configuration Capabilities make this easier to express and embodies the capability based system used elsewhere in Fuchsia. Using the existing CML syntax for configuration allows the feature to be understood more easily by fuchsia developers, and it composes well with component framework's existing tools.

Drawback: Many additional use fields

Keeping the type information in the use block adds 6 additional fields which are only valid for configuration capabilities.

It would be possible to combine these 6 fields into one field with subfields, but adding the fields was preferable as it kept the syntax close to the existing config block syntax.

Drawback: Conflating type information and routing

One drawback to putting the configuration schema in the use declaratio is that the use block defines the type information and also defines routing. Some components may wish to define their configuration without specifying routing, and then route their configuration differently in different scenarios. This is simpler if the type information is in a different place than the Use block.

Component authors that want to change how their types are defined must modify their use blocks instead of only modifying their config blocks.

If the combination of configuration definition and routing becomes a persistent pain point for developers, then Component Framework team may revisit this decision.

Alternative: Keep configuration type definition in the config block

One alternative is to not have a "type" field in the use declaration, and to instead rely on the type information in the existing "config" block. This was the case in the earlier versions of this RFC. This alternative addresses the current drawback of conflating type information and routing. It makes it easier for clients to define configuration in one place and to route it in another place (possibly including different CML shards to route differently based on different configurations).

In the current Structured Configuration-based implementation in which Component Manager "pushes" configuration to the component when launching it, Component Manager needs to ensure that the routed capability's type matches that of the target Structured Configuration field. It was decided that the type information would be placed on the use declaration because this is symmetric with the Capability Declaration. When Component Manager performs routing it needs to ensure that the type information lines up, which means that logically the information should be contained at both ends (Capability and Use) of the route.

Another reason for keeping the type information on the use declaration is that it becomes possible to keep all of the information in the use declaration and not require a matching "config" block entry. This keeps the information in a single location in the CML file, which makes it easier to audit.

Alternative: as field in use declaration

With the Structured Configuration field name and type information encapsulated within the config block, as could be used to specify the field to which the capability is being provided. This would be consistent with how most non-path "renaming" is done in CML.

The key term was used instead because the field operates differently than other as fields. as is normally optional, but key is required. key must also match an existing key in the config block if the capability is optional.

Unknown: Balancing existing Structured Config inconsistencies with Capabilities

There are a number of ways that the existing Structured Configuration is inconsistent with how other Capabilities work.

One inconsistency is that the program is deeply dependent on the format of the Structured Configuration that is defined in the component manifest. Normally it is the manifest that depends on the program, and Structured Configuration makes this relationship circular. This is visible in the fact that the Structured Configuration build rules require adding an extra layer between the component, component manifest, and the program. This circular inconsistency will not be addressed by Configuration Capabilities, but it should hopefully be addressed in a followup RFC.

Another inconsistency is that Structured Configuration is "pushed" to the component when it is started, where other capabilities are "pulled" as the component accesses them. This inconsistency is also not addressed in this RFC. The purpose of this RFC is to add routing and capability support within Component Manifests. This RFC does not change the interface between a program and Structured Configuration, so that the number of migrations needed is limited.

Fuchsia will be in a better place to address these inconsistencies after implementing Configuration Capabilities and seeing how they are used. More client data will help us address pain points and implement followup changes.

Prior art and references

Prior structured configuration rfcs: