Skip to content

Commit

Permalink
kuma-cp: pick a single the most specific TrafficRoute for every outbo…
Browse files Browse the repository at this point in the history
…und interface of a Dataplane
  • Loading branch information
yskopets committed Nov 11, 2019
1 parent 8395582 commit dfa44b6
Show file tree
Hide file tree
Showing 14 changed files with 1,625 additions and 52 deletions.
69 changes: 64 additions & 5 deletions api/mesh/v1alpha1/dataplane_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ func (n *Dataplane_Networking) GetInboundInterfaces() ([]InboundInterface, error
return ifaces, nil
}

// Matches is simply an alias for MatchTags to make source code more aesthetic.
func (d *Dataplane) Matches(selector TagSelector) bool {
if d != nil {
return d.MatchTags(selector)
}
return false
}

func (d *Dataplane) MatchTags(selector TagSelector) bool {
for _, inbound := range d.GetNetworking().GetInbound() {
if inbound.MatchTags(selector) {
Expand Down Expand Up @@ -200,9 +208,36 @@ func (s TagSelector) Matches(tags map[string]string) bool {
return true
}

type Tags map[string]map[string]bool
func (s TagSelector) Rank() (r TagSelectorRank) {
for _, value := range s {
if value == MatchAllTag {
r.WildcardMatches++
} else {
r.ExactMatches++
}
}
return
}

func MatchAll() TagSelector {
return nil
}

func MatchService(service string) TagSelector {
return TagSelector{ServiceTag: service}
}

func (t Tags) Values(key string) []string {
func MatchTags(tags map[string]string) TagSelector {
return TagSelector(tags)
}

// Set of tags that only allows a single value per key.
type SingleValueTagSet map[string]string

// Set of tags that allows multiple values per key.
type MultiValueTagSet map[string]map[string]bool

func (t MultiValueTagSet) Values(key string) []string {
if t == nil {
return nil
}
Expand All @@ -214,8 +249,8 @@ func (t Tags) Values(key string) []string {
return result
}

func (d *Dataplane) Tags() Tags {
tags := Tags{}
func (d *Dataplane) Tags() MultiValueTagSet {
tags := MultiValueTagSet{}
for _, inbound := range d.GetNetworking().GetInbound() {
for tag, value := range inbound.Tags {
_, exists := tags[tag]
Expand All @@ -236,11 +271,35 @@ func (d *Dataplane) GetIdentifyingService() string {
return ServiceUnknown
}

func (t Tags) String() string {
func (t MultiValueTagSet) String() string {
var tags []string
for tag := range t {
tags = append(tags, fmt.Sprintf("%s=%s", tag, strings.Join(t.Values(tag), ",")))
}
sort.Strings(tags)
return strings.Join(tags, " ")
}

// TagSelectorRank helps to decide which of 2 selectors is more specific.
type TagSelectorRank struct {
// Number of tags that match by the exact value.
ExactMatches int
// Number of tags that match by a wildcard ('*').
WildcardMatches int
}

func (r TagSelectorRank) CombinedWith(other TagSelectorRank) TagSelectorRank {
return TagSelectorRank{
ExactMatches: r.ExactMatches + other.ExactMatches,
WildcardMatches: r.WildcardMatches + other.WildcardMatches,
}
}

func (r TagSelectorRank) CompareTo(other TagSelectorRank) int {
thisTotal := r.ExactMatches + r.WildcardMatches
otherTotal := other.ExactMatches + other.WildcardMatches
if thisTotal == otherTotal {
return r.ExactMatches - other.ExactMatches
}
return thisTotal - otherTotal
}
118 changes: 112 additions & 6 deletions api/mesh/v1alpha1/dataplane_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ var _ = Describe("ServiceTagValue", func() {
}
},
Entry("name and port", testCase{
value: "web.default.svc:80",
value: "web.default.svc:80",
expectedHost: "web.default.svc",
expectedPort: 80,
expectedErr: "",
Expand Down Expand Up @@ -308,15 +308,16 @@ var _ = Describe("Dataplane_Networking", func() {

var _ = Describe("Dataplane_Networking_Outbound", func() {
type testCase struct {
serviceTag string
selector TagSelector
serviceTag string
selector TagSelector
expectedMatch bool
}
DescribeTable("MatchTags()", func(given testCase) {
DescribeTable("MatchTags()",
func(given testCase) {
//given
outbound := Dataplane_Networking_Outbound{
Interface: "sdf",
Service: given.serviceTag,
Service: given.serviceTag,
}

// when
Expand Down Expand Up @@ -466,9 +467,114 @@ var _ = Describe("Tags", func() {
}

// when
result := Tags(tags).String()
result := MultiValueTagSet(tags).String()

// then
Expect(result).To(Equal("service=backend-admin,backend-api version=v1"))
})
})

var _ = Describe("TagSelectorRank", func() {

Describe("CompareTo()", func() {
type testCase struct {
rank1 TagSelectorRank
rank2 TagSelectorRank
expected int
}
DescribeTable("should correctly compare two ranks",
func(given testCase) {
// expect
Expect(given.rank1.CompareTo(given.rank2)).To(Equal(given.expected))
},
Entry("0 ranks are equal", testCase{
rank1: TagSelectorRank{},
rank2: TagSelectorRank{},
expected: 0,
}),
Entry("matches by the same number of exact values (1) are equal", testCase{
rank1: TagSelectorRank{ExactMatches: 1},
rank2: TagSelectorRank{ExactMatches: 1},
expected: 0,
}),
Entry("matches by the same number of wildcard values (2) are equal", testCase{
rank1: TagSelectorRank{WildcardMatches: 2},
rank2: TagSelectorRank{WildcardMatches: 2},
expected: 0,
}),
Entry("equal ranks by non-0 ExactMatches and WildcardMatches", testCase{
rank1: TagSelectorRank{ExactMatches: 1, WildcardMatches: 2},
rank2: TagSelectorRank{ExactMatches: 1, WildcardMatches: 2},
expected: 0,
}),
Entry("match by an exact value (1) is more specific than match by a wildcard", testCase{
rank1: TagSelectorRank{ExactMatches: 1, WildcardMatches: 0},
rank2: TagSelectorRank{ExactMatches: 0, WildcardMatches: 1},
expected: 1,
}),
Entry("match by a wildcard is less specific than match by an exact value (1)", testCase{
rank1: TagSelectorRank{ExactMatches: 0, WildcardMatches: 1},
rank2: TagSelectorRank{ExactMatches: 1, WildcardMatches: 0},
expected: -1,
}),
Entry("match by an exact value (2) is more specific than match by a wildcard", testCase{
rank1: TagSelectorRank{ExactMatches: 2, WildcardMatches: 0},
rank2: TagSelectorRank{ExactMatches: 0, WildcardMatches: 2},
expected: 2,
}),
Entry("match by a wildcard is less specific than match by an exact value (2)", testCase{
rank1: TagSelectorRank{ExactMatches: 0, WildcardMatches: 2},
rank2: TagSelectorRank{ExactMatches: 2, WildcardMatches: 0},
expected: -2,
}),
Entry("match by an exact value (3) is more specific than match by a wildcard", testCase{
rank1: TagSelectorRank{ExactMatches: 3, WildcardMatches: 0},
rank2: TagSelectorRank{ExactMatches: 0, WildcardMatches: 3},
expected: 3,
}),
Entry("match by a wildcard is less specific than match by an exact value (3)", testCase{
rank1: TagSelectorRank{ExactMatches: 0, WildcardMatches: 3},
rank2: TagSelectorRank{ExactMatches: 3, WildcardMatches: 0},
expected: -3,
}),
Entry("match by an exact value is more specific than match by a wildcard", testCase{
rank1: TagSelectorRank{ExactMatches: 2, WildcardMatches: 1},
rank2: TagSelectorRank{ExactMatches: 1, WildcardMatches: 1},
expected: 1,
}),
Entry("match by a wildcard is less specific than match by an exact value", testCase{
rank1: TagSelectorRank{ExactMatches: 2, WildcardMatches: 1},
rank2: TagSelectorRank{ExactMatches: 1, WildcardMatches: 1},
expected: 1,
}),
)
})
Describe("CombinedWith()", func() {
type testCase struct {
rank1 TagSelectorRank
rank2 TagSelectorRank
expected TagSelectorRank
}
DescribeTable("should correctly aggregate two ranks",
func(given testCase) {
// expect
Expect(given.rank1.CombinedWith(given.rank2)).To(Equal(given.expected))
},
Entry("combination of two 0 ranks is zero rank", testCase{
rank1: TagSelectorRank{},
rank2: TagSelectorRank{},
expected: TagSelectorRank{},
}),
Entry("cobination of a match by an exact value with a match by a wildcard", testCase{
rank1: TagSelectorRank{ExactMatches: 1},
rank2: TagSelectorRank{WildcardMatches: 2},
expected: TagSelectorRank{ExactMatches: 1, WildcardMatches: 2},
}),
Entry("cobination of two mixed matches", testCase{
rank1: TagSelectorRank{ExactMatches: 1, WildcardMatches: 2},
rank2: TagSelectorRank{ExactMatches: 10, WildcardMatches: 20},
expected: TagSelectorRank{ExactMatches: 11, WildcardMatches: 22},
}),
)
})
})
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1X
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 h1:mgAKeshyNqWKdENOnQsg+8dRTwZFIwFaO3HNl52sweA=
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191108234033-bd318be0434a h1:R/qVym5WAxsZWQqZCwDY/8sdVKV1m1WgU4/S5IRQAzc=
golang.org/x/crypto v0.0.0-20191108234033-bd318be0434a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down Expand Up @@ -475,6 +476,7 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190909082730-f460065e899a h1:mIzbOulag9/gXacgxKlFVwpCOWSfBT3/pDyyCwGA9as=
golang.org/x/sys v0.0.0-20190909082730-f460065e899a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
32 changes: 26 additions & 6 deletions pkg/core/xds/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ package xds

import (
"fmt"
"github.com/Kong/kuma/pkg/core/logs"
"github.com/Kong/kuma/pkg/core/permissions"
"net"
"strings"

"github.com/pkg/errors"

mesh_proto "github.com/Kong/kuma/api/mesh/v1alpha1"
"github.com/Kong/kuma/pkg/core/logs"
"github.com/Kong/kuma/pkg/core/permissions"
mesh_core "github.com/Kong/kuma/pkg/core/resources/apis/mesh"
core_model "github.com/Kong/kuma/pkg/core/resources/model"

envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"

"github.com/pkg/errors"
)

type ProxyId struct {
Expand All @@ -25,12 +25,32 @@ func (id *ProxyId) String() string {
return fmt.Sprintf("%s.%s.%s", id.Mesh, id.Name, id.Namespace)
}

// ServiceName is a convenience type alias to clarify the meaning of string value.
type ServiceName = string

// RouteMap holds the most specific TrafficRoute for each outbound interface of a Dataplane.
type RouteMap map[ServiceName]*mesh_core.TrafficRouteResource

// DestinationMap holds selectors for all reachable Dataplanes grouped by service name.
type DestinationMap map[ServiceName][]mesh_proto.TagSelector

// Endpoint holds routing-related information about a single endpoint.
type Endpoint struct {
Target string
Port uint32
Tags map[string]string
}

// EndpointMap holds routing-related information about a set of endpoints grouped by service name.
type EndpointMap map[ServiceName][]Endpoint

type Proxy struct {
Id ProxyId
Dataplane *mesh_core.DataplaneResource
TrafficPermissions permissions.MatchedPermissions
Logs *logs.MatchedLogs
OutboundTargets map[string][]net.SRV
TrafficRoutes RouteMap
OutboundTargets EndpointMap
Metadata *DataplaneMetadata
}

Expand Down
5 changes: 2 additions & 3 deletions pkg/xds/envoy/envoy.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package envoy

import (
"net"
"time"

"github.com/Kong/kuma/api/mesh/v1alpha1"
Expand Down Expand Up @@ -56,7 +55,7 @@ func CreateStaticEndpoint(clusterName string, address string, port uint32) *v2.C
}
}

func CreateClusterLoadAssignment(clusterName string, endpoints []net.SRV) *v2.ClusterLoadAssignment {
func CreateClusterLoadAssignment(clusterName string, endpoints []core_xds.Endpoint) *v2.ClusterLoadAssignment {
lbEndpoints := make([]*envoy_endpoint.LbEndpoint, 0, len(endpoints))
for _, ep := range endpoints {
lbEndpoints = append(lbEndpoints, &envoy_endpoint.LbEndpoint{
Expand All @@ -68,7 +67,7 @@ func CreateClusterLoadAssignment(clusterName string, endpoints []net.SRV) *v2.Cl
Protocol: envoy_core.SocketAddress_TCP,
Address: ep.Target,
PortSpecifier: &envoy_core.SocketAddress_PortValue{
PortValue: uint32(ep.Port),
PortValue: ep.Port,
},
},
},
Expand Down
4 changes: 1 addition & 3 deletions pkg/xds/envoy/envoy_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package envoy_test

import (
"net"

"github.com/Kong/kuma/pkg/core/xds"

. "github.com/onsi/ginkgo"
Expand Down Expand Up @@ -257,7 +255,7 @@ var _ = Describe("Envoy", func() {
`
// when
resource := envoy.CreateClusterLoadAssignment("127.0.0.1:8080",
[]net.SRV{
[]xds.Endpoint{
{Target: "192.168.0.1", Port: 8081},
{Target: "192.168.0.2", Port: 8082},
})
Expand Down
10 changes: 5 additions & 5 deletions pkg/xds/generator/outbound_proxy_generator_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package generator_test

import (
"github.com/Kong/kuma/pkg/core/logs"
"io/ioutil"
"net"
"path/filepath"

"github.com/Kong/kuma/pkg/core/logs"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -64,12 +64,12 @@ var _ = Describe("OutboundProxyGenerator", func() {
},
Spec: dataplane,
},
OutboundTargets: map[string][]net.SRV{
"backend": []net.SRV{
OutboundTargets: map[string][]model.Endpoint{
"backend": []model.Endpoint{
{Target: "192.168.0.1", Port: 8081},
{Target: "192.168.0.2", Port: 8082},
},
"db": []net.SRV{
"db": []model.Endpoint{
{Target: "192.168.0.3", Port: 5432},
},
},
Expand Down
Loading

0 comments on commit dfa44b6

Please sign in to comment.