From 2b4238c1c03b51339dd3b06be71e2aaf42194b7a Mon Sep 17 00:00:00 2001 From: Alex Leong Date: Tue, 14 Nov 2023 01:15:28 +0000 Subject: [PATCH] Update gateway types to v1 Signed-off-by: Alex Leong --- Cargo.toml | 1 + integration/tests/gateway.rs | 1 + integration/tests/httproute.rs | 2 + src/duration.rs | 354 +++++++++++++++++++++++++++++++++ src/gateway.rs | 304 ++++++++++++++++++++-------- src/gatewayclass.rs | 48 +++-- src/httproute.rs | 296 +++++++++++++++++++++------ src/lib.rs | 1 + src/shared.rs | 229 +++++++++++++++------ 9 files changed, 1011 insertions(+), 225 deletions(-) create mode 100644 src/duration.rs diff --git a/Cargo.toml b/Cargo.toml index 684480a..6476828 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ k8s-openapi = { version = "0.19", features = ["schemars"] } schemars = { version = "0.8", features = ["derive"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +thiserror = "1" [dev-dependencies.k8s-openapi] version = "0.19" diff --git a/integration/tests/gateway.rs b/integration/tests/gateway.rs index 672cfed..418f60a 100644 --- a/integration/tests/gateway.rs +++ b/integration/tests/gateway.rs @@ -19,6 +19,7 @@ async fn round_trip() { }, spec: GatewaySpec { gateway_class_name: "acme-lb".to_string(), + infrastructure: None, listeners: vec![Listener { protocol: "HTTPS".to_string(), port: 443, diff --git a/integration/tests/httproute.rs b/integration/tests/httproute.rs index 8742691..93a3ee1 100644 --- a/integration/tests/httproute.rs +++ b/integration/tests/httproute.rs @@ -41,6 +41,7 @@ async fn round_trip() { ]), filters: None, matches: None, + timeouts: None, }, HttpRouteRule { matches: Some(vec![HttpRouteMatch { @@ -58,6 +59,7 @@ async fn round_trip() { filters: None, }]), filters: None, + timeouts: None, }, ]), ..HttpRouteSpec::default() diff --git a/src/duration.rs b/src/duration.rs new file mode 100644 index 0000000..969ec9e --- /dev/null +++ b/src/duration.rs @@ -0,0 +1,354 @@ +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, str::FromStr, time::Duration}; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct K8sDuration { + duration: Duration, + is_negative: bool, +} + +#[derive(Debug, thiserror::Error, Eq, PartialEq)] +#[non_exhaustive] +pub enum ParseError { + #[error("invalid unit: {}", EXPECTED_UNITS)] + InvalidUnit, + + #[error("missing a unit: {}", EXPECTED_UNITS)] + NoUnit, + + #[error("invalid floating-point number: {}", .0)] + NotANumber(#[from] std::num::ParseFloatError), +} + +const EXPECTED_UNITS: &str = "expected one of 'ns', 'us', '\u{00b5}s', 'ms', 's', 'm', or 'h'"; + +impl From for K8sDuration { + fn from(duration: Duration) -> Self { + Self { + duration, + is_negative: false, + } + } +} + +impl From for Duration { + fn from(K8sDuration { duration, .. }: K8sDuration) -> Self { + duration + } +} + +impl K8sDuration { + #[inline] + #[must_use] + pub fn is_negative(&self) -> bool { + self.is_negative + } +} + +impl fmt::Debug for K8sDuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::fmt::Write; + if self.is_negative { + f.write_char('-')?; + } + fmt::Debug::fmt(&self.duration, f) + } +} + +impl fmt::Display for K8sDuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::fmt::Write; + if self.is_negative { + f.write_char('-')?; + } + fmt::Debug::fmt(&self.duration, f) + } +} + +impl FromStr for K8sDuration { + type Err = ParseError; + + fn from_str(mut s: &str) -> Result { + // implements the same format as + // https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/time/format.go;l=1589 + + fn duration_from_units(val: f64, unit: &str) -> Result { + const MINUTE: Duration = Duration::from_secs(60); + // https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/time/format.go;l=1573 + let base = match unit { + "ns" => Duration::from_nanos(1), + // U+00B5 is the "micro sign" while U+03BC is "Greek letter mu" + "us" | "\u{00b5}s" | "\u{03bc}s" => Duration::from_micros(1), + "ms" => Duration::from_millis(1), + "s" => Duration::from_secs(1), + "m" => MINUTE, + "h" => MINUTE * 60, + _ => return Err(ParseError::InvalidUnit), + }; + Ok(base.mul_f64(val)) + } + + // Go durations are signed. Rust durations aren't. So we need to ignore + // this for now. + let is_negative = s.starts_with('-'); + s = s.trim_start_matches('+').trim_start_matches('-'); + + let mut total = Duration::from_secs(0); + while !s.is_empty() { + if let Some(unit_start) = s.find(|c: char| c.is_alphabetic()) { + let (val, rest) = s.split_at(unit_start); + let val = val.parse::()?; + let unit = if let Some(next_numeric_start) = rest.find(|c: char| !c.is_alphabetic()) + { + let (unit, rest) = rest.split_at(next_numeric_start); + s = rest; + unit + } else { + s = ""; + rest + }; + total += duration_from_units(val, unit)?; + } else if s == "0" { + return Ok(K8sDuration { + duration: Duration::from_secs(0), + is_negative, + }); + } else { + return Err(ParseError::NoUnit); + } + } + + Ok(K8sDuration { + duration: total, + is_negative, + }) + } +} + +impl Serialize for K8sDuration { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for K8sDuration { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct Visitor; + impl<'de> de::Visitor<'de> for Visitor { + type Value = K8sDuration; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a string in Go `time.Duration.String()` format") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let val = value.parse::().map_err(de::Error::custom)?; + Ok(val) + } + } + deserializer.deserialize_str(Visitor) + } +} + +impl schemars::JsonSchema for K8sDuration { + // see + // https://github.com/kubernetes/apimachinery/blob/756e2227bf3a486098f504af1a0ffb736ad16f4c/pkg/apis/meta/v1/duration.go#L61 + fn schema_name() -> String { + "K8sDuration".to_owned() + } + + fn is_referenceable() -> bool { + false + } + + fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + // the format should *not* be "duration", because "duration" means + // the duration is formatted in ISO 8601, as described here: + // https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-02#section-7.3.1 + format: None, + ..Default::default() + } + .into() + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_the_same_as_go() { + const MINUTE: Duration = Duration::from_secs(60); + const HOUR: Duration = Duration::from_secs(60 * 60); + // from Go: + // https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/time/time_test.go;l=891-951 + // ``` + // var parseDurationTests = []struct { + // in string + // want Duration + // }{ + let cases: &[(&str, K8sDuration)] = &[ + // // simple + // {"0", 0}, + ("0", Duration::from_secs(0).into()), + // {"5s", 5 * Second}, + ("5s", Duration::from_secs(5).into()), + // {"30s", 30 * Second}, + ("30s", Duration::from_secs(30).into()), + // {"1478s", 1478 * Second}, + ("1478s", Duration::from_secs(1478).into()), + // // sign + // {"-5s", -5 * Second}, + ( + "-5s", + K8sDuration { + duration: Duration::from_secs(5), + is_negative: true, + }, + ), + // {"+5s", 5 * Second}, + ("+5s", Duration::from_secs(5).into()), + // {"-0", 0}, + ( + "-0", + K8sDuration { + duration: Duration::from_secs(0), + is_negative: true, + }, + ), + // {"+0", 0}, + ("+0", Duration::from_secs(0).into()), + // // decimal + // {"5.0s", 5 * Second}, + ("5s", Duration::from_secs(5).into()), + // {"5.6s", 5*Second + 600*Millisecond}, + ( + "5.6s", + (Duration::from_secs(5) + Duration::from_millis(600)).into(), + ), + // {"5.s", 5 * Second}, + ("5.s", Duration::from_secs(5).into()), + // {".5s", 500 * Millisecond}, + (".5s", Duration::from_millis(500).into()), + // {"1.0s", 1 * Second}, + ("1.0s", Duration::from_secs(1).into()), + // {"1.00s", 1 * Second}, + ("1.00s", Duration::from_secs(1).into()), + // {"1.004s", 1*Second + 4*Millisecond}, + ( + "1.004s", + (Duration::from_secs(1) + Duration::from_millis(4)).into(), + ), + // {"1.0040s", 1*Second + 4*Millisecond}, + ( + "1.0040s", + (Duration::from_secs(1) + Duration::from_millis(4)).into(), + ), + // {"100.00100s", 100*Second + 1*Millisecond}, + ( + "100.00100s", + (Duration::from_secs(100) + Duration::from_millis(1)).into(), + ), + // // different units + // {"10ns", 10 * Nanosecond}, + ("10ns", Duration::from_nanos(10).into()), + // {"11us", 11 * Microsecond}, + ("11us", Duration::from_micros(11).into()), + // {"12µs", 12 * Microsecond}, // U+00B5 + ("12µs", Duration::from_micros(12).into()), + // {"12μs", 12 * Microsecond}, // U+03BC + ("12μs", Duration::from_micros(12).into()), + // {"13ms", 13 * Millisecond}, + ("13ms", Duration::from_millis(13).into()), + // {"14s", 14 * Second}, + ("14s", Duration::from_secs(14).into()), + // {"15m", 15 * Minute}, + ("15m", (15 * MINUTE).into()), + // {"16h", 16 * Hour}, + ("16h", (16 * HOUR).into()), + // // composite durations + // {"3h30m", 3*Hour + 30*Minute}, + ("3h30m", (3 * HOUR + 30 * MINUTE).into()), + // {"10.5s4m", 4*Minute + 10*Second + 500*Millisecond}, + ( + "10.5s4m", + (4 * MINUTE + Duration::from_secs(10) + Duration::from_millis(500)).into(), + ), + // {"-2m3.4s", -(2*Minute + 3*Second + 400*Millisecond)}, + ( + "-2m3.4s", + K8sDuration { + duration: 2 * MINUTE + Duration::from_secs(3) + Duration::from_millis(400), + is_negative: true, + }, + ), + // {"1h2m3s4ms5us6ns", 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond}, + ( + "1h2m3s4ms5us6ns", + (1 * HOUR + + 2 * MINUTE + + Duration::from_secs(3) + + Duration::from_millis(4) + + Duration::from_micros(5) + + Duration::from_nanos(6)) + .into(), + ), + // {"39h9m14.425s", 39*Hour + 9*Minute + 14*Second + 425*Millisecond}, + ( + "39h9m14.425s", + (39 * HOUR + 9 * MINUTE + Duration::from_secs(14) + Duration::from_millis(425)) + .into(), + ), + // // large value + // {"52763797000ns", 52763797000 * Nanosecond}, + ("52763797000ns", Duration::from_nanos(52763797000).into()), + // // more than 9 digits after decimal point, see https://golang.org/issue/6617 + // {"0.3333333333333333333h", 20 * Minute}, + ("0.3333333333333333333h", (20 * MINUTE).into()), + // // 9007199254740993 = 1<<53+1 cannot be stored precisely in a float64 + // {"9007199254740993ns", (1<<53 + 1) * Nanosecond}, + ( + "9007199254740993ns", + Duration::from_nanos((1 << 53) + 1).into(), + ), + // Rust Durations can handle larger durations than Go's + // representation, so skip these tests for their precision limits + + // // largest duration that can be represented by int64 in nanoseconds + // {"9223372036854775807ns", (1<<63 - 1) * Nanosecond}, + // ("9223372036854775807ns", Duration::from_nanos((1 << 63) - 1).into()), + // {"9223372036854775.807us", (1<<63 - 1) * Nanosecond}, + // ("9223372036854775.807us", Duration::from_nanos((1 << 63) - 1).into()), + // {"9223372036s854ms775us807ns", (1<<63 - 1) * Nanosecond}, + // {"-9223372036854775808ns", -1 << 63 * Nanosecond}, + // {"-9223372036854775.808us", -1 << 63 * Nanosecond}, + // {"-9223372036s854ms775us808ns", -1 << 63 * Nanosecond}, + // // largest negative value + // {"-9223372036854775808ns", -1 << 63 * Nanosecond}, + // // largest negative round trip value, see https://golang.org/issue/48629 + // {"-2562047h47m16.854775808s", -1 << 63 * Nanosecond}, + + // // huge string; issue 15011. + // {"0.100000000000000000000h", 6 * Minute}, + ("0.100000000000000000000h", (6 * MINUTE).into()), // // This value tests the first overflow check in leadingFraction. + // {"0.830103483285477580700h", 49*Minute + 48*Second + 372539827*Nanosecond}, + // } + // ``` + ]; + + for (input, expected) in cases { + let parsed = dbg!(input).parse::().unwrap(); + assert_eq!(&dbg!(parsed), expected); + } + } +} diff --git a/src/gateway.rs b/src/gateway.rs index 9510a54..204cd31 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; )] #[kube( group = "gateway.networking.k8s.io", - version = "v1beta1", + version = "v1", kind = "Gateway", status = "GatewayStatus", namespaced @@ -20,42 +20,121 @@ pub struct GatewaySpec { /// GatewayClass resource. pub gateway_class_name: ObjectName, - /// Listeners associated with this Gateway. Listeners define logical - /// endpoints that are bound on this Gateway's addresses. At least one - /// Listener MUST be specified. - /// - /// Each listener in a Gateway must have a unique combination of Hostname, - /// Port, and Protocol. - /// - /// An implementation MAY group Listeners by Port and then collapse each - /// group of Listeners into a single Listener if the implementation - /// determines that the Listeners in the group are "compatible". An - /// implementation MAY also group together and collapse compatible Listeners - /// belonging to different Gateways. - /// - /// For example, an implementation might consider Listeners to be compatible - /// with each other if all of the following conditions are met: - /// - /// 1. Either each Listener within the group specifies the "HTTP" Protocol or - /// each Listener within the group specifies either the "HTTPS" or "TLS" - /// Protocol. - /// - /// 2. Each Listener within the group specifies a Hostname that is unique - /// within the group. - /// - /// 3. As a special case, one Listener within a group may omit Hostname, in - /// which case this Listener matches when no other Listener matches. - /// - /// If the implementation does collapse compatible Listeners, the hostname - /// provided in the incoming client request MUST be matched to a Listener to - /// find the correct set of Routes. The incoming hostname MUST be matched - /// using the Hostname field for each Listener in order of most to least - /// specific. That is, exact matches must be processed before wildcard - /// matches. - /// - /// If this field specifies multiple Listeners that have the same Port value - /// but are not compatible, the implementation must raise a "Conflicted" - /// condition in the Listener status. + /// Listeners associated with this Gateway. Listeners define + /// logical endpoints that are bound on this Gateway's addresses. + /// At least one Listener MUST be specified. + /// + /// Each Listener in a set of Listeners (for example, in a single Gateway) + /// MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + /// exactly one listener. (This section uses "set of Listeners" rather than + /// "Listeners in a single Gateway" because implementations MAY merge configuration + /// from multiple Gateways onto a single data plane, and these rules _also_ + /// apply in that case). + /// + /// Practically, this means that each listener in a set MUST have a unique + /// combination of Port, Protocol, and, if supported by the protocol, Hostname. + /// + /// Some combinations of port, protocol, and TLS settings are considered + /// Core support and MUST be supported by implementations based on their + /// targeted conformance profile: + /// + /// HTTP Profile + /// + /// 1. HTTPRoute, Port: 80, Protocol: HTTP + /// 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + /// + /// TLS Profile + /// + /// 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + /// + // /"Distinct" Listeners have the following property: + /// + /// The implementation can match inbound requests to a single distinct + /// Listener. When multiple Listeners share values for fields (for + /// example, two Listeners with the same Port value), the implementation + /// can match requests to only one of the Listeners using other + /// Listener fields. + /// + /// For example, the following Listener scenarios are distinct: + /// + /// 1. Multiple Listeners with the same Port that all use the "HTTP" + /// Protocol that all have unique Hostname values. + /// 2. Multiple Listeners with the same Port that use either the "HTTPS" or + /// "TLS" Protocol that all have unique Hostname values. + /// 3. A mixture of "TCP" and "UDP" Protocol Listeners, where no Listener + /// with the same Protocol has the same Port value. + /// + /// Some fields in the Listener struct have possible values that affect + /// whether the Listener is distinct. Hostname is particularly relevant + /// for HTTP or HTTPS protocols. + /// + /// When using the Hostname value to select between same-Port, same-Protocol + /// Listeners, the Hostname value must be different on each Listener for the + /// Listener to be distinct. + /// + /// When the Listeners are distinct based on Hostname, inbound request + /// hostnames MUST match from the most specific to least specific Hostname + /// values to choose the correct Listener and its associated set of Routes. + /// + /// Exact matches must be processed before wildcard matches, and wildcard + /// matches must be processed before fallback (empty Hostname value) + /// matches. For example, `"foo.example.com"` takes precedence over + /// `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + /// + /// Additionally, if there are multiple wildcard entries, more specific + /// wildcard entries must be processed before less specific wildcard entries. + /// For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + /// The precise definition here is that the higher the number of dots in the + /// hostname to the right of the wildcard character, the higher the precedence. + /// + /// The wildcard character will match any number of characters _and dots_ to + /// the left, however, so `"*.example.com"` will match both + /// `"foo.bar.example.com"` _and_ `"bar.example.com"`. + /// + /// If a set of Listeners contains Listeners that are not distinct, then those + /// Listeners are Conflicted, and the implementation MUST set the "Conflicted" + /// condition in the Listener Status to "True". + /// + /// Implementations MAY choose to accept a Gateway with some Conflicted + /// Listeners only if they only accept the partial Listener set that contains + /// no Conflicted Listeners. To put this another way, implementations may + /// accept a partial Listener set only if they throw out *all* the conflicting + /// Listeners. No picking one of the conflicting listeners as the winner. + /// This also means that the Gateway must have at least one non-conflicting + /// Listener in this case, otherwise it violates the requirement that at + /// least one Listener must be present. + /// + /// The implementation MUST set a "ListenersNotValid" condition on the + /// Gateway Status when the Gateway contains Conflicted Listeners whether or + /// not they accept the Gateway. That Condition SHOULD clearly + /// indicate in the Message which Listeners are conflicted, and which are + /// Accepted. Additionally, the Listener status for those listeners SHOULD + /// indicate which Listeners are conflicted and not Accepted. + /// + /// A Gateway's Listeners are considered "compatible" if: + /// + /// 1. They are distinct. + /// 2. The implementation can serve them in compliance with the Addresses + /// requirement that all Listeners are available on all assigned + /// addresses. + /// + /// Compatible combinations in Extended support are expected to vary across + /// implementations. A combination that is compatible for one implementation + /// may not be compatible for another. + /// + /// For example, an implementation that cannot serve both TCP and UDP listeners + /// on the same address, or cannot mix HTTPS and generic TLS listens on the same port + /// would not consider those cases compatible, even though they are distinct. + /// + /// Note that requests SHOULD match at most one Listener. For example, if + /// Listeners are defined for "foo.example.com" and "*.example.com", a + /// request to "foo.example.com" SHOULD only be routed using routes attached + /// to the "foo.example.com" Listener (and not the "*.example.com" Listener). + /// This concept is known as "Listener Isolation". Implementations that do + /// not support Listener Isolation MUST clearly document this. + /// + /// Implementations MAY merge separate Gateways onto a single set of + /// Addresses if all Listeners across all Gateways are compatible. /// /// Support: Core pub listeners: Vec, @@ -71,9 +150,6 @@ pub struct GatewaySpec { /// other networking infrastructure, or some other address that traffic will /// be sent to. /// - /// The .listener.hostname field is used to route traffic that has already - /// arrived at the Gateway to the correct in-cluster destination. - /// /// If no Addresses are specified, the implementation MAY schedule the /// Gateway in an implementation-specific manner, assigning an appropriate /// set of Addresses. @@ -84,6 +160,11 @@ pub struct GatewaySpec { /// /// Support: Extended pub addresses: Option>, + + /// Infrastructure defines infrastructure level attributes about this Gateway instance. + /// + /// Support: Core + pub infrastructure: Option, } /// Listener embodies the concept of a logical endpoint where a Gateway accepts @@ -105,12 +186,12 @@ pub struct Listener { /// Implementations MUST apply Hostname matching appropriately for each of /// the following protocols: /// - /// * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener - /// Hostname MUST match the Host header of the request. * HTTPS: The - /// Listener Hostname SHOULD match at both the TLS and HTTP protocol layers - /// as described above. If an implementation does not ensure that both the - /// SNI and Host header match the Listener hostname, it MUST clearly document - /// that. + /// * TLS: The Listener Hostname MUST match the SNI. + /// * HTTP: The Listener Hostname MUST match the Host header of the request. + /// * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP + /// protocol layers as described above. If an implementation does not + /// ensure that both the SNI and Host header match the Listener hostname, + /// it MUST clearly document that. /// /// For HTTPRoute and TLSRoute resources, there is an interaction with the /// `spec.hostnames` array. When both listener and route specify hostnames, @@ -118,6 +199,10 @@ pub struct Listener { /// accepted. For more information, refer to the Route specific Hostnames /// documentation. /// + /// Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + /// as a suffix match. That means that a match for `*.example.com` would match + /// both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + /// /// Support: Core pub hostname: Option, @@ -137,8 +222,8 @@ pub struct Listener { /// The association of SNIs to Certificate defined in GatewayTLSConfig is /// defined based on the Hostname field for this listener. /// - /// The GatewayClass MUST use the longest matching SNI out of all available - /// certificates for any TLS handshake. + /// The GatewayClass MUST use the longest matching SNI out of all + /// available certificates for any TLS handshake. /// /// Support: Core pub tls: Option, @@ -152,16 +237,16 @@ pub struct Listener { /// determined in order of the following criteria: /// /// * The most specific match as defined by the Route type. - /// * The oldest Route based on creation timestamp. For example, a Route - /// with a creation timestamp of "2020-09-08 01:02:03" is given precedence - /// over a Route with a creation timestamp of "2020-09-08 01:02:04". + /// * The oldest Route based on creation timestamp. For example, a Route with + /// a creation timestamp of "2020-09-08 01:02:03" is given precedence over + /// a Route with a creation timestamp of "2020-09-08 01:02:04". /// * If everything else is equivalent, the Route appearing first in /// alphabetical order (namespace/name) should be given precedence. For /// example, foo/bar is given precedence over foo/baz. /// /// All valid rules within a Route attached to this Listener should be - /// implemented. Invalid Route rules can be ignored (sometimes that will - /// mean the full Route). If a Route rule transitions from valid to invalid, + /// implemented. Invalid Route rules can be ignored (sometimes that will mean + /// the full Route). If a Route rule transitions from valid to invalid, /// support for that Route rule should be dropped to ensure consistency. For /// example, even if a filter specified by a Route rule is invalid, the rest /// of the rules within that Route should still be supported. @@ -171,18 +256,18 @@ pub struct Listener { } /// ProtocolType defines the application protocol accepted by a Listener. -/// Implementations are not required to accept all the defined protocols. -/// If an implementation does not support a specified protocol, it -/// should raise a "Detached" condition for the affected Listener with -/// a reason of "UnsupportedProtocol". +/// Implementations are not required to accept all the defined protocols. If an +/// implementation does not support a specified protocol, it MUST set the +/// "Accepted" condition to False for the affected Listener with a reason of +/// "UnsupportedProtocol". /// /// Core ProtocolType values are listed in the table below. /// /// Implementations can define their own protocols if a core ProtocolType does not /// exist. Such definitions must use prefixed name, such as /// `mycompany.com/my-custom-protocol`. Un-prefixed names are reserved for core -/// protocols. Any protocol defined by implementations will fall under custom -/// conformance. +/// protocols. Any protocol defined by implementations will fall under +/// Implementation-specific conformance. /// /// Valid values include: /// @@ -193,7 +278,6 @@ pub struct Listener { /// /// * "example.com" - must include path if domain is used /// * "foo.example.com" - must include path if domain is used -/// pub type ProtocolType = String; /// GatewayTLSConfig describes a TLS configuration. @@ -202,34 +286,34 @@ pub type ProtocolType = String; )] #[serde(rename_all = "camelCase")] pub struct GatewayTlsConfig { - /// Mode defines the TLS behavior for the TLS session initiated by the - /// client. There are two possible modes: + /// Mode defines the TLS behavior for the TLS session initiated by the client. + /// There are two possible modes: /// - /// - Terminate: The TLS session between the downstream client and the - /// Gateway is terminated at the Gateway. This mode requires + /// - Terminate: The TLS session between the downstream client + /// and the Gateway is terminated at the Gateway. This mode requires /// certificateRefs to be set and contain at least one element. /// - Passthrough: The TLS session is NOT terminated by the Gateway. This - /// implies that the Gateway can't decipher the TLS stream except for the - /// ClientHello message of the TLS protocol. CertificateRefs field is - /// ignored in this mode. + /// implies that the Gateway can't decipher the TLS stream except for + /// the ClientHello message of the TLS protocol. + /// CertificateRefs field is ignored in this mode. /// /// Support: Core pub mode: Option, - /// CertificateRefs contains a series of references to Kubernetes objects - /// that contains TLS certificates and private keys. These certificates are - /// used to establish a TLS handshake for requests that match the hostname - /// of the associated listener. + /// CertificateRefs contains a series of references to Kubernetes objects that + /// contains TLS certificates and private keys. These certificates are used to + /// establish a TLS handshake for requests that match the hostname of the + /// associated listener. /// /// A single CertificateRef to a Kubernetes Secret has "Core" support. /// Implementations MAY choose to support attaching multiple certificates to /// a Listener, but this behavior is implementation-specific. /// /// References to a resource in different namespace are invalid UNLESS there - /// is a ReferencePolicy in the target namespace that allows the certificate - /// to be attached. If a ReferencePolicy does not allow this reference, the + /// is a ReferenceGrant in the target namespace that allows the certificate + /// to be attached. If a ReferenceGrant does not allow this reference, the /// "ResolvedRefs" condition MUST be set to False for this listener with the - /// "InvalidCertificateRef" reason. + /// "RefNotPermitted" reason. /// /// This field is required to have at least one element when the mode is set /// to "Terminate" (default) and is optional otherwise. @@ -237,11 +321,9 @@ pub struct GatewayTlsConfig { /// CertificateRefs can reference to standard Kubernetes resources, i.e. /// Secret, or implementation-specific custom resources. /// - /// Support: Core - A single reference to a Kubernetes Secret of type - /// kubernetes.io/tls + /// Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls /// - /// Support: Implementation-specific (More than one reference or other - /// resource types) + /// Support: Implementation-specific (More than one reference or other resource types) pub certificate_refs: Option>, /// Options are a list of key/value pairs to enable extended TLS @@ -340,28 +422,61 @@ pub struct GatewayAddress { /// GatewayStatus defines the observed state of Gateway. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] pub struct GatewayStatus { - /// Addresses lists the IP addresses that have actually been bound to the - /// Gateway. These addresses may differ from the addresses in the Spec, e.g. - /// if the Gateway automatically assigns an address from a reserved pool. + /// Addresses lists the network addresses that have been bound to the + /// Gateway. + /// + /// This list may differ from the addresses provided in the spec under some + /// conditions: + /// + /// * no addresses are specified, all addresses are dynamically assigned + /// * a combination of specified and dynamic addresses are assigned + /// * a specified address was unusable (e.g. already in use) pub addresses: Option>, /// Conditions describe the current conditions of the Gateway. /// - /// Implementations should prefer to express Gateway conditions using the - /// `GatewayConditionType` and `GatewayConditionReason` constants so that - /// operators and tools can converge on a common vocabulary to describe - /// Gateway state. + /// Implementations should prefer to express Gateway conditions + /// using the `GatewayConditionType` and `GatewayConditionReason` + /// constants so that operators and tools can converge on a common + /// vocabulary to describe Gateway state. /// /// Known condition types are: /// - /// * "Scheduled" + /// * "Accepted" + /// * "Programmed" /// * "Ready" pub conditions: Option>, - /// Routes is a list of routes bound to the Gateway. + /// Listeners provide status for each unique listener port defined in the Spec. pub listeners: Option>, } +/// GatewayInfrastructure defines infrastructure level attributes about a Gateway instance. +#[derive( + Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema, +)] +pub struct GatewayInfrastructure { + /// Labels that SHOULD be applied to any resources created in response to this Gateway. + /// + /// For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + /// For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + /// + /// An implementation may chose to add additional implementation-specific labels as they see fit. + /// + /// Support: Extended + pub labels: Option>, + + /// Annotations that SHOULD be applied to any resources created in response to this Gateway. + /// + /// For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + /// For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + /// + /// An implementation may chose to add additional implementation-specific annotations as they see fit. + /// + /// Support: Extended + pub annotations: Option>, +} + /// GatewayConditionType is a type of condition associated with a /// Gateway. This type should be used with the GatewayStatus.Conditions /// field. @@ -390,6 +505,21 @@ pub struct ListenerStatus { /// AttachedRoutes represents the total number of Routes that have been /// successfully attached to this Listener. + /// + /// Successful attachment of a Route to a Listener is based solely on the + /// combination of the AllowedRoutes field on the corresponding Listener + /// and the Route's ParentRefs field. A Route is successfully attached to + /// a Listener when it is selected by the Listener's AllowedRoutes field + /// AND the Route has a valid ParentRef selecting the whole Gateway + /// resource or a specific Listener as a parent resource (more detail on + /// attachment semantics can be found in the documentation on the various + /// Route kinds ParentRefs fields). Listener or Route status does not impact + /// successful attachment, i.e. the AttachedRoutes field count MUST be set + /// for Listeners with condition Accepted: false and MUST count successfully + /// attached Routes that may themselves have Accepted: false conditions. + /// + /// Uses for this field include troubleshooting Route attachment and + /// measuring blast radius/impact of changes to a Listener. pub attached_routes: u16, /// Conditions describe the current condition of this listener. diff --git a/src/gatewayclass.rs b/src/gatewayclass.rs index a17d43e..af8ee06 100644 --- a/src/gatewayclass.rs +++ b/src/gatewayclass.rs @@ -1,29 +1,29 @@ use crate::*; use k8s_openapi::apimachinery::pkg::apis::meta::v1 as metav1; -// GatewayClass describes a class of Gateways available to the user for creating -// Gateway resources. -// -// It is recommended that this resource be used as a template for Gateways. This -// means that a Gateway is based on the state of the GatewayClass at the time it -// was created and changes to the GatewayClass or associated parameters are not -// propagated down to existing Gateways. This recommendation is intended to -// limit the blast radius of changes to GatewayClass or associated parameters. -// If implementations choose to propagate GatewayClass changes to existing -// Gateways, that MUST be clearly documented by the implementation. -// -// Whenever one or more Gateways are using a GatewayClass, implementations MUST -// add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the -// associated GatewayClass. This ensures that a GatewayClass associated with a -// Gateway is not deleted while in use. -// -// GatewayClass is a Cluster level resource. +/// GatewayClass describes a class of Gateways available to the user for creating +/// Gateway resources. +/// +/// It is recommended that this resource be used as a template for Gateways. This +/// means that a Gateway is based on the state of the GatewayClass at the time it +/// was created and changes to the GatewayClass or associated parameters are not +/// propagated down to existing Gateways. This recommendation is intended to +/// limit the blast radius of changes to GatewayClass or associated parameters. +/// If implementations choose to propagate GatewayClass changes to existing +/// Gateways, that MUST be clearly documented by the implementation. +/// +/// Whenever one or more Gateways are using a GatewayClass, implementations SHOULD +/// add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the +/// associated GatewayClass. This ensures that a GatewayClass associated with a +/// Gateway is not deleted while in use. +/// +/// GatewayClass is a Cluster level resource. #[derive( Clone, Debug, kube::CustomResource, serde::Deserialize, serde::Serialize, schemars::JsonSchema, )] #[kube( group = "gateway.networking.k8s.io", - version = "v1beta1", + version = "v1", kind = "GatewayClass", status = "GatewayClassStatus" )] @@ -35,6 +35,8 @@ pub struct GatewayClassSpec { /// Example: "example.net/gateway-controller". /// /// This field is not mutable and cannot be empty. + /// + /// Support: Core pub controller_name: GatewayController, /// ParametersRef is a reference to a resource that contains the @@ -49,7 +51,7 @@ pub struct GatewayClassSpec { /// If the referent cannot be found, the GatewayClass's "InvalidParameters" /// status condition will be true. /// - /// Support: Custom + /// Support: Implementation-specific pub paramters_ref: Option, /// Description helps describe a GatewayClass with more details. @@ -96,4 +98,12 @@ pub struct GatewayClassStatus { /// Controllers should prefer to publish conditions using values of /// GatewayClassConditionType for the type of each Condition. pub conditions: Option>, + + /// SupportedFeatures is the set of features the GatewayClass support. + /// It MUST be sorted in ascending alphabetical order. + pub supported_features: Option>, } + +/// SupportedFeature is used to describe distinct features that are covered by +/// conformance tests. +pub type SupportedFeature = String; diff --git a/src/httproute.rs b/src/httproute.rs index f2f36bd..d4bd44c 100644 --- a/src/httproute.rs +++ b/src/httproute.rs @@ -15,7 +15,7 @@ use crate::*; )] #[kube( group = "gateway.networking.k8s.io", - version = "v1beta1", + version = "v1", kind = "HTTPRoute", struct = "HttpRoute", status = "HttpRouteStatus", @@ -26,9 +26,14 @@ pub struct HttpRouteSpec { #[serde(flatten)] pub inner: CommonRouteSpec, - /// Hostnames defines a set of hostname that should match against the HTTP - /// Host header to select a HTTPRoute to process the request. This matches - /// the RFC 1123 definition of a hostname with 2 notable exceptions: + /// Hostnames defines a set of hostnames that should match against the HTTP Host + /// header to select a HTTPRoute used to process the request. Implementations + /// MUST ignore any port value specified in the HTTP Host header while + /// performing a match and (absent of any applicable header modification + /// configuration) MUST forward this header unmodified to the backend. + /// + /// Valid values for Hostnames are determined by RFC 1123 definition of a + /// hostname with 2 notable exceptions: /// /// 1. IPs are not allowed. /// 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard @@ -44,8 +49,13 @@ pub struct HttpRouteSpec { /// * A Listener with `*.example.com` as the hostname matches HTTPRoutes /// that have either not specified any hostnames or have specified at least /// one hostname that matches the Listener hostname. For example, - /// `test.example.com` and `*.example.com` would both match. On the other - /// hand, `example.com` and `test.example.net` would not match. + /// `*.example.com`, `test.example.com`, and `foo.test.example.com` would + /// all match. On the other hand, `example.com` and `test.example.net` would + /// not match. + /// + /// Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + /// as a suffix match. That means that a match for `*.example.com` would match + /// both `test.example.com`, and `foo.test.example.com`, but not `example.com`. /// /// If both the Listener and HTTPRoute have specified hostnames, any /// HTTPRoute hostnames that do not match the Listener hostname MUST be @@ -58,6 +68,16 @@ pub struct HttpRouteSpec { /// implementation must raise an 'Accepted' Condition with a status of /// `False` in the corresponding RouteParentStatus. /// + /// In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + /// overlapping wildcard matching and exact matching hostnames), precedence must + /// be given to rules from the HTTPRoute with the largest number of: + /// + /// * Characters in a matching non-wildcard hostname. + /// * Characters in a matching hostname. + /// + /// If ties exist across multiple Routes, the matching precedence rules for + /// HTTPRouteMatches takes over. + /// /// Support: Core pub hostnames: Option>, @@ -104,15 +124,17 @@ pub struct HttpRouteRule { /// HTTP request. /// /// Proxy or Load Balancer routing configuration generated from HTTPRoutes - /// MUST prioritize rules based on the following criteria, continuing on - /// ties. Precedence must be given to the the Rule with the largest number - /// of: + /// MUST prioritize matches based on the following criteria, continuing on + /// ties. Across all rules specified on applicable Routes, precedence must be + /// given to the match having: /// - /// * Characters in a matching non-wildcard hostname. - /// * Characters in a matching hostname. - /// * Characters in a matching path. - /// * Header matches. - /// * Query param matches. + /// * "Exact" path match. + /// * "Prefix" path match with largest number of characters. + /// * Method match. + /// * Largest number of header matches. + /// * Largest number of query param matches. + /// + /// Note: The precedence of RegularExpression path matches are implementation-specific. /// /// If ties still exist across multiple Routes, matching precedence MUST be /// determined in order of the following criteria, continuing on ties: @@ -121,30 +143,37 @@ pub struct HttpRouteRule { /// * The Route appearing first in alphabetical order by /// "{namespace}/{name}". /// - /// If ties still exist within the Route that has been given precedence, - /// matching precedence MUST be granted to the first matching rule meeting - /// the above criteria. + /// If ties still exist within an HTTPRoute, matching precedence MUST be granted + /// to the FIRST matching rule (in list order) with a match meeting the above + /// criteria. /// /// When no rules matching a request have been successfully attached to the /// parent a request is coming from, a HTTP 404 status code MUST be returned. pub matches: Option>, - /// Filters define the filters that are applied to requests that match this - /// rule. + /// Filters define the filters that are applied to requests that match + /// this rule. /// /// The effects of ordering of multiple behaviors are currently unspecified. /// This can change in the future based on feedback during the alpha stage. /// - /// Conformance-levels at this level are defined based on the type of - /// filter: + /// Conformance-levels at this level are defined based on the type of filter: /// /// - ALL core filters MUST be supported by all implementations. /// - Implementers are encouraged to support extended filters. /// - Implementation-specific custom filters have no API guarantees across /// implementations. /// - /// Specifying a core filter multiple times has unspecified or custom - /// conformance. + /// Specifying the same filter multiple times is not supported unless explicitly + /// indicated in the filter. + /// + /// All filters are expected to be compatible with each other except for the + /// URLRewrite and RequestRedirect filters, which may not be combined. If an + /// implementation can not support other combinations of filters, they must clearly + /// document that limitation. In cases where incompatible or unsupported + /// filters are specified and cause the `Accepted` condition to be set to status + /// `False`, implementations may use the `IncompatibleFilters` reason to specify + /// this configuration error. /// /// Support: Core pub filters: Option>, @@ -152,30 +181,80 @@ pub struct HttpRouteRule { /// BackendRefs defines the backend(s) where matching requests should be /// sent. /// - /// A 500 status code MUST be returned if there are no BackendRefs or - /// filters specified that would result in a response being sent. + /// Failure behavior here depends on how many BackendRefs are specified and + /// how many are invalid. /// - /// A BackendRef is considered invalid when it refers to: + /// If *all* entries in BackendRefs are invalid, and there are also no filters + /// specified in this route rule, *all* traffic which matches this rule MUST + /// receive a 500 status code. /// - /// * an unknown or unsupported kind of resource - /// * a resource that does not exist - /// * a resource in another namespace when the reference has not been - /// explicitly allowed by a ReferencePolicy (or equivalent concept). + /// See the HTTPBackendRef definition for the rules about what makes a single + /// HTTPBackendRef invalid. /// - /// When a BackendRef is invalid, 500 status codes MUST be returned for + /// When a HTTPBackendRef is invalid, 500 status codes MUST be returned for /// requests that would have otherwise been routed to an invalid backend. If /// multiple backends are specified, and some are invalid, the proportion of /// requests that would otherwise have been routed to an invalid backend /// MUST receive a 500 status code. /// - /// When a BackendRef refers to a Service that has no ready endpoints, it is - /// recommended to return a 503 status code. + /// For example, if two backends are specified with equal weights, and one is + /// invalid, 50 percent of traffic must receive a 500. Implementations may + /// choose how that 50 percent is determined. /// /// Support: Core for Kubernetes Service - /// Support: Custom for any other resource + /// + /// Support: Extended for Kubernetes ServiceImport + /// + /// Support: Implementation-specific for any other resource /// /// Support for weight: Core pub backend_refs: Option>, + + // Timeouts defines the timeouts that can be configured for an HTTP request. + // + // Support: Extended + pub timeouts: Option, +} + +/// HTTPRouteTimeouts defines timeouts that can be configured for an HTTPRoute. +/// Timeout values are represented with Gateway API Duration formatting. +/// Specifying a zero value such as "0s" is interpreted as no timeout. +#[derive( + Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, schemars::JsonSchema, +)] +#[serde(rename_all = "camelCase")] +pub struct HttpRouteTimeouts { + /// Request specifies the maximum duration for a gateway to respond to an HTTP request. + /// If the gateway has not been able to respond before this deadline is met, the gateway + /// MUST return a timeout error. + /// + /// For example, setting the `rules.timeouts.request` field to the value `10s` in an + /// `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + /// to complete. + /// + /// This timeout is intended to cover as close to the whole request-response transaction + /// as possible although an implementation MAY choose to start the timeout after the entire + /// request stream has been received instead of immediately after the transaction is + /// initiated by the client. + /// + /// When this field is unspecified, request timeout behavior is implementation-specific. + /// + /// Support: Extended + pub request: Option, + + /// BackendRequest specifies a timeout for an individual request from the gateway + /// to a backend. This covers the time from when the request first starts being + /// sent from the gateway to when the full response has been received from the backend. + /// + /// An entire client HTTP transaction with a gateway, covered by the Request timeout, + /// may result in more than one call from the gateway to the destination backend, + /// for example, if automatic retries are supported. + /// + /// Because the Request timeout encompasses the BackendRequest timeout, the value of + /// BackendRequest must be <= the value of Request timeout. + /// + /// Support: Extended + pub backend_request: Option, } /// HTTPRouteMatch defines the predicate used to match requests to a given @@ -210,10 +289,11 @@ pub struct HttpRouteMatch { /// QueryParams specifies HTTP query parameter matchers. Multiple match /// values are ANDed together, meaning, a request must match all the /// specified query parameters to select the route. + /// + /// Support: Extended pub query_params: Option>, /// Method specifies HTTP method matcher. - /// /// When specified, this route will be matched only if the request has the /// specified method. /// @@ -318,8 +398,8 @@ pub type HttpMethod = String; /// HTTPRouteFilter defines processing steps that must be completed during the /// request or response lifecycle. HTTPRouteFilters are meant as an extension -/// point to express processing that may be done in Gateway implementations. -/// Some examples include request or response modification, implementing +/// point to express processing that may be done in Gateway implementations. Some +/// examples include request or response modification, implementing /// authentication strategies, rate-limiting, and traffic shaping. API /// guarantee/conformance is defined based on the type of the filter. /// @@ -334,7 +414,8 @@ pub type HttpMethod = String; /// "Support: Extended" in this package, e.g. "RequestMirror". Implementers /// are encouraged to support extended filters. /// -/// - Custom: Filters that are defined and supported by specific vendors. +/// - Implementation-specific: Filters that are defined and supported by +/// specific vendors. /// In the future, filters showing convergence in behavior across multiple /// implementations will be considered for inclusion in extended or core /// conformance levels. Filter-specific configuration for such filters @@ -374,6 +455,10 @@ pub enum HttpRouteFilter { /// Requests are sent to the specified destination, but responses from /// that destination are ignored. /// + /// This filter can be used multiple times within the same rule. Note that + /// not all implementations will be able to support mirroring to multiple + /// backends. + /// /// Support: Extended #[serde(rename_all = "camelCase")] RequestMirror { @@ -400,6 +485,8 @@ pub enum HttpRouteFilter { /// "networking.example.net"). ExtensionRef MUST NOT be used for core and /// extended filters. /// + /// This filter can be used multiple times within the same rule. + /// /// Support: Implementation-specific #[serde(rename_all = "camelCase")] ExtensionRef { extension_ref: LocalObjectReference }, @@ -501,7 +588,32 @@ pub enum HttpPathModifier { /// ReplacePrefixMatch specifies the value with which to replace the prefix /// match of a request during a rewrite or redirect. For example, a request - /// to "/foo/bar" with a prefix match of "/foo" would be modified to "/bar". + /// to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + /// of "/xyz" would be modified to "/xyz/bar". + /// + /// Note that this matches the behavior of the PathPrefix match type. This + /// matches full path elements. A path element refers to the list of labels + /// in the path split by the `/` separator. When specified, a trailing `/` is + /// ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + /// match the prefix `/abc`, but the path `/abcd` would not. + /// + /// ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + /// Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + /// the implementation setting the Accepted Condition for the Route to `status: False`. + /// + /// Request Path | Prefix Match | Replace Prefix | Modified Path + /// -------------|--------------|----------------|---------- + /// /foo/bar | /foo | /xyz | /xyz/bar + /// /foo/bar | /foo | /xyz/ | /xyz/bar + /// /foo/bar | /foo/ | /xyz | /xyz/bar + /// /foo/bar | /foo/ | /xyz/ | /xyz/bar + /// /foo | /foo | /xyz | /xyz + /// /foo/ | /foo | /xyz | /xyz/ + /// /foo/bar | /foo | | /bar + /// /foo/ | /foo | | / + /// /foo | /foo | | / + /// /foo/ | /foo | / | / + /// /foo | /foo | / | / #[serde(rename_all = "camelCase")] ReplacePrefixMatch { replace_prefix_match: String }, } @@ -517,13 +629,15 @@ pub struct HttpRequestRedirectFilter { /// header in the response. /// When empty, the scheme of the request is used. /// + /// Scheme redirects can affect the port of the redirect, for more information, + /// refer to the documentation for the port field of this filter. + /// /// Support: Extended pub scheme: Option, /// Hostname is the hostname to be used in the value of the `Location` /// header in the response. - /// - /// When empty, the hostname of the request is used. + /// When empty, the hostname in the `Host` header of the request is used. /// /// Support: Core pub hostname: Option, @@ -537,7 +651,24 @@ pub struct HttpRequestRedirectFilter { /// Port is the port to be used in the value of the `Location` /// header in the response. - /// When empty, port (if specified) of the request is used. + /// + /// If no port is specified, the redirect port MUST be derived using the + /// following rules: + /// + /// * If redirect scheme is not-empty, the redirect port MUST be the well-known + /// port associated with the redirect scheme. Specifically "http" to port 80 + /// and "https" to port 443. If the redirect scheme does not have a + /// well-known port, the listener port of the Gateway SHOULD be used. + /// * If redirect scheme is empty, the redirect port MUST be the Gateway + /// Listener port. + /// + /// Implementations SHOULD NOT add the port number in the 'Location' + /// header in the following cases: + /// + /// * A Location header that will use HTTP (whether that is determined via + /// the Listener protocol or the Scheme field) _and_ use port 80. + /// * A Location header that will use HTTPS (whether that is determined via + /// the Listener protocol or the Scheme field) _and_ use port 443. /// /// Support: Extended pub port: Option, @@ -578,13 +709,17 @@ pub struct HttpUrlRewriteFilter { pub struct HttpRequestMirrorFilter { /// BackendRef references a resource where mirrored requests are sent. /// + /// Mirrored requests must be sent only to a single destination endpoint + /// within this BackendRef, irrespective of how many endpoints are present + /// within this BackendRef. + /// /// If the referent cannot be found, this BackendRef is invalid and must be /// dropped from the Gateway. The controller must ensure the "ResolvedRefs" /// condition on the Route status is set to `status: False` and not configure /// this backend in the underlying implementation. /// /// If there is a cross-namespace reference to an *existing* object - /// that is not allowed by a ReferencePolicy, the controller must ensure the + /// that is not allowed by a ReferenceGrant, the controller must ensure the /// "ResolvedRefs" condition on the Route is set to `status: False`, /// with the "RefNotPermitted" reason and not configure this backend in the /// underlying implementation. @@ -593,11 +728,31 @@ pub struct HttpRequestMirrorFilter { /// should be used to provide more detail about the problem. /// /// Support: Extended for Kubernetes Service - /// Support: Custom for any other resource + /// + /// Support: Implementation-specific for any other resource pub backend_ref: BackendObjectReference, } -/// HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. +/// HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. +/// +/// Note that when a namespace different than the local namespace is specified, a +/// ReferenceGrant object is required in the referent namespace to allow that +/// namespace's owner to accept the reference. See the ReferenceGrant +/// documentation for details. +/// +/// When the BackendRef points to a Kubernetes Service, implementations SHOULD +/// honor the appProtocol field if it is set for the target Service Port. +/// +/// Implementations supporting appProtocol SHOULD recognize the Kubernetes +/// Standard Application Protocols defined in KEP-3726. +/// +/// If a Service appProtocol isn't specified, an implementation MAY infer the +/// backend protocol through its own means. Implementations MAY infer the +/// protocol from the Route type referring to the backend Service. +/// +/// If a Route is not able to send traffic to the backend using the specified +/// protocol then the backend is considered invalid. Implementations MUST set the +/// "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. #[derive( Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema, )] @@ -605,29 +760,52 @@ pub struct HttpRequestMirrorFilter { pub struct HttpBackendRef { /// BackendRef is a reference to a backend to forward matched requests to. /// - /// If the referent cannot be found, this HTTPBackendRef is invalid and must - /// be dropped from the Gateway. The controller must ensure the - /// "ResolvedRefs" condition on the Route is set to `status: False` and not - /// configure this backend in the underlying implementation. + /// A BackendRef can be invalid for the following reasons. In all cases, the + /// implementation MUST ensure the `ResolvedRefs` Condition on the Route + /// is set to `status: False`, with a Reason and Message that indicate + /// what is the cause of the error. /// - /// If there is a cross-namespace reference to an *existing* object - /// that is not covered by a ReferencePolicy, the controller must ensure the - /// "ResolvedRefs" condition on the Route is set to `status: False`, - /// with the "RefNotPermitted" reason and not configure this backend in the - /// underlying implementation. + /// A BackendRef is invalid if: /// - /// In either error case, the Message of the `ResolvedRefs` Condition - /// should be used to provide more detail about the problem. + /// * It refers to an unknown or unsupported kind of resource. In this + /// case, the Reason must be set to `InvalidKind` and Message of the + /// Condition must explain which kind of resource is unknown or unsupported. + /// + /// * It refers to a resource that does not exist. In this case, the Reason must + /// be set to `BackendNotFound` and the Message of the Condition must explain + /// which resource does not exist. + /// + /// * It refers a resource in another namespace when the reference has not been + /// explicitly allowed by a ReferenceGrant (or equivalent concept). In this + /// case, the Reason must be set to `RefNotPermitted` and the Message of the + /// Condition must explain which cross-namespace reference is not allowed. + /// + /// * It refers to a Kubernetes Service that has an incompatible appProtocol + /// for the given Route type + /// + /// * The BackendTLSPolicy object is installed in the cluster, a BackendTLSPolicy + /// is present that refers to the Service, and the implementation is unable + /// to meet the requirement. At the time of writing, BackendTLSPolicy is + /// experimental, but once it becomes standard, this will become a MUST + /// requirement. + /// + /// Support: Core for Kubernetes Service + /// + /// Support: Implementation-specific for any other resource + /// + /// Support for weight: Core + /// + /// Support for Kubernetes Service appProtocol: Extended /// - /// Support: Custom + /// Support for BackendTLSPolicy: Experimental and ImplementationSpecific #[serde(flatten)] pub backend_ref: Option, /// Filters defined at this level should be executed if and only if the /// request is being forwarded to the backend defined here. /// - /// Support: Custom (For broader support of filters, use the Filters field - /// in HTTPRouteRule.) + /// Support: Implementation-specific (For broader support of filters, use the + /// Filters field in HTTPRouteRule.) pub filters: Option>, } diff --git a/src/lib.rs b/src/lib.rs index 80d3d8b..775baf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ // TODO(ver): We should deny missing_docs, but this doesn't play with // CustomResource derivations. +pub mod duration; mod gateway; mod gatewayclass; mod httproute; diff --git a/src/shared.rs b/src/shared.rs index 555daf3..c94cec3 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -3,9 +3,14 @@ use k8s_openapi::apimachinery::pkg::apis::meta::v1 as metav1; use crate::BackendObjectReference; /// ParentReference identifies an API object (usually a Gateway) that can be considered -/// a parent of this resource (usually a route). The only kind of parent resource -/// with "Core" support is Gateway. This API may be extended in the future to -/// support additional kinds of parent resources, such as HTTPRoute. +/// a parent of this resource (usually a route). There are two kinds of parent resources +/// with "Core" support: +/// +/// * Gateway (Gateway conformance profile) +/// * Service (Mesh conformance profile, experimental, ClusterIP Services only) +/// +/// This API may be extended in the future to support additional kinds of parent +/// resources. /// /// The API object must be valid in the cluster; the Group and Kind must /// be registered in the cluster for this reference to be valid. @@ -15,18 +20,41 @@ use crate::BackendObjectReference; #[serde(rename_all = "camelCase")] pub struct ParentReference { /// Group is the group of the referent. + /// When unspecified, "gateway.networking.k8s.io" is inferred. + /// To set the core API group (such as for a "Service" kind referent), + /// Group must be explicitly set to "" (empty string). /// /// Support: Core pub group: Option, /// Kind is kind of the referent. /// - /// Support: Core (Gateway) - /// Support: Custom (Other Resources) + /// There are two kinds of parent resources with "Core" support: + /// + /// * Gateway (Gateway conformance profile) + /// * Service (Mesh conformance profile, experimental, ClusterIP Services only) + /// + /// Support for other resources is Implementation-Specific. pub kind: Option, - /// Namespace is the namespace of the referent. When unspecified (or empty - /// string), this refers to the local namespace of the Route. + /// Namespace is the namespace of the referent. When unspecified, this refers + /// to the local namespace of the Route. + /// + /// Note that there are specific rules for ParentRefs which cross namespace + /// boundaries. Cross-namespace references are only valid if they are explicitly + /// allowed by something in the namespace they are referring to. For example: + /// Gateway has the AllowedRoutes field, and ReferenceGrant provides a + /// generic way to enable any other kind of cross-namespace reference. + /// + /// ParentRefs from a Route to a Service in the same namespace are "producer" + /// routes, which apply default routing rules to inbound connections from + /// any namespace to the Service. + /// + /// ParentRefs from a Route to a Service in a different namespace are + /// "consumer" routes, and these routing rules are only applied to outbound + /// connections originating from the same namespace as the Route, for which + /// the intended destination of the connections are a Service targeted as a + /// ParentRef of the Route. /// /// Support: Core pub namespace: Option, @@ -42,33 +70,42 @@ pub struct ParentReference { /// * Gateway: Listener Name. When both Port (experimental) and SectionName /// are specified, the name and port of the selected listener must match /// both specified values. + /// * Service: Port Name. When both Port (experimental) and SectionName + /// are specified, the name and port of the selected listener must match + /// both specified values. Note that attaching Routes to Services as Parents + /// is part of experimental Mesh support and is not supported for any other + /// purpose. /// - /// Implementations MAY choose to support attaching Routes to other - /// resources. If that is the case, they MUST clearly document how - /// SectionName is interpreted. + /// Implementations MAY choose to support attaching Routes to other resources. + /// If that is the case, they MUST clearly document how SectionName is + /// interpreted. /// - /// When unspecified (empty string), this will reference the entire - /// resource. For the purpose of status, an attachment is considered - /// successful if at least one section in the parent resource accepts it. - /// For example, Gateway listeners can restrict which Routes can attach to - /// them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners - /// accept attachment from the referencing Route, the Route MUST be - /// considered successfully attached. If no Gateway listeners accept - /// attachment from this Route, the Route MUST be considered detached from - /// the Gateway. + /// When unspecified (empty string), this will reference the entire resource. + /// For the purpose of status, an attachment is considered successful if at + /// least one section in the parent resource accepts it. For example, Gateway + /// listeners can restrict which Routes can attach to them by Route kind, + /// namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + /// the referencing Route, the Route MUST be considered successfully + /// attached. If no Gateway listeners accept attachment from this Route, the + /// Route MUST be considered detached from the Gateway. /// /// Support: Core pub section_name: Option, /// Port is the network port this Route targets. It can be interpreted - /// differently based on the type of parent resource: + /// differently based on the type of parent resource. /// - /// * Gateway: All listeners listening on the specified port that also - /// support this kind of Route(and select this Route). It's not recommended - /// to set `Port` unless the networking behaviors specified in a Route must - /// apply to a specific port as opposed to a listener(s) whose port(s) may - /// be changed. When both Port and SectionName are specified, the name and - /// port of the selected listener must match both specified values. + /// When the parent resource is a Gateway, this targets all listeners + /// listening on the specified port that also support this kind of Route(and + /// select this Route). It's not recommended to set `Port` unless the + /// networking behaviors specified in a Route must apply to a specific port + /// as opposed to a listener(s) whose port(s) may be changed. When both Port + /// and SectionName are specified, the name and port of the selected listener + /// must match both specified values. + /// + /// When the parent resource is a Service, this targets a specific port in the + /// Service spec. When both Port (experimental) and SectionName are specified, + /// the name and port of the selected port must match both specified values. /// /// Implementations MAY choose to support other parent resources. /// Implementations supporting other types of parent resources MUST clearly @@ -79,8 +116,8 @@ pub struct ParentReference { /// listeners can restrict which Routes can attach to them by Route kind, /// namespace, or hostname. If 1 of 2 Gateway listeners accept attachment /// from the referencing Route, the Route MUST be considered successfully - /// attached. If no Gateway listeners accept attachment from this Route, the - /// Route MUST be considered detached from the Gateway. + /// attached. If no Gateway listeners accept attachment from this Route, + /// the Route MUST be considered detached from the Gateway. /// /// Support: Extended pub port: Option, @@ -93,25 +130,65 @@ pub struct ParentReference { )] #[serde(rename_all = "camelCase")] pub struct CommonRouteSpec { - /// ParentRefs references the resources (usually Gateways) that a Route - /// wants to be attached to. Note that the referenced parent resource needs - /// to allow this for the attachment to be complete. For Gateways, that - /// means the Gateway needs to allow attachment from Routes of this kind and - /// namespace. - /// - /// The only kind of parent resource with "Core" support is Gateway. This - /// API may be extended in the future to support additional kinds of parent - /// resources such as one of the route kinds. - /// - /// It is invalid to reference an identical parent more than once. It is - /// valid to reference multiple distinct sections within the same parent - /// resource, such as 2 Listeners within a Gateway. - /// - /// It is possible to separately reference multiple distinct objects that - /// may be collapsed by an implementation. For example, some implementations - /// may choose to merge compatible Gateway Listeners together. If that is - /// the case, the list of routes attached to those resources should also be + /// ParentRefs references the resources (usually Gateways) that a Route wants + /// to be attached to. Note that the referenced parent resource needs to + /// allow this for the attachment to be complete. For Gateways, that means + /// the Gateway needs to allow attachment from Routes of this kind and + /// namespace. For Services, that means the Service must either be in the same + /// namespace for a "producer" route, or the mesh implementation must support + /// and allow "consumer" routes for the referenced Service. ReferenceGrant is + /// not applicable for governing ParentRefs to Services - it is not possible to + /// create a "producer" route for a Service in a different namespace from the + /// Route. + /// + /// There are two kinds of parent resources with "Core" support: + /// + /// * Gateway (Gateway conformance profile) + /// * Service (Mesh conformance profile, experimental, ClusterIP Services only) + /// This API may be extended in the future to support additional kinds of parent + /// resources. + /// + /// ParentRefs must be _distinct_. This means either that: + /// + /// * They select different objects. If this is the case, then parentRef + /// entries are distinct. In terms of fields, this means that the + /// multi-part key defined by `group`, `kind`, `namespace`, and `name` must + /// be unique across all parentRef entries in the Route. + /// * They do not select different objects, but for each optional field used, + /// each ParentRef that selects the same object must set the same set of + /// optional fields to different values. If one ParentRef sets a + /// combination of optional fields, all must set the same combination. + /// + /// Some examples: + /// + /// * If one ParentRef sets `sectionName`, all ParentRefs referencing the + /// same object must also set `sectionName`. + /// * If one ParentRef sets `port`, all ParentRefs referencing the same + /// object must also set `port`. + /// * If one ParentRef sets `sectionName` and `port`, all ParentRefs + /// referencing the same object must also set `sectionName` and `port`. + /// + /// It is possible to separately reference multiple distinct objects that may + /// be collapsed by an implementation. For example, some implementations may + /// choose to merge compatible Gateway Listeners together. If that is the + /// case, the list of routes attached to those resources should also be /// merged. + /// + /// Note that for ParentRefs that cross namespace boundaries, there are specific + /// rules. Cross-namespace references are only valid if they are explicitly + /// allowed by something in the namespace they are referring to. For example, + /// Gateway has the AllowedRoutes field, and ReferenceGrant provides a + /// generic way to enable other kinds of cross-namespace reference. + /// + /// ParentRefs from a Route to a Service in the same namespace are "producer" + /// routes, which apply default routing rules to inbound connections from + /// any namespace to the Service. + /// + /// ParentRefs from a Route to a Service in a different namespace are + /// "consumer" routes, and these routing rules are only applied to outbound + /// connections originating from the same namespace as the Route, for which + /// the intended destination of the connections are a Service targeted as a + /// ParentRef of the Route. pub parent_refs: Option>, } @@ -121,9 +198,28 @@ pub type PortNumber = u16; /// BackendRef defines how a Route should forward a request to a Kubernetes /// resource. /// -/// Note that when a namespace is specified, a ReferencePolicy object is -/// required in the referent namespace to allow that namespace's owner to accept -/// the reference. See the ReferencePolicy documentation for details. +/// Note that when a namespace different than the local namespace is specified, a +/// ReferenceGrant object is required in the referent namespace to allow that +/// namespace's owner to accept the reference. See the ReferenceGrant +/// documentation for details. +/// +/// When the BackendRef points to a Kubernetes Service, implementations SHOULD +/// honor the appProtocol field if it is set for the target Service Port. +/// +/// Implementations supporting appProtocol SHOULD recognize the Kubernetes +/// Standard Application Protocols defined in KEP-3726. +/// +/// If a Service appProtocol isn't specified, an implementation MAY infer the +/// backend protocol through its own means. Implementations MAY infer the +/// protocol from the Route type referring to the backend Service. +/// +/// If a Route is not able to send traffic to the backend using the specified +/// protocol then the backend is considered invalid. Implementations MUST set the +/// "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. +/// +/// Note that when the BackendTLSPolicy object is enabled by the implementation, +/// there are some extra rules about validity to consider here. See the fields +/// where this struct is used for more information about the exact behavior. #[derive( Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema, )] @@ -135,9 +231,9 @@ pub struct BackendRef { /// implementation supports. Weight is not a percentage and the sum of /// weights does not need to equal 100. /// - /// If only one backend is specified and it has a weight greater than 0, - /// 100% of the traffic is forwarded to that backend. If weight is set to 0, - /// no traffic should be forwarded for this entry. If unspecified, weight + /// If only one backend is specified and it has a weight greater than 0, 100% + /// of the traffic is forwarded to that backend. If weight is set to 0, no + /// traffic should be forwarded for this entry. If unspecified, weight /// defaults to 1. /// /// Support for this field varies based on the context where used. @@ -179,25 +275,25 @@ pub struct RouteParentStatus { /// [names]: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names pub controller_name: GatewayController, - /// Conditions describes the status of the route with respect to the - /// Gateway. Note that the route's availability is also subject to the - /// Gateway's own status conditions and listener status. + /// Conditions describes the status of the route with respect to the Gateway. + /// Note that the route's availability is also subject to the Gateway's own + /// status conditions and listener status. /// /// If the Route's ParentRef specifies an existing Gateway that supports /// Routes of this kind AND that Gateway's controller has sufficient access, /// then that Gateway's controller MUST set the "Accepted" condition on the - /// Route, to indicate whether the route has been accepted or rejected by - /// the Gateway, and why. + /// Route, to indicate whether the route has been accepted or rejected by the + /// Gateway, and why. /// /// A Route MUST be considered "Accepted" if at least one of the Route's /// rules is implemented by the Gateway. /// - /// There are a number of cases where the "Accepted" condition may not be - /// set due to lack of controller visibility, that includes when: + /// There are a number of cases where the "Accepted" condition may not be set + /// due to lack of controller visibility, that includes when: /// /// * The Route refers to a non-existent parent. /// * The Route is of a type that the controller does not support. - /// * The Route is in a namespace the the controller does not have access to. + /// * The Route is in a namespace the controller does not have access to. pub conditions: Vec, } @@ -352,4 +448,17 @@ pub type AnnotationKey = String; pub type AnnotationValue = String; /// AddressType defines how a network address is represented as a text string. +/// This may take two possible forms: +/// +/// * A predefined CamelCase string identifier (currently limited to `IPAddress` or `Hostname`) +/// * A domain-prefixed string identifier (like `acme.io/CustomAddressType`) +/// +/// Values `IPAddress` and `Hostname` have Extended support. +/// +/// The `NamedAddress` value has been deprecated in favor of implementation +/// specific domain-prefixed strings. +/// +/// All other values, including domain-prefixed values have Implementation-specific support, +/// which are used in implementation-specific behaviors. Support for additional +/// predefined CamelCase identifiers may be added in future releases. pub type AddressType = String;