Skip to content

Commit

Permalink
Refactor inbound policy index in preparation for grpc support (#12784)
Browse files Browse the repository at this point in the history
Refactor inbound policy index in preparation for grpc support.  No functional changes.  

Signed-off-by: Alex Leong <alex@buoyant.io>
  • Loading branch information
adleong authored Jul 1, 2024
1 parent f444a83 commit eed3943
Show file tree
Hide file tree
Showing 14 changed files with 589 additions and 577 deletions.
34 changes: 18 additions & 16 deletions policy-controller/core/src/inbound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ pub enum AuthorizationRef {
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum HttpRouteRef {
pub enum RouteRef {
Default(&'static str),
Linkerd(GroupKindName),
Resource(GroupKindName),
}

/// Describes how a proxy should handle inbound connections.
Expand Down Expand Up @@ -89,13 +89,15 @@ pub struct InboundServer {

pub protocol: ProxyProtocol,
pub authorizations: HashMap<AuthorizationRef, ClientAuthorization>,
pub http_routes: HashMap<HttpRouteRef, HttpRoute>,
pub http_routes: HashMap<RouteRef, InboundRoute<HttpRouteMatch>>,
}

pub type HttpRoute = InboundRoute<HttpRouteMatch>;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HttpRoute {
pub struct InboundRoute<M> {
pub hostnames: Vec<HostMatch>,
pub rules: Vec<HttpRouteRule>,
pub rules: Vec<InboundRouteRule<M>>,
pub authorizations: HashMap<AuthorizationRef, ClientAuthorization>,

/// This is required for ordering returned `HttpRoute`s by their creation
Expand All @@ -104,8 +106,8 @@ pub struct HttpRoute {
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HttpRouteRule {
pub matches: Vec<HttpRouteMatch>,
pub struct InboundRouteRule<M> {
pub matches: Vec<M>,
pub filters: Vec<Filter>,
}

Expand All @@ -117,15 +119,15 @@ pub enum Filter {
FailureInjector(FailureInjectorFilter),
}

// === impl InboundHttpRoute ===
// === impl InboundRoute ===

/// The default `InboundHttpRoute` used for any `InboundServer` that
/// The default `InboundRoute` used for any `InboundServer` that
/// does not have routes.
impl Default for HttpRoute {
impl Default for InboundRoute<HttpRouteMatch> {
fn default() -> Self {
Self {
hostnames: vec![],
rules: vec![HttpRouteRule {
rules: vec![InboundRouteRule {
matches: vec![HttpRouteMatch {
path: Some(PathMatch::Prefix("/".to_string())),
headers: vec![],
Expand All @@ -145,20 +147,20 @@ impl Default for HttpRoute {

// === impl InboundHttpRouteRef ===

impl Ord for HttpRouteRef {
impl Ord for RouteRef {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(Self::Default(a), Self::Default(b)) => a.cmp(b),
(Self::Linkerd(a), Self::Linkerd(b)) => a.cmp(b),
(Self::Resource(a), Self::Resource(b)) => a.cmp(b),
// Route resources are always preferred over default resources, so they should sort
// first in a list.
(Self::Linkerd(_), Self::Default(_)) => std::cmp::Ordering::Less,
(Self::Default(_), Self::Linkerd(_)) => std::cmp::Ordering::Greater,
(Self::Resource(_), Self::Default(_)) => std::cmp::Ordering::Less,
(Self::Default(_), Self::Resource(_)) => std::cmp::Ordering::Greater,
}
}
}

impl PartialOrd for HttpRouteRef {
impl PartialOrd for RouteRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
Expand Down
125 changes: 12 additions & 113 deletions policy-controller/grpc/src/inbound.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{routes, workload::Workload};
use crate::workload::Workload;
use futures::prelude::*;
use linkerd2_proxy_api::{
self as api,
Expand All @@ -10,16 +10,17 @@ use linkerd2_proxy_api::{
};
use linkerd_policy_controller_core::{
inbound::{
AuthorizationRef, ClientAuthentication, ClientAuthorization, DiscoverInboundServer, Filter,
HttpRoute, HttpRouteRef, HttpRouteRule, InboundServer, InboundServerStream, ProxyProtocol,
ServerRef,
AuthorizationRef, ClientAuthentication, ClientAuthorization, DiscoverInboundServer,
InboundServer, InboundServerStream, ProxyProtocol, ServerRef,
},
IdentityMatch, IpNet, NetworkMatch,
};
use maplit::*;
use std::{num::NonZeroU16, str::FromStr, sync::Arc};
use tracing::trace;

mod http;

#[derive(Clone, Debug)]
pub struct InboundPolicyServer<T> {
discover: T,
Expand Down Expand Up @@ -144,21 +145,23 @@ fn to_server(srv: &InboundServer, cluster_networks: &[IpNet]) -> proto::Server {
ProxyProtocol::Detect { timeout } => Some(proto::proxy_protocol::Kind::Detect(
proto::proxy_protocol::Detect {
timeout: timeout.try_into().map_err(|error| tracing::warn!(%error, "failed to convert protocol detect timeout to protobuf")).ok(),
http_routes: to_http_route_list(&srv.http_routes, cluster_networks),
http_routes: http::to_route_list(&srv.http_routes, cluster_networks),
},
)),
ProxyProtocol::Http1 => Some(proto::proxy_protocol::Kind::Http1(
proto::proxy_protocol::Http1 {
routes: to_http_route_list(&srv.http_routes, cluster_networks),
routes: http::to_route_list(&srv.http_routes, cluster_networks),
},
)),
ProxyProtocol::Http2 => Some(proto::proxy_protocol::Kind::Http2(
proto::proxy_protocol::Http2 {
routes: to_http_route_list(&srv.http_routes, cluster_networks),
routes: http::to_route_list(&srv.http_routes, cluster_networks),
},
)),
ProxyProtocol::Grpc => Some(proto::proxy_protocol::Kind::Grpc(
proto::proxy_protocol::Grpc::default(),
ProxyProtocol::Grpc => Some(proto::proxy_protocol::Kind::Http2(
proto::proxy_protocol::Http2 {
routes: http::to_route_list(&srv.http_routes, cluster_networks),
},
)),
ProxyProtocol::Opaque => Some(proto::proxy_protocol::Kind::Opaque(
proto::proxy_protocol::Opaque {},
Expand Down Expand Up @@ -323,107 +326,3 @@ fn to_authz(
authentication: Some(authn),
}
}

fn to_http_route_list<'r>(
routes: impl IntoIterator<Item = (&'r HttpRouteRef, &'r HttpRoute)>,
cluster_networks: &[IpNet],
) -> Vec<proto::HttpRoute> {
// Per the Gateway API spec:
//
// > If ties still exist across multiple Routes, matching precedence MUST be
// > determined in order of the following criteria, continuing on ties:
// >
// > The oldest Route based on creation timestamp.
// > The Route appearing first in alphabetical order by
// > "{namespace}/{name}".
//
// Note that we don't need to include the route's namespace in this
// comparison, because all these routes will exist in the same
// namespace.
let mut route_list = routes.into_iter().collect::<Vec<_>>();
route_list.sort_by(|(a_ref, a), (b_ref, b)| {
let by_ts = match (&a.creation_timestamp, &b.creation_timestamp) {
(Some(a_ts), Some(b_ts)) => a_ts.cmp(b_ts),
(None, None) => std::cmp::Ordering::Equal,
// Routes with timestamps are preferred over routes without.
(Some(_), None) => return std::cmp::Ordering::Less,
(None, Some(_)) => return std::cmp::Ordering::Greater,
};
by_ts.then_with(|| a_ref.cmp(b_ref))
});

route_list
.into_iter()
.map(|(route_ref, route)| to_http_route(route_ref, route.clone(), cluster_networks))
.collect()
}

fn to_http_route(
reference: &HttpRouteRef,
HttpRoute {
hostnames,
rules,
authorizations,
creation_timestamp: _,
}: HttpRoute,
cluster_networks: &[IpNet],
) -> proto::HttpRoute {
let metadata = Metadata {
kind: Some(match reference {
HttpRouteRef::Default(name) => metadata::Kind::Default(name.to_string()),
HttpRouteRef::Linkerd(gkn) => metadata::Kind::Resource(api::meta::Resource {
group: gkn.group.to_string(),
kind: gkn.kind.to_string(),
name: gkn.name.to_string(),
..Default::default()
}),
}),
};

let hosts = hostnames
.into_iter()
.map(routes::convert_host_match)
.collect();

let rules = rules
.into_iter()
.map(
|HttpRouteRule { matches, filters }| proto::http_route::Rule {
matches: matches
.into_iter()
.map(routes::http::convert_match)
.collect(),
filters: filters.into_iter().filter_map(convert_filter).collect(),
},
)
.collect();

let authorizations = authorizations
.iter()
.map(|(n, c)| to_authz(n, c, cluster_networks))
.collect();

proto::HttpRoute {
metadata: Some(metadata),
hosts,
rules,
authorizations,
}
}

fn convert_filter(filter: Filter) -> Option<proto::http_route::Filter> {
use proto::http_route::filter::Kind;

let kind = match filter {
Filter::FailureInjector(f) => Some(Kind::FailureInjector(
routes::http::convert_failure_injector_filter(f),
)),
Filter::RequestHeaderModifier(f) => Some(Kind::RequestHeaderModifier(
routes::convert_request_header_modifier_filter(f),
)),
Filter::ResponseHeaderModifier(_) => None,
Filter::RequestRedirect(f) => Some(Kind::Redirect(routes::convert_redirect_filter(f))),
};

kind.map(|kind| proto::http_route::Filter { kind: Some(kind) })
}
114 changes: 114 additions & 0 deletions policy-controller/grpc/src/inbound/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use super::to_authz;
use crate::routes;
use linkerd2_proxy_api::{inbound, meta};
use linkerd_policy_controller_core::{
inbound::{Filter, HttpRoute, InboundRouteRule, RouteRef},
IpNet,
};

pub(crate) fn to_route_list<'r>(
routes: impl IntoIterator<Item = (&'r RouteRef, &'r HttpRoute)>,
cluster_networks: &[IpNet],
) -> Vec<inbound::HttpRoute> {
// Per the Gateway API spec:
//
// > If ties still exist across multiple Routes, matching precedence MUST be
// > determined in order of the following criteria, continuing on ties:
// >
// > The oldest Route based on creation timestamp.
// > The Route appearing first in alphabetical order by
// > "{namespace}/{name}".
//
// Note that we don't need to include the route's namespace in this
// comparison, because all these routes will exist in the same
// namespace.
let mut route_list = routes.into_iter().collect::<Vec<_>>();
route_list.sort_by(|(a_ref, a), (b_ref, b)| {
let by_ts = match (&a.creation_timestamp, &b.creation_timestamp) {
(Some(a_ts), Some(b_ts)) => a_ts.cmp(b_ts),
(None, None) => std::cmp::Ordering::Equal,
// Routes with timestamps are preferred over routes without.
(Some(_), None) => return std::cmp::Ordering::Less,
(None, Some(_)) => return std::cmp::Ordering::Greater,
};
by_ts.then_with(|| a_ref.cmp(b_ref))
});

route_list
.into_iter()
.map(|(route_ref, route)| to_http_route(route_ref, route.clone(), cluster_networks))
.collect()
}

fn to_http_route(
reference: &RouteRef,
HttpRoute {
hostnames,
rules,
authorizations,
creation_timestamp: _,
}: HttpRoute,
cluster_networks: &[IpNet],
) -> inbound::HttpRoute {
let metadata = meta::Metadata {
kind: Some(match reference {
RouteRef::Default(name) => meta::metadata::Kind::Default(name.to_string()),
RouteRef::Resource(gkn) => meta::metadata::Kind::Resource(meta::Resource {
group: gkn.group.to_string(),
kind: gkn.kind.to_string(),
name: gkn.name.to_string(),
..Default::default()
}),
}),
};

let hosts = hostnames
.into_iter()
.map(routes::convert_host_match)
.collect();

let rules = rules
.into_iter()
.map(
|InboundRouteRule { matches, filters }| inbound::http_route::Rule {
matches: matches
.into_iter()
.map(routes::http::convert_match)
.collect(),
filters: filters
.into_iter()
.filter_map(convert_http_filter)
.collect(),
},
)
.collect();

let authorizations = authorizations
.iter()
.map(|(n, c)| to_authz(n, c, cluster_networks))
.collect();

inbound::HttpRoute {
metadata: Some(metadata),
hosts,
rules,
authorizations,
}
}

fn convert_http_filter(filter: Filter) -> Option<inbound::http_route::Filter> {
use inbound::http_route::filter::Kind;

let kind = match filter {
Filter::FailureInjector(f) => Some(Kind::FailureInjector(
routes::http::convert_failure_injector_filter(f),
)),
Filter::RequestHeaderModifier(f) => Some(Kind::RequestHeaderModifier(
routes::convert_request_header_modifier_filter(f),
)),
Filter::ResponseHeaderModifier(_) => None,
Filter::RequestRedirect(f) => Some(Kind::Redirect(routes::convert_redirect_filter(f))),
};

kind.map(|kind| inbound::http_route::Filter { kind: Some(kind) })
}
2 changes: 1 addition & 1 deletion policy-controller/k8s/index/src/inbound.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pub mod authorization_policy;
mod http_route;
pub mod index;
mod meshtls_authentication;
mod network_authentication;
mod routes;
mod server;
pub mod server_authorization;
mod workload;
Expand Down
Loading

0 comments on commit eed3943

Please sign in to comment.