blob: 082fd1a355b9bac0190e51cd39f8c56c96ca70ba [file] [log] [blame] [view]
# Availability
To select what capabilities go in its [namespace][glossary.namespace], a
component specifies a [`use`][docs.use] declaration for each of those
capabilities in its [manifest][docs.component-manifests]. However, since
[capability routing][docs.capability-routing] involves a chain of components,
just because a component `use`s a capability doesn't mean it will always be
available. It can be unavailable for various reasons: for example, if one of the
components in the chain didn't route the capability, or if one of those
components failed to resolve.
When a component `use`s or routes a capability, it may have various expectations
around whether the capability must be available. In some cases, a capability is
essential to a component's operation: if someone creates a topology is that
fails to route the capability to a component, something should ideally detect
that failure as early as possible to prevent a bug. However, in other cases, the
component may be tolerant of the capability's absence, or the capability may be
expected to be absent in certain configurations. In order to accommodate these
different scenarios, [cml][docs.cml] provides an `availability` option on
[`use`][docs.use], [`offer`][docs.offer], and [`expose`][docs.expose] which the
component can use to declare its expectation about a capability's availability.
## Options
The framework supports the following options for `availability`:
- [`required`](#required)
- [`optional`](#optional)
- [`transitional`](#transitional)
- [`same_as_target`](#same-as-target)
### Required
The most commonly used `availability` option is `required`, which indicates that
the component always expects the capability to be available, and cannot be
expected to function properly if it's absent. This is the default option that is
automatically set if no availability is specified.
### Optional
`optional` signifies that a capability may not be present on some
configurations. If a component `use`s an optional capability that turns out to
be unavailable, the component should be able to function without it, for example
by deactivating any features requiring that capability.
However, if an `optional` capability is not available on some configuration, the
framework expects that the topology still includes a complete route for the
capability that terminates in [`void`][docs.offer]. In this way, the product
owner is expected to make an explicit choice that either the capability is
available (its route terminates at some provider component) or it is not (its
route terminates at `void`). If the route is incomplete,
[tooling and diagnostics](#tooling-and-diagonstics) will report this as an
error.
### Transitional
`transitional` is like a weaker version of `optional`. Like `optional`, if a
component uses a `transitional` capability, it should be tolerant to the absence
of the capability. However, unlike `optional`, a `transitional` route does not
need to terminate in `void` and
[tooling and diagnostics](#tooling-and-diagonstics) will not report any error if
a `transitional` route is incomplete or invalid.
The name "transitional" connotes the idea that this option is useful for soft
transitions. For example, suppose you have an `Echo` protocol, that you want to
replace with `EchoV2`. At an early step in this transition, you can change the
client to `use` the new protocol with `transitional` availability. Later, once
an end-to-end route has been established, you can upgrade the availability to
`required` or `optional`. If you had tried to use `optional` instead,
[tooling and diagnostics](#tooling-and-diagnostics) would complain that the
client component's parent was missing a capability route for `Echo2`, while
`transitional` will suppress such warnings.
### Same as target
`same_as_target` acts as a passthrough: it causes the availability to be
inherited from whatever availability was set by the route's target component.
This is useful for intermediate components that pass a lot of capabilities
through, so that when the `availability` of a route is changed in the target, no
change is required in the source.
Because the component that `use`s a capability is its final destination,
`same_as_target` is not a valid option for `use`.
## Availability comparisons
There is a relative ordering of "strength" between the availability options.
From strongest to weakest, the order is: `required > optional > transient`.
`same_as_target` is not part of this because it's a passthrough: the strength of
a `same_as_target` capability route is effectively equal to whatever
availability it inherited from the target.
It is valid, and useful in many cases, to *downgrade* the availability from the
source to the target. For example, if component `X` exposes a `required`
capability to `Y`, `Y` may choose to `use` the capability as `optional`, or
`offer` the capability as `optional` to another child. However, it is a routing
error to *upgrade* the availability from the source. If this happens, any
attempt to use or route the capability will fail, in the same way it would fail
if the routing chain were incomplete. This is an error because it means you are
trying to establish stronger guarantees on the availability than what the source
has guaranteed. For example, it doesn't make sense for a component to `use` a
capability as `required` if its parent `offer`ed the capability as `optional`,
since that means the parent has declared the capability might not be available.
## Tooling and diagnostics
The main effect of setting availability is to control how host tooling and
diagnostics reports routing errors. In a nutshell, this means the weaker the
`availability`, the less strict they will be. Here's the detailed specification:
- If a `required` capability route is incomplete, invalid, or ends in `void`:
- [Scrutiny][src.scrutiny] will report this as a routing error. This will
cause a build error if the build configuration required this to pass.
- `component_manager` will log a `WARNING` message in the using
component's scope describing the routing error.
- If an `optional` capability route is incomplete or invalid:
- [Scrutiny][src.scrutiny] will report this as a routing error. This will
cause a build error if the build configuration required this to pass.
- `component_manager` will log an `INFO` message in the using component's
scope describing the routing error.
- If an `optional` capability route is ends in `void`:
- [Scrutiny][src.scrutiny] will not report an error.
- `component_manager` may log an `INFO` message, but no error.
- If a `transitional` capability route is incomplete, invalid, or ends in
`void`:
- No error or other information is logged.
For general runtime behavior, `availability` has no effect. For example, suppose
a component attempts to `use` a protocol in its namespace whose route is broken.
The end result will be the same if the protocol was routed as `required` or
`optional`:
- The protocol will appear in the component's namespace.
- The initial call to connect to the capability will succeed (assuming a
standard one-way API is used like
[connect_to_protocol][src.connect_to_protocol] in rust).
- `component_manager` will attempt to route the capability. In the end,
routing will fail, and the channel will be closed with a `NOT_FOUND`
[epitaph][docs.epitaph].
## source_availability: unknown
[cml][docs.cml] has an additional feature which can be used to autogenerate
either a `required` or `optional void` `offer`, depending on whether a
[cml shard][docs.includes] is included that has a child declaration for the
source. More information on this feature is in the
[build docs][docs.inter-shard-deps].
## Example
The following example illustrates the concepts described in this doc.
In this example, there are three components:
- `echo_client`, which tries to consume various protocols provided by
`echo_server`
- `echo_server`, which provides some protocols
- `echo_realm`, the parent of `echo_client` and `echo_server` which links them
together
Let's examine their cml files:
```json5
// echo_client.cml
{
...
use: [
{ protocol: "fuchsia.example.Echo" },
{
protocol: "fuchsia.example.EchoV2",
availability: "transitional",
},
{
protocol: "fuchsia.example.Stats",
availability: "optional",
},
],
}
// echo_server.cml
{
...
capabilities: [
{ protocol: "fuchsia.example.Echo" },
],
expose: [
{
protocol: "fuchsia.example.Echo",
from: "self",
},
],
}
// echo_realm.cml
{
offer: [
{
protocol: "fuchsia.example.Echo",
from: "#echo_server",
to: "#echo_client",
availability: "required",
},
{
protocol: "fuchsia.example.Stats",
from: "void",
to: "#echo_client",
availability: "optional",
},
],
children: [
{
name: "echo_server",
url: "echo_server#meta/echo_server.cm",
},
{
name: "echo_client",
url: "echo_client#meta/echo_client.cm",
},
],
}
```
Recall that when `availability` is omitted, it defaults to `required`. With this
topology, the behavior is as follows:
- `echo_client` will be able to successfully connect to
`fuchsia.example.Echo`.
- `echo_client` would fail to connect to `fuchsia.example.EchoV2` or
`fuchsia.example.Stats`.
- Tooling and diagnostics will not log an error.
- No error for `fuchsia.example.Stats` because it's `optional` and routes
to `void`.
- No error for `fuchsia.example.Stats` because it's `transient`.
Now, let's see what happens with a different version:
```json5
// echo_client.cml
{
...
use: [
{ protocol: "fuchsia.example.Echo" },
{
protocol: "fuchsia.example.EchoV2",
availability: "transitional",
},
{
protocol: "fuchsia.example.Stats",
availability: "optional",
},
],
}
// echo_server.cml
{
...
capabilities: [
{
protocol: [
"fuchsia.example.Echo",
"fuchsia.example.EchoV2",
"fuchsia.example.Stats",
],
},
],
expose: [
{
protocol: [
"fuchsia.example.Echo",
"fuchsia.example.EchoV2",
],
from: "self",
},
{
protocol: "fuchsia.example.Stats",
from: "self",
availability: "optional",
},
],
}
// echo_realm.cml
{
offer: [
{
protocol: [
"fuchsia.example.Echo",
"fuchsia.example.EchoV2",
],
from: "#echo_server",
to: "#echo_client",
availability: "same_as_target",
},
{
protocol: "fuchsia.example.Stats",
from: "#echo_server",
to: "#echo_client",
availability: "optional",
},
],
children: [
{
name: "echo_server",
url: "echo_server#meta/echo_server.cm",
},
{
name: "echo_client",
url: "echo_client#meta/echo_client.cm",
},
],
}
```
Now:
- `echo_client` will be able to successfully connect to
`fuchsia.example.Echo`, `fuchsia.example.EchoV2`, and
`fuchsia.example.Stats`.
- Each route, while having different `availability`, is complete and
terminates in a real component.
- For each route, availability between source and target passes the
[comparison check](#availability-comparisons)
- Tooling and diagnostics will not log an error, because all routes complete.
[docs.cml]: https://fuchsia.dev/reference/cml
[docs.use]: https://fuchsia.dev/reference/cml#use
[docs.offer]: https://fuchsia.dev/reference/cml#offer
[docs.epitaph]: /docs/reference/fidl/language/language.md#protocols
[docs.includes]: https://fuchsia.dev/reference/cml#include
[docs.expose]: https://fuchsia.dev/reference/cml#expose
[docs.inter-shard-deps]: /docs/development/components/build.md#inter-shard-dependencies
[docs.capability-routing]: /docs/concepts/components/v2/capabilities/README.md#routing
[docs.component-manifests]: /docs/concepts/components/v2/component_manifests.md
[glossary.namespace]: /docs/glossary/README.md#namespace
[src.connect_to_protocol]: /src/lib/fuchsia-component/src/client.rs
[src.scrutiny]: /src/developer/ffx/plugins/scrutiny/verify/README.md