Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gateway): add crossMesh to MeshGatewayConfig #5183

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2205,6 +2205,10 @@ spec:
description: MeshGatewayConfigSpec specifies the options available for
a Kuma MeshGateway.
properties:
crossMesh:
description: CrossMesh specifies whether listeners configured by this
gateway are cross mesh listeners.
type: boolean
replicas:
default: 1
description: Replicas is the number of dataplane proxy replicas to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,10 @@ spec:
description: MeshGatewayConfigSpec specifies the options available for
a Kuma MeshGateway.
properties:
crossMesh:
description: CrossMesh specifies whether listeners configured by this
gateway are cross mesh listeners.
type: boolean
replicas:
default: 1
description: Replicas is the number of dataplane proxy replicas to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ spec:
description: MeshGatewayConfigSpec specifies the options available for
a Kuma MeshGateway.
properties:
crossMesh:
description: CrossMesh specifies whether listeners configured by this
gateway are cross mesh listeners.
type: boolean
replicas:
default: 1
description: Replicas is the number of dataplane proxy replicas to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ type MeshGatewayServiceSpec struct {
type MeshGatewayConfigSpec struct {
MeshGatewayCommonConfig `json:",inline"`

// CrossMesh specifies whether listeners configured by this gateway are
// cross mesh listeners.
CrossMesh bool `json:"crossMesh,omitempty"`

// Tags specifies a set of Kuma tags that are included in the
// MeshGatewayInstance and thus propagated to every Dataplane generated to
// serve the MeshGateway.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,18 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req kube_ctrl.Request
return kube_ctrl.Result{}, nil
}

config, err := r.meshGatewayConfigFromClass(ctx, class)
if err != nil {
return kube_ctrl.Result{}, errors.Wrap(err, "unable to get Config from GatewayClass")
}

ns := kube_core.Namespace{}
if err := r.Client.Get(ctx, kube_types.NamespacedName{Name: gateway.Namespace}, &ns); err != nil {
return kube_ctrl.Result{}, errors.Wrap(err, "unable to get Namespace of MeshGateway")
}

mesh := k8s_util.MeshOf(gateway, &ns)
gatewaySpec, listenerConditions, err := r.gapiToKumaGateway(ctx, gateway, mesh)
gatewaySpec, listenerConditions, err := r.gapiToKumaGateway(ctx, mesh, gateway, config)
if err != nil {
return kube_ctrl.Result{}, errors.Wrap(err, "error generating MeshGateway.kuma.io")
}
Expand All @@ -89,7 +94,7 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req kube_ctrl.Request
return kube_ctrl.Result{}, errors.Wrap(err, "could not reconcile owned MeshGateway.kuma.io")
}

gatewayInstance, err = r.createOrUpdateInstance(ctx, r.Client, mesh, gateway, class)
gatewayInstance, err = r.createOrUpdateInstance(ctx, mesh, gateway, config)
if err != nil {
return kube_ctrl.Result{}, errors.Wrap(err, "unable to reconcile MeshGatewayInstance")
}
Expand All @@ -102,31 +107,40 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req kube_ctrl.Request
return kube_ctrl.Result{}, nil
}

func (r *GatewayReconciler) createOrUpdateInstance(ctx context.Context, client kube_client.Client, mesh string, gateway *gatewayapi.Gateway, class *gatewayapi.GatewayClass) (*mesh_k8s.MeshGatewayInstance, error) {
func (r *GatewayReconciler) meshGatewayConfigFromClass(ctx context.Context, class *gatewayapi.GatewayClass) (mesh_k8s.MeshGatewayConfigSpec, error) {
ref, _, err := getParametersRef(ctx, r.Client, class.Spec.ParametersRef)
if err != nil {
return mesh_k8s.MeshGatewayConfigSpec{}, errors.Wrap(err, "unable to fetch parameters for GatewayClass")
}

if ref != nil {
if ref.Spec.CrossMesh {
ref.Spec.MeshGatewayCommonConfig.ServiceType = kube_core.ServiceTypeClusterIP
}

return ref.Spec, nil
}

return mesh_k8s.MeshGatewayConfigSpec{
MeshGatewayCommonConfig: mesh_k8s.MeshGatewayCommonConfig{
ServiceType: kube_core.ServiceTypeLoadBalancer,
Replicas: 1,
},
}, nil
}

func (r *GatewayReconciler) createOrUpdateInstance(ctx context.Context, mesh string, gateway *gatewayapi.Gateway, config mesh_k8s.MeshGatewayConfigSpec) (*mesh_k8s.MeshGatewayInstance, error) {
instance := &mesh_k8s.MeshGatewayInstance{
ObjectMeta: kube_meta.ObjectMeta{
Namespace: gateway.Namespace,
Name: gateway.Name,
},
}

tags := common.ServiceTagForGateway(kube_client.ObjectKeyFromObject(gateway))
commonConfig := mesh_k8s.MeshGatewayCommonConfig{
ServiceType: kube_core.ServiceTypeLoadBalancer,
}

ref, _, err := getParametersRef(ctx, client, class.Spec.ParametersRef)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch parameters for GatewayClass")
}

if ref != nil {
tags = mesh_proto.Merge(
tags,
ref.Spec.Tags,
)
commonConfig = ref.Spec.MeshGatewayCommonConfig
}
tags := mesh_proto.Merge(
common.ServiceTagForGateway(kube_client.ObjectKeyFromObject(gateway)),
config.Tags,
)

if _, err := kube_controllerutil.CreateOrUpdate(ctx, r.Client, instance, func() error {
if instance.Annotations == nil {
Expand All @@ -136,7 +150,7 @@ func (r *GatewayReconciler) createOrUpdateInstance(ctx context.Context, client k

instance.Spec = mesh_k8s.MeshGatewayInstanceSpec{
Tags: tags,
MeshGatewayCommonConfig: commonConfig,
MeshGatewayCommonConfig: config.MeshGatewayCommonConfig,
}

err := kube_controllerutil.SetControllerReference(gateway, instance, r.Scheme)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,24 @@ import (

mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
mesh_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
"github.com/kumahq/kuma/pkg/plugins/runtime/k8s/controllers/gatewayapi/common"
"github.com/kumahq/kuma/pkg/plugins/runtime/k8s/controllers/gatewayapi/policy"
)

type ListenerConditions map[gatewayapi.SectionName][]kube_meta.Condition

func validProtocol(protocol gatewayapi.ProtocolType) bool {
func validProtocol(crossMesh bool, protocol gatewayapi.ProtocolType) bool {
switch protocol {
case gatewayapi.HTTPProtocolType, gatewayapi.HTTPSProtocolType:
return true
return !crossMesh || protocol == gatewayapi.HTTPProtocolType
default:
}

return false
}

func ValidateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener, ListenerConditions) {
func ValidateListeners(crossMesh bool, listeners []gatewayapi.Listener) ([]gatewayapi.Listener, ListenerConditions) {
var validListeners []gatewayapi.Listener
listenerConditions := ListenerConditions{}

Expand Down Expand Up @@ -103,18 +104,22 @@ func ValidateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener,
protocols = map[gatewayapi.ProtocolType]struct{}{}
}

if validProtocol(l.Protocol) {
if validProtocol(crossMesh, l.Protocol) {
protocols[l.Protocol] = struct{}{}
}
portProtocols[l.Port] = protocols
}

for _, l := range listeners {
if !validProtocol(l.Protocol) {
if !validProtocol(crossMesh, l.Protocol) {
message := fmt.Sprintf("unsupported protocol %s", l.Protocol)
if crossMesh {
message = fmt.Sprintf("%s with cross-mesh", message)
}
appendDetachedCondition(
l.Name,
gatewayapi.ListenerReasonUnsupportedProtocol,
fmt.Sprintf("unsupported protocol %s", l.Protocol),
message,
)
continue
}
Expand Down Expand Up @@ -161,10 +166,11 @@ func ValidateListeners(listeners []gatewayapi.Listener) ([]gatewayapi.Listener,
// conditions to set on the gatewayapi listeners
func (r *GatewayReconciler) gapiToKumaGateway(
ctx context.Context,
gateway *gatewayapi.Gateway,
mesh string,
gateway *gatewayapi.Gateway,
config mesh_k8s.MeshGatewayConfigSpec,
) (*mesh_proto.MeshGateway, ListenerConditions, error) {
validListeners, listenerConditions := ValidateListeners(gateway.Spec.Listeners)
validListeners, listenerConditions := ValidateListeners(config.CrossMesh, gateway.Spec.Listeners)

var listeners []*mesh_proto.MeshGateway_Listener

Expand All @@ -176,6 +182,7 @@ func (r *GatewayReconciler) gapiToKumaGateway(
// Gateways, so just create a tag specifically for this listener
mesh_proto.ListenerTag: string(l.Name),
},
CrossMesh: config.CrossMesh,
}

if protocol, ok := mesh_proto.MeshGateway_Listener_Protocol_value[string(l.Protocol)]; ok {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var _ = Describe("ValidateListeners", func() {
},
},
}
valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
valids, conditions := k8s_gatewayapi.ValidateListeners(false, listeners)
Expect(valids).To(ConsistOf(
HaveField("Name", gatewayapi.SectionName("prod")),
))
Expand Down Expand Up @@ -55,7 +55,7 @@ var _ = Describe("ValidateListeners", func() {
},
},
}
valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
valids, conditions := k8s_gatewayapi.ValidateListeners(false, listeners)

Expect(valids).To(BeEmpty())

Expand Down Expand Up @@ -105,13 +105,60 @@ var _ = Describe("ValidateListeners", func() {
},
},
}
valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
valids, conditions := k8s_gatewayapi.ValidateListeners(false, listeners)
Expect(valids).To(ConsistOf(
HaveField("Name", gatewayapi.SectionName("prod-1")),
HaveField("Name", gatewayapi.SectionName("prod-2")),
))
Expect(conditions).To(BeEmpty())
})
It("enforces HTTP and cross-mesh", func() {
same := gatewayapi.NamespacesFromSame
listeners := []gatewayapi.Listener{
{
Name: gatewayapi.SectionName("valid-mesh"),
Protocol: gatewayapi.HTTPProtocolType,
Port: gatewayapi.PortNumber(80),
AllowedRoutes: &gatewayapi.AllowedRoutes{
Namespaces: &gatewayapi.RouteNamespaces{
From: &same,
},
},
},
{
Name: gatewayapi.SectionName("invalid-mesh"),
Protocol: gatewayapi.HTTPSProtocolType,
Port: gatewayapi.PortNumber(80),
AllowedRoutes: &gatewayapi.AllowedRoutes{
Namespaces: &gatewayapi.RouteNamespaces{
From: &same,
},
},
},
}

valids, conditions := k8s_gatewayapi.ValidateListeners(true, listeners)
Expect(valids).To(ConsistOf(
HaveField("Name", gatewayapi.SectionName("valid-mesh")),
))

invalid := ContainElements(
MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(gatewayapi.ListenerConditionAccepted)),
"Status": Equal(kube_meta.ConditionFalse),
"Reason": Equal(string(gatewayapi.ListenerReasonUnsupportedProtocol)),
}),
MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(gatewayapi.ListenerConditionReady)),
"Status": Equal(kube_meta.ConditionFalse),
}),
)
Expect(conditions).To(
MatchAllKeys(Keys{
gatewayapi.SectionName("invalid-mesh"): invalid,
}),
)
})
It("works with multiple listeners for same hostname:port conflict", func() {
same := gatewayapi.NamespacesFromSame
foo := gatewayapi.Hostname("foo.com")
Expand Down Expand Up @@ -139,7 +186,7 @@ var _ = Describe("ValidateListeners", func() {
},
},
}
valids, conditions := k8s_gatewayapi.ValidateListeners(listeners)
valids, conditions := k8s_gatewayapi.ValidateListeners(false, listeners)

Expect(valids).To(BeEmpty())

Expand Down
Loading