Skip to content

Commit 181a207

Browse files
authored
outbound: synthesize client policies on Unimplemented (#2396)
If the policy controller is from a Linkerd version earlier than 2.13.x, it will return the `Unimplemented` gRPC status code for requests to the `OutboundPolicies` API. The proxy's outbound policy client will currently retry this error code, rather than synthesizing a default policy. Since 2.13.x proxies require an `OutboundPolicy` to be discovered before handling outbound traffic, this means that 2.13.x proxies cannot handle outbound connections when the control plane is on an earlier version. Therefore, installing Linkerd 2.13 and then downgrading to 2.12 can potentially break the data plane's ability to route traffic. In order to support downgrade scenarios, the proxy should also synthesize a default policy when receiving an `Unimplemented` gRPC status code from the policy controller. This branch changes the proxy to do that. A warning is logged which indicates that the control plane version is older than the proxy's.
1 parent ad4d5b6 commit 181a207

File tree

4 files changed

+25
-17
lines changed

4 files changed

+25
-17
lines changed

linkerd/app/core/src/errors.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@ pub use tonic::Code as Grpc;
99
#[derive(Debug, thiserror::Error)]
1010
#[error("connect timed out after {0:?}")]
1111
pub struct ConnectTimeout(pub(crate) std::time::Duration);
12+
13+
/// Returns `true` if `error` was caused by a gRPC error with the provided
14+
/// status code.
15+
#[inline]
16+
pub fn has_grpc_status(error: &crate::Error, code: tonic::Code) -> bool {
17+
cause_ref::<tonic::Status>(error.as_ref())
18+
.map(|s| s.code() == code)
19+
.unwrap_or(false)
20+
}

linkerd/app/gateway/src/discover.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@ impl Gateway {
2323
Error = Error,
2424
Future = impl Send + Unpin,
2525
> + Clone {
26-
#[inline]
27-
fn is_not_found(e: &Error) -> bool {
28-
errors::cause_ref::<tonic::Status>(e.as_ref())
29-
.map(|s| s.code() == tonic::Code::NotFound)
30-
.unwrap_or(false)
31-
}
32-
3326
use futures::future;
3427

3528
let allowlist = self.config.allow_discovery.clone();
@@ -71,7 +64,14 @@ impl Gateway {
7164
// names that are not Services, so we will get a `NotFound`
7265
// error if we looked up a pod DNS name. In this case, we
7366
// will synthesize a default policy.
74-
Err(error) if is_not_found(&error) => tracing::debug!("Policy not found"),
67+
Err(error) if errors::has_grpc_status(&error, tonic::Code::NotFound) =>
68+
tracing::debug!("Policy not found"),
69+
// Earlier versions of the Linkerd control plane (e.g.
70+
// 2.12.x) will return `Unimplemented` for requests to the
71+
// OutboundPolicy API. Log a warning and synthesize a policy
72+
// for backwards compatibility.
73+
Err(error) if errors::has_grpc_status(&error, tonic::Code::Unimplemented) =>
74+
tracing::warn!("Policy controller returned `Unimplemented`, the control plane may be out of date."),
7575
Err(error) => return Err(error),
7676
}
7777

linkerd/app/outbound/src/discover.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,13 @@ impl<N> Outbound<N> {
108108
// XXX(ver) The policy controller may (for the time being) reject
109109
// our lookups, since it doesn't yet serve endpoint metadata for
110110
// forwarding.
111-
Err(error) if is_not_found(&error) => tracing::debug!("Policy not found"),
111+
Err(error) if errors::has_grpc_status(&error, tonic::Code::NotFound) => tracing::debug!("Policy not found"),
112+
// Earlier versions of the Linkerd control plane (e.g.
113+
// 2.12.x) will return `Unimplemented` for requests to the
114+
// OutboundPolicy API. Log a warning and synthesize a policy
115+
// for backwards compatibility.
116+
Err(error) if errors::has_grpc_status(&error, tonic::Code::Unimplemented) =>
117+
tracing::warn!("Policy controller returned `Unimplemented`, the control plane may be out of date."),
112118
Err(error) => return Err(error),
113119
}
114120

@@ -143,13 +149,6 @@ impl<N> Outbound<N> {
143149
}
144150
}
145151

146-
#[inline]
147-
fn is_not_found(e: &Error) -> bool {
148-
errors::cause_ref::<tonic::Status>(e.as_ref())
149-
.map(|s| s.code() == tonic::Code::NotFound)
150-
.unwrap_or(false)
151-
}
152-
153152
pub fn spawn_synthesized_profile_policy(
154153
mut profile: watch::Receiver<profiles::Profile>,
155154
synthesize: impl Fn(&profiles::Profile) -> policy::ClientPolicy + Send + 'static,

linkerd/app/outbound/src/policy/api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl Recover<tonic::Status> for GrpcRecover {
112112
// Non-retryable
113113
tonic::Code::InvalidArgument | tonic::Code::FailedPrecondition => Err(status),
114114
// Indicates no policy for this target
115-
tonic::Code::NotFound => Err(status),
115+
tonic::Code::NotFound | tonic::Code::Unimplemented => Err(status),
116116
_ => {
117117
tracing::debug!(%status, "Recovering");
118118
Ok(self.0.stream())

0 commit comments

Comments
 (0)