Skip to content

Commit

Permalink
feat(kuma-cp) initial connection policy support for Gateway (#2933)
Browse files Browse the repository at this point in the history
Gateway resources create connections from the gateway (source) to
services running in the mesh (destination). Add support for matching
connection policies based on these sources and destinations.

The sources for connection policies are gateway listeners, but the
destinations aren't known until we process the relevant gateway route
types. So, we cache the policies that matched on the source (along with
their match rank) on each virtual host, and complete the match once the
final route table has been built.

Note that some Kuma connection policies are implemented by configuring
both the Envoy HTTP connection manager and the relevant upstream
cluster. These connection policies cannot be implemented in Gateway,
since they cannot configure the HTTP connection manager with different
setting for each destination.

Signed-off-by: James Peach <james.peach@konghq.com>
  • Loading branch information
jpeach authored Oct 15, 2021
1 parent 3981b24 commit c10ab8a
Show file tree
Hide file tree
Showing 26 changed files with 1,600 additions and 103 deletions.
18 changes: 18 additions & 0 deletions pkg/core/policy/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package policy

import (
mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
)

// MatchSelector succeeds if any of the given selectors matches the tags. It
// additionally returns the rank of the selector that matched the tag.
func MatchSelector(tags map[string]string, selectors []*mesh_proto.Selector) (mesh_proto.TagSelectorRank, bool) {
for _, selector := range selectors {
sourceSelector := mesh_proto.TagSelector(selector.GetMatch())
if sourceSelector.Matches(tags) {
return sourceSelector.Rank(), true
}
}

return mesh_proto.TagSelectorRank{}, false
}
83 changes: 83 additions & 0 deletions pkg/plugins/runtime/gateway/connection_policy_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package gateway

import (
"sort"

mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
"github.com/kumahq/kuma/pkg/core/policy"
"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"
"github.com/kumahq/kuma/pkg/xds/envoy"
)

// ConnectionPolicyGenerator matches connection policies for each route table
// entry that forwards traffic.
type ConnectionPolicyGenerator struct {
}

func (*ConnectionPolicyGenerator) SupportsProtocol(p mesh_proto.Gateway_Listener_Protocol) bool {
return true
}

func (g *ConnectionPolicyGenerator) GenerateHost(ctx xds_context.Context, info *GatewayResourceInfo) (*core_xds.ResourceSet, error) {
for _, e := range info.RouteTable.Entries {
for i, destination := range e.Action.Forward {
e.Action.Forward[i].Policies = mapPoliciesForDestination(destination.Destination, info)
}
if e.Mirror != nil {
e.Mirror.Forward.Policies = mapPoliciesForDestination(e.Mirror.Forward.Destination, info)
}
}

return nil, nil
}

func mapPoliciesForDestination(destination envoy.Tags, info *GatewayResourceInfo) map[model.ResourceType]model.Resource {
policies := map[model.ResourceType]model.Resource{}

for _, policyType := range ConnectionPolicyTypes {
if policy := matchConnectionPolicy(info.Host.Policies[policyType], destination); policy != nil {
policies[policyType] = policy
}
}

return policies
}

func matchConnectionPolicy(candidates []match.RankedPolicy, destination envoy.Tags) model.Resource {
var matches []match.RankedPolicy

for _, c := range candidates {
if rank, ok := policy.MatchSelector(destination, c.Policy.Destinations()); ok {
// Track this match with the combined source+destination rank.
matches = append(matches, match.RankedPolicy{
Rank: rank.CombinedWith(c.Rank),
Policy: c.Policy,
})
}
}

if len(matches) == 0 {
return nil
}

// Sort more specific (higher ranked) policies first.
sort.Slice(matches, func(i, j int) bool {
n := matches[i].Rank.CompareTo(matches[j].Rank)
switch {
case n < 0:
return false
case n > 0:
return true
default /* i == 0 */ :
// If the rank is the same, the most recent
// policy sorts to the front (i.e. takes priority).
return matches[i].Policy.GetMeta().GetCreationTime().After(
matches[j].Policy.GetMeta().GetCreationTime())
}
})

return matches[0].Policy
}
2 changes: 1 addition & 1 deletion pkg/plugins/runtime/gateway/gateway_route_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (g *GatewayRouteGenerator) GenerateHost(ctx xds_context.Context, info *Gate
}
}

// The kubernetes Ingress and Gateway APIs define prefix matching
// The Kubernetes Ingress and Gateway APIs define prefix matching
// to match in terms of path components, so we follow suit here.
// Envoy path prefix matching is byte-wise, so we need to do some
// transformations. Unless there is already an exact match for the
Expand Down
250 changes: 244 additions & 6 deletions pkg/plugins/runtime/gateway/gateway_route_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,9 @@ var _ = Describe("Gateway Gateway Route", func() {

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

dataplanes.Generate("echo-service")
dataplanes.Generate("echo-mirror")
dataplanes.Generate("exact-header-match")
dataplanes.Generate("regex-header-match")

// Add dataplane resources for all the services used in the test suite.
for _, service := range []string{
"api-service",
"echo-exact",
"echo-mirror",
"echo-prefix",
Expand Down Expand Up @@ -566,6 +562,248 @@ conf:
kuma.io/service: echo-service
`,
),
)

Entry("match timeout policy",
"15-gateway-route.yaml", `
type: GatewayRoute
mesh: default
name: echo-service
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
rules:
- matches:
- path:
match: PREFIX
value: /
filters:
- mirror:
percentage: 1
backend:
destination:
kuma.io/service: echo-mirror
backends:
- destination:
kuma.io/service: echo-service
- matches:
- path:
match: PREFIX
value: /api
backends:
- destination:
kuma.io/service: api-service
`, `
type: Timeout
mesh: default
name: echo-service
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: echo-service
conf:
connect_timeout: 10s
http:
request_timeout: 10s
idle_timeout: 10s
`, `
type: Timeout
mesh: default
name: api-service
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: api-service
conf:
connect_timeout: 20s
http:
request_timeout: 20s
idle_timeout: 20s
`, `
type: Timeout
mesh: default
name: echo-mirror
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: echo-mirror
conf:
connect_timeout: 300s
http:
request_timeout: 30s
idle_timeout: 30s
`,
),

Entry("match circuit breaker policy",
"16-gateway-route.yaml", `
type: GatewayRoute
mesh: default
name: echo-service
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
rules:
- matches:
- path:
match: PREFIX
value: /
filters:
- mirror:
percentage: 1
backend:
destination:
kuma.io/service: echo-mirror
backends:
- destination:
kuma.io/service: echo-service
- matches:
- path:
match: PREFIX
value: /api
backends:
- destination:
kuma.io/service: api-service
`, `
type: CircuitBreaker
mesh: default
name: echo-service
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: echo-service
conf:
baseEjectionTime: 10s
thresholds:
maxRetries: 10
detectors:
localErrors:
consecutive: 10
`, `
type: CircuitBreaker
mesh: default
name: api-service
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: api-service
conf:
baseEjectionTime: 20s
thresholds:
maxRetries: 20
detectors:
localErrors:
consecutive: 20
`, `
type: CircuitBreaker
mesh: default
name: echo-mirror
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: echo-mirror
conf:
baseEjectionTime: 30s
thresholds:
maxRetries: 20
detectors:
localErrors:
consecutive: 30
`,
),

Entry("match health check policy",
"17-gateway-route.yaml", `
type: GatewayRoute
mesh: default
name: echo-service
selectors:
- match:
kuma.io/service: gateway-default
conf:
http:
rules:
- matches:
- path:
match: PREFIX
value: /
filters:
- mirror:
percentage: 1
backend:
destination:
kuma.io/service: echo-mirror
backends:
- destination:
kuma.io/service: echo-service
- matches:
- path:
match: PREFIX
value: /api
backends:
- destination:
kuma.io/service: api-service
`, `
type: HealthCheck
mesh: default
name: echo-service
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: echo-service
conf:
interval: 10s
timeout: 10s
healthyThreshold: 1
unhealthyThreshold: 1
`, `
type: HealthCheck
mesh: default
name: api-service
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: api-service
conf:
interval: 20s
timeout: 20s
healthyThreshold: 2
unhealthyThreshold: 2
`, `
type: HealthCheck
mesh: default
name: echo-mirror
sources:
- match:
kuma.io/service: gateway-default
destinations:
- match:
kuma.io/service: echo-mirror
conf:
interval: 30s
timeout: 30s
healthyThreshold: 3
unhealthyThreshold: 3
`,
),
)
})
Loading

0 comments on commit c10ab8a

Please sign in to comment.