Skip to content

Commit

Permalink
feat(rules): algo for inbound rules (#12560)
Browse files Browse the repository at this point in the history
## Implementation information

* add a new entry type `RuleEntry` and algo to compute resulting
configuration
* introduce descriptor flag `InterpretFromEntriesAsRules` and set it to
`true` for MeshTimeout (and potentially for all policies that support
only `Mesh` in `spec.from[].targetRef.kind`)

## Supporting documentation

Fix #12382

---------

Signed-off-by: Ilya Lobkov <[email protected]>
  • Loading branch information
lobkovilya authored Jan 23, 2025
1 parent b7131bb commit 8c6aeca
Show file tree
Hide file tree
Showing 66 changed files with 612 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,5 @@ var DoNothingResourceResourceTypeDescriptor = model.ResourceTypeDescriptor{
AllowedOnSystemNamespaceOnly: false,
IsReferenceableInTo: false,
ShortName: "dnr",
InterpretFromEntriesAsRules: false,
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,5 @@ var HostnameGeneratorResourceTypeDescriptor = model.ResourceTypeDescriptor{
AllowedOnSystemNamespaceOnly: true,
IsReferenceableInTo: false,
ShortName: "hg",
InterpretFromEntriesAsRules: false,
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,5 @@ var MeshExternalServiceResourceTypeDescriptor = model.ResourceTypeDescriptor{
AllowedOnSystemNamespaceOnly: true,
IsReferenceableInTo: true,
ShortName: "extsvc",
InterpretFromEntriesAsRules: false,
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,5 @@ var MeshMultiZoneServiceResourceTypeDescriptor = model.ResourceTypeDescriptor{
AllowedOnSystemNamespaceOnly: false,
IsReferenceableInTo: true,
ShortName: "mzsvc",
InterpretFromEntriesAsRules: false,
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,5 @@ var MeshServiceResourceTypeDescriptor = model.ResourceTypeDescriptor{
AllowedOnSystemNamespaceOnly: false,
IsReferenceableInTo: true,
ShortName: "msvc",
InterpretFromEntriesAsRules: false,
}
7 changes: 3 additions & 4 deletions pkg/core/resources/model/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ type ResourceTypeDescriptor struct {
IsReferenceableInTo bool
// ShortName a name that is used in kubectl or in the envoy configuration
ShortName string
// InterpretFromEntriesAsRules if true, the entries in the spec.from field should be interpreted as rules.
// It's true for policies that allow only kind 'Mesh' in the spec.from.targetRef.
InterpretFromEntriesAsRules bool
}

func newObject(baseResource Resource) Resource {
Expand Down Expand Up @@ -732,10 +735,6 @@ type PolicyItem interface {
GetDefault() interface{}
}

type RuleItem interface {
GetDefault() interface{}
}

type TransformDefaultAfterMerge interface {
Transform()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
InboundRules:
:0: []
Rules:
:0:
- BackendRefOriginIndex: {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
InboundRules:
:0:
- conf:
- connectionTimeout: 33s
http:
requestTimeout: 33s
origin:
- Resource:
creationTime: "0001-01-01T00:00:00Z"
mesh: mesh-1
modificationTime: "0001-01-01T00:00:00Z"
name: mt-2
type: MeshTimeout
RuleIndex: 0
- Resource:
creationTime: "0001-01-01T00:00:00Z"
mesh: mesh-1
modificationTime: "0001-01-01T00:00:00Z"
name: mt-1
type: MeshTimeout
RuleIndex: 0
Rules:
:0:
- BackendRefOriginIndex: {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
InboundRules: null
Rules:
:0:
- BackendRefOriginIndex: {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
InboundRules: null
Rules:
:0:
- BackendRefOriginIndex: {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
InboundRules:
1.1.1.1:8080: []
1.1.1.1:8081: []
Rules:
1.1.1.1:8080:
- BackendRefOriginIndex: {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
InboundRules:
1.1.1.1:8080: []
1.1.1.1:8081: []
Rules:
1.1.1.1:8080:
- BackendRefOriginIndex: {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
InboundRules:
1.1.1.1:8080: []
Rules:
1.1.1.1:8080:
- BackendRefOriginIndex: {}
Expand Down
110 changes: 110 additions & 0 deletions pkg/plugins/policies/core/rules/inbound/inboundrules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package inbound

import (
core_model "github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/core/resources/registry"
"github.com/kumahq/kuma/pkg/plugins/policies/core/rules/common"
"github.com/kumahq/kuma/pkg/plugins/policies/core/rules/merge"
)

type Rule struct {
Conf interface{} `json:"conf"`
Origin []common.Origin `json:"origin"`
}

type RuleEntry interface {
common.BaseEntry
}

// ruleEntryAdapter is a helper struct that allows using any BaseEntry as RuleEntry. For example, this is needed to
// provide backward compatibility for legacy FromEntries and use them as RuleEntries. Currently, RuleEntry and BaseEntry
// are the same, so this adapter is not needed, but in the future RuleEntry is expected to have additional methods
// like GetMatches() and GetTargetRef() that are not present in BaseEntry.
type ruleEntryAdapter[T common.BaseEntry] struct {
BaseEntry T
}

func newRuleEntryAdapter[T common.BaseEntry](base T) *ruleEntryAdapter[T] {
return &ruleEntryAdapter[T]{BaseEntry: base}
}

func (r *ruleEntryAdapter[T]) GetDefault() interface{} {
return r.BaseEntry.GetDefault()
}

type PolicyWithRules interface {
core_model.Policy
GetRules() []RuleEntry
}

func BuildRules(policies core_model.ResourceList) ([]*Rule, error) {
entries, err := getEntries(policies)
if err != nil {
return []*Rule{}, err
}
return buildRules(entries)
}

func getEntries(resources core_model.ResourceList) ([]common.WithPolicyAttributes[RuleEntry], error) {
desc, err := registry.Global().DescriptorFor(resources.GetItemType())
if err != nil {
return nil, err
}

policies, ok := common.Cast[interface {
PolicyWithRules
core_model.PolicyWithFromList
}](resources.GetItems())
if !ok {
return nil, nil
}

entries := []common.WithPolicyAttributes[RuleEntry]{}

for i, policy := range policies {
switch {
case len(policy.GetRules()) > 0:
for j, rule := range policy.GetRules() {
entries = append(entries, common.WithPolicyAttributes[RuleEntry]{
Entry: rule,
Meta: resources.GetItems()[i].GetMeta(),
TopLevel: policy.GetTargetRef(),
RuleIndex: j,
})
}
case desc.InterpretFromEntriesAsRules && len(policy.GetFromList()) > 0:
for j, fromEntry := range policy.GetFromList() {
entries = append(entries, common.WithPolicyAttributes[RuleEntry]{
Entry: newRuleEntryAdapter(fromEntry),
Meta: resources.GetItems()[i].GetMeta(),
TopLevel: policy.GetTargetRef(),
RuleIndex: j,
})
}
}
}

return entries, nil
}

func buildRules[T interface {
common.PolicyAttributes
common.Entry[RuleEntry]
}](list []T) ([]*Rule, error) {
if len(list) == 0 {
return []*Rule{}, nil
}

Sort(list)

merged, err := merge.Entries(list)
if err != nil {
return nil, err
}

ruleOrigin, _ := common.Origins(list, true)
return []*Rule{{
Conf: merged,
Origin: ruleOrigin,
}}, nil
}
48 changes: 48 additions & 0 deletions pkg/plugins/policies/core/rules/inbound/inboundrules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package inbound_test

import (
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"sigs.k8s.io/yaml"

core_model "github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/plugins/policies/core/rules/inbound"
"github.com/kumahq/kuma/pkg/test"
"github.com/kumahq/kuma/pkg/test/matchers"
"github.com/kumahq/kuma/pkg/test/resources/file"
)

var _ = Describe("BuildInboundRules", func() {
buildResourceList := func(rs []core_model.Resource) core_model.ResourceList {
Expect(rs).ToNot(BeEmpty())
rl := rs[0].Descriptor().NewList()
for _, p := range rs {
if strings.HasPrefix(p.GetMeta().GetName(), "matched-for-rules-") {
_ = rl.AddItem(p)
}
}
return rl
}

DescribeTable("should build a rule-based view for policies",
func(inputFile string) {
// given
resources := file.ReadInputFile(inputFile)
resourceList := buildResourceList(resources)

// when
rules, err := inbound.BuildRules(resourceList)
Expect(err).ToNot(HaveOccurred())

// then
bytes, err := yaml.Marshal(struct {
Rules []*inbound.Rule `json:"rules"`
}{Rules: rules})
Expect(err).ToNot(HaveOccurred())
Expect(bytes).To(matchers.MatchGoldenYAML(strings.Replace(inputFile, ".input.", ".golden.", 1)))
},
test.EntriesForFolder("inboundrules"),
)
})
15 changes: 15 additions & 0 deletions pkg/plugins/policies/core/rules/inbound/sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package inbound

import (
"slices"

"github.com/kumahq/kuma/pkg/plugins/policies/core/rules/common"
"github.com/kumahq/kuma/pkg/plugins/policies/core/rules/sort"
)

func Sort[T common.PolicyAttributes](list []T) {
slices.SortStableFunc(list, sort.Compose(
sort.CompareByPolicyAttributes[T],
sort.CompareByDisplayName[T],
))
}
11 changes: 11 additions & 0 deletions pkg/plugins/policies/core/rules/inbound/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package inbound_test

import (
"testing"

"github.com/kumahq/kuma/pkg/test"
)

func TestRules(t *testing.T) {
test.RunSpecs(t, "Inbound Rules Suite")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
rules:
- conf:
- connectionTimeout: 1m41s
http:
requestTimeout: 12s
idleTimeout: 10s
origin:
- Resource:
creationTime: "0001-01-01T00:00:00Z"
mesh: mesh-1
modificationTime: "0001-01-01T00:00:00Z"
name: matched-for-rules-mt-bbbbbb
type: MeshTimeout
RuleIndex: 0
- Resource:
creationTime: "0001-01-01T00:00:00Z"
mesh: mesh-1
modificationTime: "0001-01-01T00:00:00Z"
name: matched-for-rules-mt-aaaaaa
type: MeshTimeout
RuleIndex: 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# it should handle policies with 'from' when InterpretFromEntriesAsRules is set to true
type: MeshTimeout
name: matched-for-rules-mt-aaaaaa
mesh: mesh-1
spec:
from:
- targetRef:
kind: Mesh
default:
idleTimeout: 10s
http:
requestTimeout: 12s
---
type: MeshTimeout
name: matched-for-rules-mt-bbbbbb
mesh: mesh-1
spec:
from:
- targetRef:
kind: Mesh
default:
idleTimeout: 100s
connectionTimeout: 101s
http:
requestTimeout: 102s
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
rules:
- conf:
- connectionTimeout: 1m41s
http:
requestTimeout: 3m22s
idleTimeout: 10s
origin:
- Resource:
creationTime: "0001-01-01T00:00:00Z"
mesh: mesh-1
modificationTime: "0001-01-01T00:00:00Z"
name: matched-for-rules-mt-cccccc
type: MeshTimeout
RuleIndex: 0
- Resource:
creationTime: "0001-01-01T00:00:00Z"
mesh: mesh-1
modificationTime: "0001-01-01T00:00:00Z"
name: matched-for-rules-mt-bbbbbb
type: MeshTimeout
RuleIndex: 0
- Resource:
creationTime: "0001-01-01T00:00:00Z"
mesh: mesh-1
modificationTime: "0001-01-01T00:00:00Z"
name: matched-for-rules-mt-aaaaaa
type: MeshTimeout
RuleIndex: 0
Loading

0 comments on commit 8c6aeca

Please sign in to comment.