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(kuma-cp): improve BuildRules algorithm #6973

Merged
merged 11 commits into from
Jun 12, 2023
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
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ require (
go.opentelemetry.io/proto/otlp v0.19.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.24.0
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.10.0
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0
Expand Down Expand Up @@ -84,6 +84,7 @@ require (
)

require (
gonum.org/v1/gonum v0.13.0
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e
)
Expand All @@ -100,7 +101,7 @@ require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/aws/aws-sdk-go v1.44.187 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/boombuler/barcode v1.0.1 // indirect
lobkovilya marked this conversation as resolved.
Show resolved Hide resolved
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
Expand Down Expand Up @@ -195,7 +196,7 @@ require (
golang.org/x/mod v0.10.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/tools v0.9.1 // indirect
golang.org/x/tools v0.9.3 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/api v0.122.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
Expand Down
13 changes: 8 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down Expand Up @@ -610,8 +611,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down Expand Up @@ -847,14 +848,16 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=
gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM=
gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
Expand Down
188 changes: 140 additions & 48 deletions pkg/plugins/policies/core/rules/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import (
"encoding"
"fmt"
"sort"
"strings"

"github.com/pkg/errors"
"golang.org/x/exp/maps"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/graph/topo"

common_api "github.com/kumahq/kuma/api/common/v1alpha1"
mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
Expand Down Expand Up @@ -73,10 +78,60 @@ func (ss Subset) IsSubset(other Subset) bool {
return false
}
for _, otherTag := range oTags {
if otherTag.Value == tag.Value && otherTag.Not != tag.Not {
if !isSubset(tag, otherTag) {
return false
}
if otherTag.Value != tag.Value && !otherTag.Not && !tag.Not {
}
}
return true
}

func isSubset(t1, t2 Tag) bool {
switch {
// t2={y: b} can't be a subset of t1={x: a} because point {y: b, x: c} belongs to t2, but doesn't belong to t1
case t1.Key != t2.Key:
return false

// t2={y: !a} is a subset of t1={y: !b} if and only if a == b
case t1.Not == t2.Not:
return t1.Value == t2.Value

// t2={y: a} is a subset of t1={y: !b} if and only if a != b
case t1.Not:
return t1.Value != t2.Value

// t2={y: !a} can't be a subset of t1={y: b} because point {y: c} belongs to t2, but doesn't belong to t1
case t2.Not:
return false

default:
panic("impossible")
}
}

// Intersect returns true if there exists an element that belongs both to 'other' and current set.
// Empty set intersects with all sets.
func (ss Subset) Intersect(other Subset) bool {
if len(ss) == 0 || len(other) == 0 {
return true
}
otherByKeysOnlyPositive := map[string][]Tag{}
for _, t := range other {
if t.Not {
continue
}
otherByKeysOnlyPositive[t.Key] = append(otherByKeysOnlyPositive[t.Key], t)
}
for _, tag := range ss {
if tag.Not {
continue
}
oTags, ok := otherByKeysOnlyPositive[tag.Key]
if !ok {
return true
}
for _, otherTag := range oTags {
if otherTag != tag {
return false
}
}
Expand Down Expand Up @@ -233,68 +288,97 @@ func BuildSingleItemRules(matchedPolicies []core_model.Resource) (SingleItemRule
func BuildRules(list []PolicyItemWithMeta) (Rules, error) {
rules := Rules{}

// 1. Each targetRef should be represented as a list of tags
tagSet := map[Tag]bool{}
// 1. Convert list of rules into the list of subsets
var subsets []Subset
for _, item := range list {
ss, err := asSubset(item.GetTargetRef())
if err != nil {
return nil, err
}
for _, t := range ss {
tagSet[t] = true
}
subsets = append(subsets, ss)
}
tags := []Tag{}
for tag := range tagSet {
tags = append(tags, tag)

// 2. Create a graph where nodes are subsets and edge exists between 2 subsets only if there is an intersection
g := simple.NewUndirectedGraph()

for nodeId := range subsets {
g.AddNode(simple.Node(nodeId))
}

sort.Slice(tags, func(i, j int) bool {
if tags[i].Key != tags[j].Key {
return tags[i].Key < tags[j].Key
for i := range subsets {
for j := range subsets {
if i == j {
continue
}
if subsets[i].Intersect(subsets[j]) {
g.SetEdge(simple.Edge{F: simple.Node(i), T: simple.Node(j)})
}
}
return tags[i].Value < tags[j].Value
}

// 3. Construct rules for all connected components of the graph independently
components := topo.ConnectedComponents(g)

sort.SliceStable(components, func(i, j int) bool {
return strings.Join(toStringList(components[i]), ":") > strings.Join(toStringList(components[j]), ":")
})

// 2. Iterate over all possible combinations with negations
iter := NewSubsetIter(tags)
for {
ss := iter.Next()
if ss == nil {
break
for _, nodes := range components {
tagSet := map[Tag]bool{}
for _, node := range nodes {
for _, t := range subsets[node.ID()] {
tagSet[t] = true
}
}

tags := []Tag{}
for tag := range tagSet {
tags = append(tags, tag)
}
// 3. For each combination determine a configuration
confs := []interface{}{}
// confs := []PolicyConf{}
distinctOrigins := map[core_model.ResourceKey]core_model.ResourceMeta{}
for i := 0; i < len(list); i++ {
item := list[i]
itemSubset, err := asSubset(item.GetTargetRef())

sort.Slice(tags, func(i, j int) bool {
if tags[i].Key != tags[j].Key {
return tags[i].Key < tags[j].Key
}
return tags[i].Value < tags[j].Value
})

// 4. Iterate over all possible combinations with negations
iter := NewSubsetIter(tags)
for {
ss := iter.Next()
if ss == nil {
break
}
// 5. For each combination determine a configuration
confs := []interface{}{}
distinctOrigins := map[core_model.ResourceKey]core_model.ResourceMeta{}
for i := 0; i < len(list); i++ {
item := list[i]
itemSubset, err := asSubset(item.GetTargetRef())
if err != nil {
return nil, err
}
if itemSubset.IsSubset(ss) {
confs = append(confs, item.GetDefault())
distinctOrigins[core_model.MetaToResourceKey(item.ResourceMeta)] = item.ResourceMeta
}
}
merged, err := MergeConfs(confs)
if err != nil {
return nil, err
}
if itemSubset.IsSubset(ss) {
confs = append(confs, item.GetDefault())
distinctOrigins[core_model.MetaToResourceKey(item.ResourceMeta)] = item.ResourceMeta
}
}
merged, err := MergeConfs(confs)
if err != nil {
return nil, err
}
if merged != nil {
var origins []core_model.ResourceMeta
for _, origin := range distinctOrigins {
origins = append(origins, origin)
if merged != nil {
origins := maps.Values(distinctOrigins)
sort.Slice(origins, func(i, j int) bool {
return origins[i].GetName() < origins[j].GetName()
})
rules = append(rules, &Rule{
Subset: ss,
Conf: merged,
Origin: origins,
})
}
sort.Slice(origins, func(i, j int) bool {
return origins[i].GetName() < origins[j].GetName()
})
rules = append(rules, &Rule{
Subset: ss,
Conf: merged,
Origin: origins,
})
}
}

Expand All @@ -305,6 +389,14 @@ func BuildRules(list []PolicyItemWithMeta) (Rules, error) {
return rules, nil
}

func toStringList(nodes []graph.Node) []string {
rv := make([]string, 0, len(nodes))
for _, id := range nodes {
rv = append(rv, fmt.Sprintf("%d", id.ID()))
}
return rv
}

func asSubset(tr common_api.TargetRef) (Subset, error) {
switch tr.Kind {
case common_api.Mesh:
Expand Down
Loading