Skip to content

Commit

Permalink
feat(kuma-cp) match gateway routes (#2758)
Browse files Browse the repository at this point in the history
Update the binding between Gateway and GatewayRoute resources. This can
be a bit complex because both size can have an optional hostname and we
have to map that to Envoy's slightly different concept of virtual host.

Signed-off-by: James Peach <james.peach@konghq.com>
  • Loading branch information
jpeach authored Sep 15, 2021
1 parent 13a2ffa commit ca70bbf
Show file tree
Hide file tree
Showing 13 changed files with 790 additions and 57 deletions.
63 changes: 63 additions & 0 deletions pkg/plugins/runtime/gateway/gateway_route_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package gateway

import (
mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
"github.com/kumahq/kuma/pkg/core/resources/model"
core_xds "github.com/kumahq/kuma/pkg/core/xds"
"github.com/kumahq/kuma/pkg/plugins/runtime/gateway/match"
xds_context "github.com/kumahq/kuma/pkg/xds/context"
)

func filterGatewayRoutes(in []model.Resource, accept func(resource *core_mesh.GatewayRouteResource) bool) []*core_mesh.GatewayRouteResource {
routes := make([]*core_mesh.GatewayRouteResource, 0, len(in))

for _, r := range in {
if trafficRoute, ok := r.(*core_mesh.GatewayRouteResource); ok {
if accept(trafficRoute) {
routes = append(routes, trafficRoute)
}
}
}

return routes
}

// GatewayRouteGenerator generates Kuma gateway routes from GatewayRoute resources.
type GatewayRouteGenerator struct {
}

func (*GatewayRouteGenerator) SupportsProtocol(p mesh_proto.Gateway_Listener_Protocol) bool {
return p == mesh_proto.Gateway_Listener_HTTP || p == mesh_proto.Gateway_Listener_HTTPS
}

func (*GatewayRouteGenerator) GenerateHost(ctx xds_context.Context, info *GatewayResourceInfo) (*core_xds.ResourceSet, error) {
gatewayRoutes := filterGatewayRoutes(info.Host.Routes, func(route *core_mesh.GatewayRouteResource) bool {
// Wildcard virtual host accepts all routes.
if info.Host.Hostname == WildcardHostname {
return true
}

// If the route has no hostnames, it matches all virtualhosts.
names := route.Spec.GetConf().GetHttp().GetHostnames()
if len(names) == 0 {
return true
}

// Otherwise, match the virtualhost name to the route names.
return match.Hostnames(info.Host.Hostname, names...)
})

if len(gatewayRoutes) == 0 {
return nil, nil
}

resources := ResourceAggregator{}

log.V(1).Info("applying merged traffic routes",
"listener-port", info.Listener.Port,
"listener-name", info.Listener.ResourceName,
)

return resources.Get(), nil
}
222 changes: 222 additions & 0 deletions pkg/plugins/runtime/gateway/gateway_route_generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package gateway_test

import (
"path"

"github.com/envoyproxy/go-control-plane/pkg/cache/v3"
"github.com/ghodss/yaml"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"

core_model "github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/core/runtime"
"github.com/kumahq/kuma/pkg/test/matchers"
xds_server "github.com/kumahq/kuma/pkg/xds/server/v3"
)

var _ = Describe("Gateway Gateway Route", func() {
var rt runtime.Runtime
var dataplanes *DataplaneGenerator

Do := func() (cache.Snapshot, error) {
serverCtx := xds_server.NewXdsContext()
reconciler := xds_server.DefaultReconciler(rt, serverCtx)

// We expect there to be a Dataplane fixture named
// "default" in the current mesh.
ctx, proxy := MakeGeneratorContext(rt,
core_model.ResourceKey{Mesh: "default", Name: "default"})

Expect(proxy.Dataplane.Spec.IsBuiltinGateway()).To(BeTrue())

if err := reconciler.Reconcile(*ctx, proxy); err != nil {
return cache.Snapshot{}, err
}

return serverCtx.Cache().GetSnapshot(proxy.Id.String())
}

BeforeEach(func() {
var err error

rt, err = BuildRuntime()
Expect(err).To(Succeed(), "build runtime instance")

Expect(StoreNamedFixture(rt, "mesh-default.yaml")).To(Succeed())
Expect(StoreNamedFixture(rt, "dataplane-default.yaml")).To(Succeed())
Expect(StoreNamedFixture(rt, "gateway-default.yaml")).To(Succeed())

dataplanes = &DataplaneGenerator{Manager: rt.ResourceManager()}

// TODO(jpeach) dataplane resources won't be used in the
// tests until the GatewayRoute generator starts generating
// endpoints.
dataplanes.Generate("echo-service")
})

DescribeTable("generate matching resources",
func(goldenFileName string, fixtureResources ...string) {
// given
for _, resource := range fixtureResources {
Expect(StoreInlineFixture(rt, []byte(resource))).To(Succeed())
}

// when
snap, err := Do()
Expect(err).To(Succeed())

// then
Expect(yaml.Marshal(MakeProtoSnapshot(snap))).
To(matchers.MatchGoldenYAML(path.Join("testdata", goldenFileName)))

},
// When we have a route with multiple hostnames that is
// attached to a listener with no hostname (i.e. a wildcard),
// we ought to generate distinct Envoy virtualhost entries
// for each hostname
Entry("should expand route hostnames into virtual hosts",
"01-gateway-route.yaml", `
type: Gateway
mesh: default
name: edge-gateway
selectors:
- match:
kuma.io/service: gateway-default
conf:
listeners:
- port: 8080
protocol: HTTP
tags:
port: http/8080
`, `
type: GatewayRoute
mesh: default
name: echo-service
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
hostnames:
- foo.example.com
- bar.example.com
- "*.example.com"
rules:
- matches:
- path:
match: EXACT
value: /
backends:
- destination:
kuma.io/service: echo-service
`,
),

// Attaching a wildcard route (i.e. configured without
// any hostnames) to a wildcard Gateway listener should
// generate a wildcard virtual host.
Entry("should create a wildcard virtual host",
"02-gateway-route.yaml", `
type: Gateway
mesh: default
name: edge-gateway
selectors:
- match:
kuma.io/service: gateway-default
conf:
listeners:
- port: 8080
protocol: HTTP
tags:
port: http/8080
`, `
type: GatewayRoute
mesh: default
name: echo-service
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
rules:
- matches:
- path:
match: EXACT
value: /
backends:
- destination:
kuma.io/service: echo-service
`, `
type: GatewayRoute
mesh: default
name: echo-service-extra
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
hostnames:
- extra.example.com
rules:
- matches:
- path:
match: EXACT
value: /
backends:
- destination:
kuma.io/service: echo-service
`,
),

// Given a route with matching hostnames and a route with
// non-matching hostnames, only the route with matching
// hostnames should be configured on the Gateway.
//
// TODO(jpeach) This test won't test anything until we
// implement route generation, because neither of the two
// GatewayRoute fixtures generate anything at all
Entry("should match route hostnames on the listener",
"03-gateway-route.yaml", `
type: GatewayRoute
mesh: default
name: echo-service
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
hostnames:
- echo.example.com
rules:
- matches:
- path:
match: EXACT
value: /service/echo
backends:
- destination:
kuma.io/service: echo-service
`, `
type: GatewayRoute
mesh: default
name: echo-service-2
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
hostnames:
- foo.example.com
rules:
- matches:
- path:
match: EXACT
value: /service/foo
backends:
- destination:
kuma.io/service: echo-service
`,
),
)

})
Loading

0 comments on commit ca70bbf

Please sign in to comment.