diff --git a/internal/gatewayapi/address.go b/internal/gatewayapi/address.go index b8d7d507563f..feaa2ab33a46 100644 --- a/internal/gatewayapi/address.go +++ b/internal/gatewayapi/address.go @@ -18,7 +18,12 @@ type AddressesTranslator interface { func (t *Translator) ProcessAddresses(gateways []*GatewayContext, xdsIR XdsIRMap, infraIR InfraIRMap, resources *Resources) { for _, gateway := range gateways { // Infra IR already exist - irKey := irStringKey(gateway.Gateway.Namespace, gateway.Gateway.Name) + var irKey string + if resources.EnvoyProxy != nil && resources.EnvoyProxy.Spec.MergeGateways != nil && *resources.EnvoyProxy.Spec.MergeGateways { + irKey = string(t.GatewayClassName) + } else { + irKey = irStringKey(gateway.Gateway.Namespace, gateway.Gateway.Name) + } gwInfraIR := infraIR[irKey] var ipAddr []string diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index d5426610905e..d5ebe0f79020 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -283,6 +283,12 @@ func GatewayOwnerLabels(namespace, name string) map[string]string { } } +// GatewayClassOwnerLabel returns the GatewayCLass Owner label using +// the provided name as the value. +func GatewayClassOwnerLabel(name string) map[string]string { + return map[string]string{OwningGatewayClassLabel: name} +} + // servicePortToContainerPort translates a service port into an ephemeral // container port. func servicePortToContainerPort(servicePort int32) int32 { diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go index f7d9601e399d..3b0f5b34e2d8 100644 --- a/internal/gatewayapi/listener.go +++ b/internal/gatewayapi/listener.go @@ -32,26 +32,23 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap // and compute status for each, and add valid ones // to the Xds IR. for _, gateway := range gateways { - // init IR per gateway - irKey := irStringKey(gateway.Gateway.Namespace, gateway.Gateway.Name) - gwXdsIR := &ir.Xds{} - gwInfraIR := ir.NewInfra() - gwInfraIR.Proxy.Name = irKey - gwInfraIR.Proxy.GetProxyMetadata().Labels = GatewayOwnerLabels(gateway.Namespace, gateway.Name) - if resources.EnvoyProxy != nil { - gwInfraIR.Proxy.Config = resources.EnvoyProxy - } - - // save the IR references in the map before the translation starts - xdsIR[irKey] = gwXdsIR - infraIR[irKey] = gwInfraIR - // Infra IR proxy ports must be unique. var foundPorts []*protocolPort + var irKey string + + if resources.EnvoyProxy != nil && resources.EnvoyProxy.Spec.MergeGateways != nil && *resources.EnvoyProxy.Spec.MergeGateways { + irKey = string(t.GatewayClassName) + } else { + irKey = irStringKey(gateway.Gateway.Namespace, gateway.Gateway.Name) + } + + if resources.EnvoyProxy != nil { + infraIR[irKey].Proxy.Config = resources.EnvoyProxy + } - gwXdsIR.AccessLog = processAccessLog(gwInfraIR.Proxy.Config) - gwXdsIR.Tracing = processTracing(gateway.Gateway, gwInfraIR.Proxy.Config) - gwXdsIR.Metrics = processMetrics(gwInfraIR.Proxy.Config) + xdsIR[irKey].AccessLog = processAccessLog(infraIR[irKey].Proxy.Config) + xdsIR[irKey].Tracing = processTracing(gateway.Gateway, infraIR[irKey].Proxy.Config) + xdsIR[irKey].Metrics = processMetrics(infraIR[irKey].Proxy.Config) for _, listener := range gateway.listeners { // Process protocol & supported kinds @@ -119,7 +116,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap // see more https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.Listener. irListener.Hostnames = append(irListener.Hostnames, "*") } - gwXdsIR.HTTP = append(gwXdsIR.HTTP, irListener) + xdsIR[irKey].HTTP = append(xdsIR[irKey].HTTP, irListener) } // Add the listener to the Infra IR. Infra IR ports must have a unique port number per layer-4 protocol @@ -146,7 +143,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap ContainerPort: containerPort, } // Only 1 listener is supported. - gwInfraIR.Proxy.Listeners[0].Ports = append(gwInfraIR.Proxy.Listeners[0].Ports, infraPort) + infraIR[irKey].Proxy.Listeners[0].Ports = append(infraIR[irKey].Proxy.Listeners[0].Ports, infraPort) } } } diff --git a/internal/gatewayapi/testdata/merge-multiple-gateways.in.yaml b/internal/gatewayapi/testdata/merge-multiple-gateways.in.yaml new file mode 100644 index 000000000000..ac62159762b1 --- /dev/null +++ b/internal/gatewayapi/testdata/merge-multiple-gateways.in.yaml @@ -0,0 +1,37 @@ +envoyproxy: + apiVersion: config.gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + namespace: envoy-gateway-system + name: test + spec: + mergeGateways: true +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-2 + port: 8888 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same diff --git a/internal/gatewayapi/testdata/merge-multiple-gateways.out.yaml b/internal/gatewayapi/testdata/merge-multiple-gateways.out.yaml new file mode 100644 index 000000000000..eaffe23d2616 --- /dev/null +++ b/internal/gatewayapi/testdata/merge-multiple-gateways.out.yaml @@ -0,0 +1,117 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + status: + listeners: + - attachedRoutes: 0 + name: http + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-2 + port: 8888 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + status: + listeners: + - attachedRoutes: 0 + name: http-2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +infraIR: + envoy-gateway-class: + proxy: + config: + apiVersion: config.gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + creationTimestamp: null + name: test + namespace: envoy-gateway-system + spec: + mergeGateways: true + listeners: + - address: "" + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + - containerPort: 8888 + name: http-2 + protocol: HTTP + servicePort: 8888 + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + name: envoy-gateway-class +xdsIR: + envoy-gateway-class: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + port: 10080 + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-2/http-2 + port: 8888 + diff --git a/internal/gatewayapi/testdata/multiple-gateways.in.yaml b/internal/gatewayapi/testdata/multiple-gateways.in.yaml new file mode 100644 index 000000000000..4602b9bfe2f7 --- /dev/null +++ b/internal/gatewayapi/testdata/multiple-gateways.in.yaml @@ -0,0 +1,29 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-2 + port: 8888 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same \ No newline at end of file diff --git a/internal/gatewayapi/testdata/multiple-gateways.out.yaml b/internal/gatewayapi/testdata/multiple-gateways.out.yaml new file mode 100644 index 000000000000..ffc4d9306f7b --- /dev/null +++ b/internal/gatewayapi/testdata/multiple-gateways.out.yaml @@ -0,0 +1,123 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + status: + listeners: + - attachedRoutes: 0 + name: http + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-2 + port: 8888 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + status: + listeners: + - attachedRoutes: 0 + name: http-2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: "" + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + envoy-gateway/gateway-2: + proxy: + listeners: + - address: "" + ports: + - containerPort: 8888 + name: http-2 + protocol: HTTP + servicePort: 8888 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-2 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + port: 10080 + envoy-gateway/gateway-2: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-2/http-2 + port: 8888 \ No newline at end of file diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 7a94ad234cf0..fefe66143bfc 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -6,6 +6,7 @@ package gatewayapi import ( + "github.com/envoyproxy/gateway/internal/ir" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -29,6 +30,7 @@ const ( // The value should be the namespace of the accepted Envoy Gateway. OwningGatewayNamespaceLabel = "gateway.envoyproxy.io/owning-gateway-namespace" + OwningGatewayClassLabel = "gateway.envoyproxy.io/owning-gatewayclass" // OwningGatewayNameLabel is the owner reference label used for managed infra. // The value should be the name of the accepted Envoy Gateway. OwningGatewayNameLabel = "gateway.envoyproxy.io/owning-gateway-name" @@ -118,12 +120,12 @@ func newTranslateResult(gateways []*GatewayContext, } func (t *Translator) Translate(resources *Resources) *TranslateResult { - xdsIR := make(XdsIRMap) - infraIR := make(InfraIRMap) - // Get Gateways belonging to our GatewayClass. gateways := t.GetRelevantGateways(resources.Gateways) + // Build IR maps. + xdsIR, infraIR := t.InitIRs(gateways, resources) + // Process all Listeners for all relevant Gateways. t.ProcessListeners(gateways, xdsIR, infraIR, resources) @@ -176,3 +178,27 @@ func (t *Translator) GetRelevantGateways(gateways []*v1beta1.Gateway) []*Gateway return relevant } + +func (t *Translator) InitIRs(gateways []*GatewayContext, resources *Resources) (map[string]*ir.Xds, map[string]*ir.Infra) { + xdsIR := make(XdsIRMap) + infraIR := make(InfraIRMap) + + var irKey string + for _, gateway := range gateways { + gwXdsIR := &ir.Xds{} + gwInfraIR := ir.NewInfra() + if resources.EnvoyProxy != nil && resources.EnvoyProxy.Spec.MergeGateways != nil && *resources.EnvoyProxy.Spec.MergeGateways { + irKey = string(t.GatewayClassName) + gwInfraIR.Proxy.GetProxyMetadata().Labels = GatewayClassOwnerLabel(string(t.GatewayClassName)) + } else { + irKey = irStringKey(gateway.Gateway.Namespace, gateway.Gateway.Name) + gwInfraIR.Proxy.GetProxyMetadata().Labels = GatewayOwnerLabels(gateway.Namespace, gateway.Name) + } + + // save the IR references in the map before the translation starts + xdsIR[irKey] = gwXdsIR + infraIR[irKey] = gwInfraIR + } + + return xdsIR, infraIR +}