Skip to content

Commit

Permalink
api: add API_VERSIONING.md. (envoyproxy#8399)
Browse files Browse the repository at this point in the history
This captures the API versioning guidelines implied by
https://docs.google.com/document/d/1xeVvJ6KjFBkNjVspPbY_PwEDHC7XPi0J5p1SqUXcCl8/edit#heading=h.xgk8xel154p
and splits apart the Envoy API and internal implementation breaking change policies.

Some of the policy decisions (e.g. not allowing vNalpha to be hand edited, how we do manual breaking
changes, etc.) have been added to these guidelines based on recent experience with protoxform and
mechanical major version upgrade work, they are not part of the original stable API versioning work.

Risk level: Low
Testing: Formatting and docs build.

Fixes envoyproxy#8371

Signed-off-by: Harvey Tuch <[email protected]>
  • Loading branch information
htuch authored and nandu-vinodan committed Oct 17, 2019
1 parent 8d010da commit 5366b30
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 127 deletions.
68 changes: 26 additions & 42 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,66 +23,50 @@ maximize the chances of your PR being merged.

# Breaking change policy

* As of the 1.3.0 release, the Envoy user-facing configuration and APIs are
locked and we will not make breaking changes between official numbered
releases. This includes bootstrap configuration, REST/gRPC APIs (EDS, CDS, RDS,
etc.), and CLI switches. We will also try to not change behavioral semantics
(e.g., HTTP header processing order), though this is harder to outright
guarantee.
* We reserve the right to deprecate configuration, after two release cycles. For example, all
deprecations between 1.3.0 and 1.4.0 will be deleted soon AFTER 1.5.0 is tagged and released
(at the beginning of the 1.6.0 release cycle). This results in a three to six month window for
migrating from deprecated code paths to new code paths.
Both API and implementation stability are important to Envoy. Since the API is consumed by clients
beyond Envoy, it has a distinct set of [versioning guidelines](api/API_VERSIONING.md). Below, we
articulate the Envoy implementation stability rules, which operate within the context of the API
versioning guidelines:

* Features may be marked as deprecated in a given versioned API at any point in time, but this may
only be done when a replacement implementation and configuration path is available in Envoy on
master. Deprecators must implement a conversion from the deprecated configuration to the latest
`vNalpha` (with the deprecated field) that Envoy uses internally. A field may be deprecated if
this tool would be able to perform the conversion. For example, removing a field to describe
HTTP/2 window settings is valid if a more comprehensive HTTP/2 protocol options field is being
introduced to replace it. The PR author deprecating the old configuration is responsible for
updating all tests and canonical configuration, or guarding them with the
`DEPRECATED_FEATURE_TEST()` macro. This will be validated by the `bazel.compile_time_options`
target, which will hard-fail when deprecated configuration is used. The majority of tests and
configuration for a feature should be expressed in terms of the latest Envoy internal
configuration (i.e. `vNalpha`), only a minimal number of tests necessary to validate configuration
translation should be guarded via the `DEPRECATED_FEATURE_TEST()` macro.
* We will delete deprecated configuration across major API versions. E.g. a field marked deprecated
in v2 will be removed in v3.
* Unless the community and Envoy maintainer team agrees on an exception, during the
first release cycle after a feature has been deprecated, use of that feature
will cause a logged warning, and incrementing the
[runtime](https://www.envoyproxy.io/docs/envoy/latest/configuration/runtime#config-runtime)
runtime.deprecated_feature_use stat.
`runtime.deprecated_feature_use` stat.
During the second release cycle, use of the deprecated configuration will
cause a configuration load failure, unless the feature in question is
explicitly overridden in
[runtime](https://www.envoyproxy.io/docs/envoy/latest/configuration/runtime#config-runtime)
config. Finally during the third release cycle the code and configuration will be removed
entirely.
config. Finally, following the deprecation of the API major version where the field was first
marked deprecated, the entire implementation code will be removed from the Envoy implementation.
* This policy means that organizations deploying master should have some time to get ready for
breaking changes, but we make no guarantees about the length of time.
breaking changes at the next major API version. This is typically a window of at least 12 months
or until the organization moves to the next major API version.
* The breaking change policy also applies to source level extensions (e.g., filters). Code that
conforms to the public interface documentation should continue to compile and work within the
deprecation window. Within this window, a warning of deprecation should be carefully logged (some
features might need rate limiting for logging this). We make no guarantees about code or deployments
that rely on undocumented behavior.
* All deprecations/breaking changes will be clearly listed in the [deprecated log](docs/root/intro/deprecated.rst).
* High risk deprecations//breaking changes may be announced to the
* High risk deprecations/breaking changes may be announced to the
[envoy-announce](https://groups.google.com/forum/#!forum/envoy-announce) email list but by default
it is expected the multi-phase warn-by-default/fail-by-default is sufficient to warn users to move
away from deprecated features.
* Protobuf configuration in an alpha namespace, e.g. `v2alpha`, do not have any
restrictions on breaking changes. They may be freely modified, together with
their respective features.
* Configuration in the `v2` namespace are considered stable and subject to the
above policy. They are
[frozen](https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/v2_overview#status).
There will be no changes leading to wire incompatibility (as describe in the
[API style guide](api/STYLE.md)), however fields may be deprecated over time.
When a field is deprecated, it will follow the deprecation release cycle
described above.
* No configuration field or message in the `v2` namespace may be deprecated
unless there is a corresponding semantic equivalent available to replace it.
The litmus test is to imagine that a stateless translation tool exists that
could convert from the earlier API to the new API. A field may be deprecated
if this tool would be able to perform the conversion. For example, removing a
field to describe HTTP/2 window settings is valid if a more comprehensive
HTTP/2 protocol options field is being introduced to replace it. The PR author
deprecating the old configuration is responsible for updating all tests and
canonical configuration, or guarding them with the DEPRECATED_FEATURE_TEST() macro.
This will be validated by the bazel.compile_time_options target, which will hard-fail when
deprecated configuration is used.
* For configuration deprecations that are not covered by the above semantic
replacement policy, any deprecation will only take place after
community consultation on mailing lists, Slack and GitHub, over the period of
a minimum of two Envoy release cycles (~6 months). Cases where a feature is
outright deleted with no replacement will get an additional two Envoy release
cycles (~12 months) before removal.

# Release cadence

Expand Down
199 changes: 199 additions & 0 deletions api/API_VERSIONING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# API versioning guidelines

The Envoy project (and in the future [UDPA](https://github.com/cncf/udpa)) takes API stability and
versioning seriously. Providing stable APIs is a necessary step in ensuring API adoption and success
of the ecosystem. Below we articulate the API versioning guidelines that aim to deliver this
stability.

# API semantic versioning

The Envoy APIs consist of a family of packages, e.g. `envoy.admin.v2alpha`,
`envoy.service.trace.v2`. Each package is independently versioned with a protobuf semantic
versioning scheme based on https://cloud.google.com/apis/design/versioning.

The major version for a package is captured in its name (and directory structure). E.g. version 2
of the tracing API package is named `envoy.service.trace.v2` and its constituent protos are located
in `api/envoy/service/trace/v2`. Every protobuf must live directly in a versioned package namespace,
we do not allow subpackages such as `envoy.service.trace.v2.somethingelse`.

Minor and patch versions will be implemented in the future, this effort is tracked in
https://github.com/envoyproxy/envoy/issues/8416.

In everyday discussion and GitHub labels, we refer to the `v2`, `v3`, `vN`, `...` APIs. This has a
specific technical meaning. Any given message in the Envoy API, e.g. the `Bootstrap` at
`envoy.config.bootstrap.v3.Boostrap`, will transitively reference a number of packages in the Envoy
API. These may be at `vN`, `v(N-1)`, etc. The Envoy API is technically a DAG of versioned package
namespaces. When we talk about the `vN xDS API`, we really refer to the `N` of the root
configuration resources (e.g. bootstrap, xDS resources such as `Cluster`). The
v3 API bootstrap configuration is `envoy.config.bootstrap.v3.Boostrap`, even
though it might might transitively reference `envoy.service.trace.v2`.

# Backwards compatibility

In general, within a package's major API version, we do not allow any breaking changes. The guiding
principle is that neither the wire format nor protobuf compiler generated language bindings should
experience a backward compatible break on a change. Specifically:

* Fields should not be renumbered or have their types changed. This is standard proto development
procedure.

* Renaming of fields or package namespaces for a proto must not occur. This is inherently dangerous,
since:
* Field renames break wire compatibility. This is stricter than standard proto development
procedure in the sense that it does not break binary wire format. However, it **does** break
loading of YAML/JSON into protos as well as text protos. Since we consider YAML/JSON to be first
class inputs, we must not change field names.

* For service definitions, the gRPC endpoint URL is inferred from package namespace, so this will
break client/server communication.

* For a message embedded in an `Any` object, the type URL, which the package namespace is a part
of, may be used by Envoy or other API consuming code. Currently, this applies to the top-level
resources embedded in `DiscoveryResponse` objects, e.g. `Cluster`, `Listener`, etc.

* Consuming code will break and require source code changes to match the API changes.

* Some other changes are considered breaking for Envoy APIs that are usually considered safe in
terms of protobuf wire compatibility:
* Upgrading a singleton field to a repeated, e.g. `uint32 foo = 1;` to `repeated uint32 foo = 1`.
This changes the JSON wire representation and hence is considered a breaking change.

* Wrapping an existing field with `oneof`. This has no protobuf or JSON/YAML wire implications,
but is disruptive to various consuming stubs in languages such as Go, creating unnecessary
churn.

* Increasing the strictness of
[protoc-gen-validate](https://github.com/envoyproxy/protoc-gen-validate) annotations. Exceptions
may be granted for scenarios in which these stricter conditions model behavior already implied
structurally or by documentation.

The exception to the above policy is for API versions tagged `vNalpha`. Within an alpha major
version, arbitrary breaking changes are allowed.

Note that changes to default values for wrapped types, e.g. `google.protobuf.UInt32Value` are not
governed by the above policy. Any management server requiring stability across Envoy API or
implementations within a major version should set explicit values for these fields.

# API lifecycle

The API lifecycle follows a calendar clock. At the end of Q3 each year, a major API version
increment may occur for any Envoy API package, in concert with the quarterly Envoy release.

Envoy will support at most three major versions of any API package at all times:
* The current stable major version, e.g. v3.
* The previous stable major version, e.g. v2. This is needed to ensure that we provide at least 1
year for a supported major version to sunset. By supporting two stable major versions
simultaneously, this makes it easier to coordinate control plane and Envoy
rollouts as well. This previous stable major version will be supported for 1
year after the introduction of the new current stable major version.
* Optionally, the next experimental alpha major version, e.g. v4alpha. This is a release candidate
for the next stable major version. This is only generated when the current stable major version
requires a breaking change at the next cycle, e.g. a deprecation or field rename. This release
candidate is mechanically generated via the
[protoxform](https://github.com/envoyproxy/envoy/tree/master/tools/protoxform) tool from the
current stable major version, making use of annotations such as `deprecated = true`. This is not a
human editable artifact.

An example of how this might play out is that at the end of September in 2020, we will freeze
`envoy.config.bootstrap.v4alpha` and this package will become the current stable major version
`envoy.config.bootstrap.v4`. The `envoy.config.bootstrap.v3` package will become the previous stable
major version and support for `envoy.config.bootstrap.v2` will be dropped from the Envoy
implementation. Note that some transitively referenced package, e.g.
`envoy.config.filter.network.foo.v2` may remain at version 2 during this release, if no changes were
made to the referenced package.

The implication of this API lifecycle and clock is that any deprecated feature in the Envoy API will retain
implementation support for 1-2 years (1.5 years on average).

# New API features

The Envoy APIs can be [safely extended](https://cloud.google.com/apis/design/compatibility) with new
packages, messages, enums, fields and enum values, while maintaining [backwards
compatibility](#backwards-compatibility). Additions to the API for a given package should normally
only be made to the *current stable major version*. The rationale for this policy is that:
* The feature is immediately available to Envoy users who consume the current stable major version.
This would not be the case if the feature was placed in `vNalpha`.
* `vNalpha` can be mechanically generated from `vN` without requiring developers to maintain the new
feature in both locations.
* We encourage Envoy users to move to the current stable major version from the previous one to
consume new functionality.

# When can an API change be made to a package's previous stable major version?

As a pragmatic concession, we allow API feature additions to the previous stable major version for a
single quarter following a major API version increment. Any changes to the previous stable major
version must be manually reflected in a consistent manner in the current stable major version as
well.

# How to make a breaking change across major versions

We maintain [backwards compatibility](#backwards-compatibility) within a major version but allow
breaking changes across major versions. This enables API deprecations, cleanups, refactoring and
reorganization. The Envoy APIs have a stylized workflow for achieving this. There are two prescribed
methods, depending on whether the change is mechanical or manual.

## Mechanical breaking changes

Field deprecations, renames, etc. are mechanical changes that will be supported by the
[protoxform](https://github.com/envoyproxy/envoy/tree/master/tools/protoxform) tool. These are
guided by annotations in protobuf.
* Deprecations are specified with the built-in protobuf deprecated option set on a message, enum,
field or enum value. No field may be marked as deprecated unless a replacement for this
functionality exists and the corresponding Envoy implementation is production ready.

* Renames are specified with a `[#rename-at-next-major-version: <new name>]` protobuf comment
annotation.

* We anticipate that `protoxform` will also support `oneof` promotion, package movement, etc. via
similar annotations.

## Manual breaking changes

A manual breaking change is distinct from the mechanical changes such as field deprecation, since in
general it requires new code and tests to be implemented in Envoy by hand. For example, if a developer
wants to unify `HeaderMatcher` with `StringMatcher` in the route configuration, this is a likely
candidate for this class of change. The following steps are required:
1. The new version of the feature, e.g. the `NewHeaderMatcher` message should be added, together
with referencing fields, in the current stable major version for the route configuration proto.
2. The Envoy implementation should be changed to consume configuration from the fields added in (1).
Translation code (and tests) should be written to map from the existing field and messages to
(1).
3. The old message/enum/field/enum value should be annotated as deprecated.
4. At the next major version, `protoxform` will remove the deprecated version automatically.

This approach ensures that API major version releases are predictable and mechanical, and has the
bulk of the Envoy code and test changes owned by feature developers, rather than the API owners.
There will be no major `vN` initiative to address technical debt beyond that enabled by the above
process.

# One Definition Rule (ODR)

To avoid maintaining more than two stable major versions of a package, and to cope with diamond
dependency, we add a restriction on how packages may be referenced transitively; a package may have
at most one version of another package in its transitive dependency set. This implies that some
packages will have a major version bump during a release cycle simply to allow them to catch up to
the current stable version of their dependencies.

Some of this complexity and churn can be avoided by having strict rules on how packages may
reference each other. Package organization and `BUILD` visibility constraints should be used
restrictions to maintain a shallow depth in the dependency tree for any given package.

# Minimizing the impact of churn

In addition to stability, the API versioning policy has an explicit goal of minimizing the developer
overhead for the Envoy community, other clients of the APIs (e.g. gRPC), management server vendors
and the wider API tooling ecosystem. A certain amount of API churn between major versions is
desirable to reduce technical debt and to support API evolution, but too much creates costs and
barriers to upgrade.

We consider deprecations to be *mandatory changes*. Any deprecation will be removed at the next
stable API version.

Other mechanical breaking changes are considered *discretionary*. These include changes such as
field renames and are largely reflected in protobuf comments. The `protoxform` tool may decide to
minimize API churn by deferring application of discretionary changes until a major version cycle
where the respective message is undergoing a mandatory change.

The Envoy API structure helps with minimizing churn between versions. Developers should architect
and split packages such that high churn protos, e.g. HTTP connection manager, are isolated in
packages and have a shallow reference hierarchy.
5 changes: 0 additions & 5 deletions api/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,3 @@ The following are some general guidelines around documentation.
* Prefer *italics* for emphasis as `backtick` emphasis is somewhat jarring in our Sphinx theme.
* All documentation is expected to use proper English grammar with proper punctuation. If you are
not a fluent English speaker please let us know and we will help out.
* Tag messages/enum/files with `[#proto-status: draft|experimental|frozen]` to
reflect their [API
status](https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/v2_overview#status).
Frozen entities do not need to be tagged except when overriding an outer scope
draft or experimental status.
Loading

0 comments on commit 5366b30

Please sign in to comment.