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): consumer policies on app's namespace #10361

Merged
4 changes: 2 additions & 2 deletions api/common/v1alpha1/ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ var (
Gateway TargetRefProxyType = "Gateway"
)

func (k TargetRefKind) Less(o TargetRefKind) bool {
return order[k] < order[o]
func (k TargetRefKind) Compare(o TargetRefKind) int {
return order[k] - order[o]
}

func AllTargetRefKinds() []TargetRefKind {
Expand Down
13 changes: 13 additions & 0 deletions api/mesh/v1alpha1/dataplane_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ const (
// at this moment is "shadow". When effect is "shadow" the policy doesn't change the DPPs configs, but could be
// observed using the Inspect API.
EffectLabel = "kuma.io/effect"

// PolicyRoleLabel is a standard label that reflects the role of the policy. The value is automatically set by the
// Kuma CP based on the policy spec. Supported values are "producer", "consumer", "system" and "workload-owner".
PolicyRoleLabel = "kuma.io/policy-role"
)

type ResourceOrigin string
Expand All @@ -65,6 +69,15 @@ const (
ZoneResourceOrigin ResourceOrigin = "zone"
)

type PolicyRole string

const (
ProducerPolicyRole PolicyRole = "producer"
ConsumerPolicyRole PolicyRole = "consumer"
SystemPolicyRole PolicyRole = "system"
WorkloadOwnerPolicyRole PolicyRole = "workload-owner"
)

func (o ResourceOrigin) IsValid() error {
switch o {
case GlobalResourceOrigin, ZoneResourceOrigin:
Expand Down
12 changes: 4 additions & 8 deletions pkg/api-server/resource_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type resourceEndpoints struct {
filter func(request *restful.Request) (store.ListFilterFunc, error)
meshContextBuilder xds_context.MeshContextBuilder
xdsHooks []xds_hooks.ResourceSetHook
systemNamespace string

disableOriginLabelValidation bool
}
Expand Down Expand Up @@ -382,20 +383,15 @@ func (r *resourceEndpoints) createResource(
return
}

labels := resRest.GetMeta().GetLabels()
if r.mode == config_core.Zone {
if labels == nil {
labels = map[string]string{}
}
labels[mesh_proto.ResourceOriginLabel] = string(mesh_proto.ZoneResourceOrigin)
}

res := r.descriptor.NewObject()
_ = res.SetSpec(resRest.GetSpec())
res.SetMeta(resRest.GetMeta())
if r.descriptor.HasStatus {
_ = res.SetStatus(resRest.GetStatus())
}

labels := model.ComputeLabels(res, r.mode, false, r.systemNamespace)

if err := r.resManager.Create(ctx, res, store.CreateByKey(name, meshName), store.CreateWithLabels(labels)); err != nil {
rest_errors.HandleError(ctx, response, err, "Could not create a resource")
return
Expand Down
1 change: 1 addition & 0 deletions pkg/api-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ func addResourcesEndpoints(
meshContextBuilder: meshContextBuilder,
disableOriginLabelValidation: cfg.Multizone.Zone.DisableOriginLabelValidation,
xdsHooks: xdsHooks,
systemNamespace: cfg.Store.Kubernetes.SystemNamespace,
}
if cfg.Mode == config_core.Zone && cfg.Multizone != nil && cfg.Multizone.Zone != nil {
endpoints.zoneName = cfg.Multizone.Zone.Name
Expand Down
61 changes: 61 additions & 0 deletions pkg/core/resources/model/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
common_api "github.com/kumahq/kuma/api/common/v1alpha1"
mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
config_core "github.com/kumahq/kuma/pkg/config/core"
"github.com/kumahq/kuma/pkg/plugins/runtime/k8s/metadata"
)

const (
Expand Down Expand Up @@ -435,6 +436,66 @@ func ResourceOrigin(rm ResourceMeta) (mesh_proto.ResourceOrigin, bool) {
return "", false
}

func ComputeLabels(r Resource, mode config_core.CpMode, isK8s bool, systemNamespace string) map[string]string {
labels := r.GetMeta().GetLabels()
if len(labels) == 0 {
labels = map[string]string{}
}

setIfNotExist := func(k, v string) {
if _, ok := labels[k]; !ok {
labels[k] = v
}
}

if isK8s && r.Descriptor().Scope == ScopeMesh {
setIfNotExist(metadata.KumaMeshLabel, DefaultMesh)
}

if mode == config_core.Zone {
setIfNotExist(mesh_proto.ResourceOriginLabel, string(mesh_proto.ZoneResourceOrigin))
}

if ns, ok := labels[mesh_proto.KubeNamespaceTag]; ok && r.Descriptor().IsPolicy && r.Descriptor().IsPluginOriginated {
var role mesh_proto.PolicyRole
switch ns {
case systemNamespace:
role = mesh_proto.SystemPolicyRole
default:
role = ComputePolicyRole(r.GetSpec().(Policy))
}
setIfNotExist(mesh_proto.PolicyRoleLabel, string(role))
}

return labels
}

func ComputePolicyRole(p Policy) mesh_proto.PolicyRole {
hasTo := false
if pwtl, ok := p.(PolicyWithToList); ok && len(pwtl.GetToList()) > 0 {
hasTo = true
}

hasFrom := false
if pwfl, ok := p.(PolicyWithFromList); ok && len(pwfl.GetFromList()) > 0 {
hasFrom = true
}

if hasTo && !hasFrom {
// todo(lobkovilya): detect if the policy is a producer policy when they're supported
return mesh_proto.ConsumerPolicyRole
} else {
return mesh_proto.WorkloadOwnerPolicyRole
}
}
lobkovilya marked this conversation as resolved.
Show resolved Hide resolved

func PolicyRole(r Resource) mesh_proto.PolicyRole {
if r.GetMeta() == nil || r.GetMeta().GetLabels() == nil || r.GetMeta().GetLabels()[mesh_proto.PolicyRoleLabel] == "" {
return mesh_proto.SystemPolicyRole
}
return mesh_proto.PolicyRole(r.GetMeta().GetLabels()[mesh_proto.PolicyRoleLabel])
}

// ZoneOfResource returns zone from which the resource was synced to Global CP
// There is no information in the resource itself whether the resource is synced or created on the CP.
// Therefore, it's a caller responsibility to make use it only on synced resources.
Expand Down
78 changes: 64 additions & 14 deletions pkg/core/resources/model/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ package model_test
import (
"fmt"
"reflect"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kube_meta "k8s.io/apimachinery/pkg/apis/meta/v1"

mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
"github.com/kumahq/kuma/pkg/core/resources/model"
core_model "github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/kds"
"github.com/kumahq/kuma/pkg/kds/context"
"github.com/kumahq/kuma/pkg/kds/reconcile"
policies_api "github.com/kumahq/kuma/pkg/plugins/policies/meshaccesslog/api/v1alpha1"
"github.com/kumahq/kuma/pkg/plugins/policies/meshtimeout/api/v1alpha1"
meshtimeout_api "github.com/kumahq/kuma/pkg/plugins/policies/meshtimeout/api/v1alpha1"
"github.com/kumahq/kuma/pkg/test/resources/builders"
test_model "github.com/kumahq/kuma/pkg/test/resources/model"
)

Expand All @@ -32,9 +35,9 @@ var _ = Describe("Resource", func() {
})

var _ = Describe("IsReferenced", func() {
metaFuncs := map[string]map[string]func(mesh, name string) model.ResourceMeta{
metaFuncs := map[string]map[string]func(mesh, name string) core_model.ResourceMeta{
"zone": {
"k8s": func(mesh, name string) model.ResourceMeta {
"k8s": func(mesh, name string) core_model.ResourceMeta {
return &test_model.ResourceMeta{
Mesh: mesh,
Name: fmt.Sprintf("%s.foo", name),
Expand All @@ -46,7 +49,7 @@ var _ = Describe("IsReferenced", func() {
},
}
},
"universal": func(mesh, name string) model.ResourceMeta {
"universal": func(mesh, name string) core_model.ResourceMeta {
return &test_model.ResourceMeta{
Mesh: mesh,
Name: name,
Expand All @@ -58,7 +61,7 @@ var _ = Describe("IsReferenced", func() {
},
},
"global": {
"k8s": func(mesh, name string) model.ResourceMeta {
"k8s": func(mesh, name string) core_model.ResourceMeta {
return &test_model.ResourceMeta{
Mesh: mesh,
Name: fmt.Sprintf("%s.foo", name),
Expand All @@ -69,7 +72,7 @@ var _ = Describe("IsReferenced", func() {
},
}
},
"universal": func(mesh, name string) model.ResourceMeta {
"universal": func(mesh, name string) core_model.ResourceMeta {
return &test_model.ResourceMeta{
Mesh: mesh,
Name: name,
Expand All @@ -81,8 +84,8 @@ var _ = Describe("IsReferenced", func() {
},
}

syncTo := func(meta func(mesh, name string) model.ResourceMeta, dst string) func(mesh, name string) model.ResourceMeta {
return func(mesh, name string) model.ResourceMeta {
syncTo := func(meta func(mesh, name string) core_model.ResourceMeta, dst string) func(mesh, name string) core_model.ResourceMeta {
return func(mesh, name string) core_model.ResourceMeta {
gm := meta(mesh, name)

var mapper reconcile.ResourceMapper
Expand All @@ -93,7 +96,7 @@ var _ = Describe("IsReferenced", func() {
mapper = context.HashSuffixMapper(true)
}

r := v1alpha1.NewMeshTimeoutResource() // resource doesn't matter, we just want to call mapper to get a new meta
r := meshtimeout_api.NewMeshTimeoutResource() // resource doesn't matter, we just want to call mapper to get a new meta
r.SetMeta(gm)
nr, err := mapper(kds.Features{}, r)
Expect(err).ToNot(HaveOccurred())
Expand Down Expand Up @@ -144,23 +147,70 @@ var _ = Describe("IsReferenced", func() {
}

It("should return true when t1 is referencing route-1", func() {
Expect(model.IsReferenced(refMeta("m1", "t1"), "route-1", resMeta("m1", "route-1"))).To(BeTrue())
Expect(core_model.IsReferenced(refMeta("m1", "t1"), "route-1", resMeta("m1", "route-1"))).To(BeTrue())
})

It("should return false when t1 is referencing route-2", func() {
Expect(model.IsReferenced(refMeta("m1", "t1"), "route-2", resMeta("m1", "route-1"))).To(BeFalse())
Expect(core_model.IsReferenced(refMeta("m1", "t1"), "route-2", resMeta("m1", "route-1"))).To(BeFalse())
})

It("should return false when meshes are different", func() {
Expect(model.IsReferenced(refMeta("m1", "t1"), "route-1", resMeta("m2", "route-1"))).To(BeFalse())
Expect(core_model.IsReferenced(refMeta("m1", "t1"), "route-1", resMeta("m2", "route-1"))).To(BeFalse())
})

It("should return true when route name has max allowed length", func() {
longRouteName := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
Expect(model.IsReferenced(refMeta("m1", "t1"), longRouteName, resMeta("m1", longRouteName))).To(BeTrue())
Expect(core_model.IsReferenced(refMeta("m1", "t1"), longRouteName, resMeta("m1", longRouteName))).To(BeTrue())
})
}, entries,
)
})

var _ = Describe("ComputePolicyRole", func() {
type testCase struct {
policy core_model.Policy
expectedRole mesh_proto.PolicyRole
}

DescribeTable("should compute the correct policy role",
func(given testCase) {
role := core_model.ComputePolicyRole(given.policy)
Expect(role).To(Equal(given.expectedRole))
},
Entry("consumer policy", testCase{
policy: builders.MeshTimeout().
WithMesh("mesh-1").WithName("name-1").
WithTargetRef(builders.TargetRefMesh()).
AddTo(builders.TargetRefMesh(), meshtimeout_api.Conf{
IdleTimeout: &kube_meta.Duration{Duration: 123 * time.Second},
}).
Build().Spec,
expectedRole: mesh_proto.ConsumerPolicyRole,
}),
Entry("workload-owner policy with from", testCase{
policy: builders.MeshTimeout().
WithMesh("mesh-1").WithName("name-1").
WithTargetRef(builders.TargetRefMesh()).
AddFrom(builders.TargetRefMesh(), meshtimeout_api.Conf{
IdleTimeout: &kube_meta.Duration{Duration: 123 * time.Second},
}).
Build().Spec,
expectedRole: mesh_proto.WorkloadOwnerPolicyRole,
}),
Entry("workload-owner policy with both from and to", testCase{
policy: builders.MeshTimeout().
WithMesh("mesh-1").WithName("name-1").
WithTargetRef(builders.TargetRefMesh()).
AddTo(builders.TargetRefMesh(), meshtimeout_api.Conf{
IdleTimeout: &kube_meta.Duration{Duration: 123 * time.Second},
}).
AddFrom(builders.TargetRefMesh(), meshtimeout_api.Conf{
IdleTimeout: &kube_meta.Duration{Duration: 123 * time.Second},
}).
Build().Spec,
expectedRole: mesh_proto.WorkloadOwnerPolicyRole,
}),
)
})
Loading
Loading