added computed failover policy resource (#19975)

pull/19976/head
aahel 11 months ago committed by GitHub
parent cae23821dc
commit ae998a698a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,6 +2,7 @@
flowchart TD
auth/v2beta1/computedtrafficpermissions --> auth/v2beta1/trafficpermissions
auth/v2beta1/computedtrafficpermissions --> auth/v2beta1/workloadidentity
catalog/v2beta1/computedfailoverpolicy
catalog/v2beta1/failoverpolicy --> catalog/v2beta1/service
catalog/v2beta1/healthstatus
catalog/v2beta1/node --> catalog/v2beta1/nodehealthstatus

@ -0,0 +1,55 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package types
import (
"fmt"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/internal/resource"
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
)
type DecodedComputedFailoverPolicy = resource.DecodedResource[*pbcatalog.ComputedFailoverPolicy]
func RegisterComputedFailoverPolicy(r resource.Registry) {
r.Register(resource.Registration{
Type: pbcatalog.ComputedFailoverPolicyType,
Proto: &pbcatalog.ComputedFailoverPolicy{},
Scope: resource.ScopeNamespace,
Validate: ValidateComputedFailoverPolicy,
ACLs: &resource.ACLHooks{
Read: aclReadHookFailoverPolicy,
Write: resource.DecodeAndAuthorizeWrite(aclWriteHookComputedFailoverPolicy),
List: resource.NoOpACLListHook,
},
})
}
var ValidateComputedFailoverPolicy = resource.DecodeAndValidate(validateComputedFailoverPolicy)
func validateComputedFailoverPolicy(res *DecodedComputedFailoverPolicy) error {
if res.Data.Config != nil && res.Data.Config.SamenessGroup != "" {
return fmt.Errorf(`invalid "config" field: computed failover policy cannot have a sameness_group`)
}
for _, fc := range res.Data.PortConfigs {
if fc.GetSamenessGroup() != "" {
return fmt.Errorf(`invalid "config" field: computed failover policy cannot have a sameness_group`)
}
}
dfp := convertToDecodedFailoverPolicy(res)
return validateFailoverPolicy(dfp)
}
func aclWriteHookComputedFailoverPolicy(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, res *DecodedComputedFailoverPolicy) error {
dfp := convertToDecodedFailoverPolicy(res)
return aclWriteHookFailoverPolicy(authorizer, authzContext, dfp)
}
func convertToDecodedFailoverPolicy(res *DecodedComputedFailoverPolicy) *DecodedFailoverPolicy {
dfp := &DecodedFailoverPolicy{}
dfp.Data = (*pbcatalog.FailoverPolicy)(res.GetData())
dfp.Resource = res.GetResource()
return dfp
}

@ -0,0 +1,250 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package types
import (
"fmt"
"testing"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/resourcetest"
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/version/versiontest"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
)
type computedFailoverTestcase struct {
failover *pbcatalog.ComputedFailoverPolicy
expectErr string
}
func convertToComputedFailverPolicyTestCases(fpCases map[string]failoverTestcase) map[string]computedFailoverTestcase {
cfpTestcases := map[string]computedFailoverTestcase{}
for k, v := range fpCases {
cfpTestcases[k] = computedFailoverTestcase{
failover: &pbcatalog.ComputedFailoverPolicy{
Config: v.failover.Config,
PortConfigs: v.failover.PortConfigs,
},
expectErr: v.expectErr,
}
}
return cfpTestcases
}
func TestValidateComputedFailoverPolicy(t *testing.T) {
run := func(t *testing.T, tc computedFailoverTestcase) {
res := resourcetest.Resource(pbcatalog.ComputedFailoverPolicyType, "api").
WithTenancy(resource.DefaultNamespacedTenancy()).
WithData(t, tc.failover).
Build()
err := ValidateComputedFailoverPolicy(res)
// Verify that validate didn't actually change the object.
got := resourcetest.MustDecode[*pbcatalog.ComputedFailoverPolicy](t, res)
prototest.AssertDeepEqual(t, tc.failover, got.Data)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
}
cases := convertToComputedFailverPolicyTestCases(getCommonTestCases())
cases["plain config: sameness_group"] = computedFailoverTestcase{
failover: &pbcatalog.ComputedFailoverPolicy{
Config: &pbcatalog.FailoverConfig{
SamenessGroup: "test",
},
},
expectErr: `invalid "config" field: computed failover policy cannot have a sameness_group`,
}
cases["ported config: sameness_group"] = computedFailoverTestcase{
failover: &pbcatalog.ComputedFailoverPolicy{
PortConfigs: map[string]*pbcatalog.FailoverConfig{
"http": {
SamenessGroup: "test",
},
},
},
expectErr: `invalid "config" field: computed failover policy cannot have a sameness_group`,
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
run(t, tc)
})
}
}
func TestComputedFailoverPolicyACLs(t *testing.T) {
// Wire up a registry to generically invoke hooks
testFailOverPolicyAcls(t, true)
}
func testFailOverPolicyAcls(t *testing.T, isComputedFailoverPolicy bool) {
registry := resource.NewRegistry()
Register(registry)
newFailover := func(t *testing.T, name, tenancyStr string, destRefs []*pbresource.Reference) []*pbresource.Resource {
var dr []*pbcatalog.FailoverDestination
for _, destRef := range destRefs {
dr = append(dr, &pbcatalog.FailoverDestination{Ref: destRef})
}
var (
res1 *pbresource.Resource
res2 *pbresource.Resource
)
cfgDests := &pbcatalog.FailoverConfig{Destinations: dr}
portedCfgDests := map[string]*pbcatalog.FailoverConfig{
"http": {Destinations: dr},
}
var cfgData, portedCfgData protoreflect.ProtoMessage
var typ *pbresource.Type
if isComputedFailoverPolicy {
typ = pbcatalog.ComputedFailoverPolicyType
cfgData = &pbcatalog.ComputedFailoverPolicy{
Config: cfgDests,
}
portedCfgData = &pbcatalog.ComputedFailoverPolicy{
PortConfigs: portedCfgDests,
}
} else {
typ = pbcatalog.FailoverPolicyType
cfgData = &pbcatalog.FailoverPolicy{
Config: cfgDests,
}
portedCfgData = &pbcatalog.FailoverPolicy{
PortConfigs: portedCfgDests,
}
}
res1 = resourcetest.Resource(typ, name).
WithTenancy(resourcetest.Tenancy(tenancyStr)).
WithData(t, cfgData).
Build()
resourcetest.ValidateAndNormalize(t, registry, res1)
res2 = resourcetest.Resource(typ, name).
WithTenancy(resourcetest.Tenancy(tenancyStr)).
WithData(t, portedCfgData).
Build()
resourcetest.ValidateAndNormalize(t, registry, res2)
return []*pbresource.Resource{res1, res2}
}
const (
DENY = resourcetest.DENY
ALLOW = resourcetest.ALLOW
DEFAULT = resourcetest.DEFAULT
)
serviceRef := func(tenancy, name string) *pbresource.Reference {
return newRefWithTenancy(pbcatalog.ServiceType, tenancy, name)
}
resOneDest := func(tenancy, destTenancy string) []*pbresource.Resource {
return newFailover(t, "api", tenancy, []*pbresource.Reference{
serviceRef(destTenancy, "dest1"),
})
}
resTwoDests := func(tenancy, destTenancy string) []*pbresource.Resource {
return newFailover(t, "api", tenancy, []*pbresource.Reference{
serviceRef(destTenancy, "dest1"),
serviceRef(destTenancy, "dest2"),
})
}
run := func(t *testing.T, name string, tc resourcetest.ACLTestCase) {
t.Run(name, func(t *testing.T) {
resourcetest.RunACLTestCase(t, tc, registry)
})
}
isEnterprise := versiontest.IsEnterprise()
serviceRead := func(partition, namespace, name string) string {
if isEnterprise {
return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "read" } } }`, partition, namespace, name)
}
return fmt.Sprintf(` service %q { policy = "read" } `, name)
}
serviceWrite := func(partition, namespace, name string) string {
if isEnterprise {
return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "write" } } }`, partition, namespace, name)
}
return fmt.Sprintf(` service %q { policy = "write" } `, name)
}
assert := func(t *testing.T, name string, rules string, resList []*pbresource.Resource, readOK, writeOK string) {
for i, res := range resList {
tc := resourcetest.ACLTestCase{
AuthCtx: resource.AuthorizerContext(res.Id.Tenancy),
Res: res,
Rules: rules,
ReadOK: readOK,
WriteOK: writeOK,
ListOK: DEFAULT,
}
run(t, fmt.Sprintf("%s-%d", name, i), tc)
}
}
tenancies := []string{"default.default"}
if isEnterprise {
tenancies = append(tenancies, "default.foo", "alpha.default", "alpha.foo")
}
for _, policyTenancyStr := range tenancies {
t.Run("policy tenancy: "+policyTenancyStr, func(t *testing.T) {
for _, destTenancyStr := range tenancies {
t.Run("dest tenancy: "+destTenancyStr, func(t *testing.T) {
for _, aclTenancyStr := range tenancies {
t.Run("acl tenancy: "+aclTenancyStr, func(t *testing.T) {
aclTenancy := resourcetest.Tenancy(aclTenancyStr)
maybe := func(match string, parentOnly bool) string {
if policyTenancyStr != aclTenancyStr {
return DENY
}
if !parentOnly && destTenancyStr != aclTenancyStr {
return DENY
}
return match
}
t.Run("no rules", func(t *testing.T) {
rules := ``
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), DENY, DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), DENY, DENY)
})
t.Run("api:read", func(t *testing.T) {
rules := serviceRead(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
t.Run("api:write", func(t *testing.T) {
rules := serviceWrite(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
t.Run("api:write dest1:read", func(t *testing.T) {
rules := serviceWrite(aclTenancy.Partition, aclTenancy.Namespace, "api") +
serviceRead(aclTenancy.Partition, aclTenancy.Namespace, "dest1")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), maybe(ALLOW, false))
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
})
}
})
}
})
}
}

@ -156,14 +156,6 @@ func validateFailoverPolicy(res *DecodedFailoverPolicy) error {
func validateFailoverConfig(config *pbcatalog.FailoverConfig, ported bool, wrapErr func(error) error) error {
var merr error
if config.SamenessGroup != "" {
// TODO(v2): handle other forms of failover
merr = multierror.Append(merr, wrapErr(resource.ErrInvalidField{
Name: "sameness_group",
Wrapped: fmt.Errorf("not supported in this release"),
}))
}
if len(config.Regions) > 0 {
merr = multierror.Append(merr, wrapErr(resource.ErrInvalidField{
Name: "regions",

@ -4,20 +4,17 @@
package types
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/resourcetest"
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/version/versiontest"
)
func TestMutateFailoverPolicy(t *testing.T) {
@ -191,54 +188,61 @@ func TestMutateFailoverPolicy(t *testing.T) {
}
}
func TestValidateFailoverPolicy(t *testing.T) {
type configTestcase struct {
config *pbcatalog.FailoverConfig
expectErr string
}
type testcase struct {
failover *pbcatalog.FailoverPolicy
expectErr string
}
run := func(t *testing.T, tc testcase) {
res := resourcetest.Resource(pbcatalog.FailoverPolicyType, "api").
WithTenancy(resource.DefaultNamespacedTenancy()).
WithData(t, tc.failover).
Build()
type failoverTestcase struct {
failover *pbcatalog.FailoverPolicy
expectErr string
}
require.NoError(t, MutateFailoverPolicy(res))
type configTestcase struct {
config *pbcatalog.FailoverConfig
expectErr string
}
// Verify that mutate didn't actually change the object.
got := resourcetest.MustDecode[*pbcatalog.FailoverPolicy](t, res)
prototest.AssertDeepEqual(t, tc.failover, got.Data)
func maybeWrap(wrapPrefix, base string) string {
if base != "" {
return wrapPrefix + base
}
return ""
}
err := ValidateFailoverPolicy(res)
func addFailoverConfigSamenessGroupCases(fpcases map[string]failoverTestcase) {
// Verify that validate didn't actually change the object.
got = resourcetest.MustDecode[*pbcatalog.FailoverPolicy](t, res)
prototest.AssertDeepEqual(t, tc.failover, got.Data)
configCases := map[string]configTestcase{}
configCases["dest with sameness"] = configTestcase{
config: &pbcatalog.FailoverConfig{
Destinations: []*pbcatalog.FailoverDestination{
{Ref: newRef(pbcatalog.ServiceType, "api-backup")},
},
SamenessGroup: "blah",
},
expectErr: `invalid "destinations" field: exactly one of destinations or sameness_group should be set`,
}
configCases["sameness without dest"] = configTestcase{
config: &pbcatalog.FailoverConfig{
SamenessGroup: "blah",
},
}
for name, tc := range configCases {
fpcases["plain config: "+name] = failoverTestcase{
failover: &pbcatalog.FailoverPolicy{
Config: proto.Clone(tc.config).(*pbcatalog.FailoverConfig),
},
expectErr: maybeWrap(`invalid "config" field: `, tc.expectErr),
}
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
fpcases["ported config: "+name] = failoverTestcase{
failover: &pbcatalog.FailoverPolicy{
PortConfigs: map[string]*pbcatalog.FailoverConfig{
"http": proto.Clone(tc.config).(*pbcatalog.FailoverConfig),
},
},
expectErr: maybeWrap(`invalid value of key "http" within port_configs: `, tc.expectErr),
}
}
}
func getCommonTestCases() map[string]failoverTestcase {
configCases := map[string]configTestcase{
"dest with sameness": {
config: &pbcatalog.FailoverConfig{
Destinations: []*pbcatalog.FailoverDestination{
{Ref: newRef(pbcatalog.ServiceType, "api-backup")},
},
SamenessGroup: "blah",
},
// TODO(v2): uncomment after this is supported
// expectErr: `invalid "destinations" field: exactly one of destinations or sameness_group should be set`,
expectErr: `invalid "sameness_group" field: not supported in this release`,
},
"dest without sameness": {
config: &pbcatalog.FailoverConfig{
Destinations: []*pbcatalog.FailoverDestination{
@ -246,13 +250,6 @@ func TestValidateFailoverPolicy(t *testing.T) {
},
},
},
"sameness without dest": {
config: &pbcatalog.FailoverConfig{
SamenessGroup: "blah",
},
// TODO(v2): remove after this is supported
expectErr: `invalid "sameness_group" field: not supported in this release`,
},
"regions without dest": {
config: &pbcatalog.FailoverConfig{
Regions: []string{"us-east1", "us-west2"},
@ -327,7 +324,7 @@ func TestValidateFailoverPolicy(t *testing.T) {
},
}
cases := map[string]testcase{
fpcases := map[string]failoverTestcase{
// emptiness
"empty": {
failover: &pbcatalog.FailoverPolicy{},
@ -391,22 +388,15 @@ func TestValidateFailoverPolicy(t *testing.T) {
},
}
maybeWrap := func(wrapPrefix, base string) string {
if base != "" {
return wrapPrefix + base
}
return ""
}
for name, tc := range configCases {
cases["plain config: "+name] = testcase{
fpcases["plain config: "+name] = failoverTestcase{
failover: &pbcatalog.FailoverPolicy{
Config: proto.Clone(tc.config).(*pbcatalog.FailoverConfig),
},
expectErr: maybeWrap(`invalid "config" field: `, tc.expectErr),
}
cases["ported config: "+name] = testcase{
fpcases["ported config: "+name] = failoverTestcase{
failover: &pbcatalog.FailoverPolicy{
PortConfigs: map[string]*pbcatalog.FailoverConfig{
"http": proto.Clone(tc.config).(*pbcatalog.FailoverConfig),
@ -415,6 +405,37 @@ func TestValidateFailoverPolicy(t *testing.T) {
expectErr: maybeWrap(`invalid value of key "http" within port_configs: `, tc.expectErr),
}
}
return fpcases
}
func TestValidateFailoverPolicy(t *testing.T) {
run := func(t *testing.T, tc failoverTestcase) {
res := resourcetest.Resource(pbcatalog.FailoverPolicyType, "api").
WithTenancy(resource.DefaultNamespacedTenancy()).
WithData(t, tc.failover).
Build()
require.NoError(t, MutateFailoverPolicy(res))
// Verify that mutate didn't actually change the object.
got := resourcetest.MustDecode[*pbcatalog.FailoverPolicy](t, res)
prototest.AssertDeepEqual(t, tc.failover, got.Data)
err := ValidateFailoverPolicy(res)
// Verify that validate didn't actually change the object.
got = resourcetest.MustDecode[*pbcatalog.FailoverPolicy](t, res)
prototest.AssertDeepEqual(t, tc.failover, got.Data)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
}
cases := getCommonTestCases()
addFailoverConfigSamenessGroupCases(cases)
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
@ -682,152 +703,7 @@ func TestSimplifyFailoverPolicy(t *testing.T) {
func TestFailoverPolicyACLs(t *testing.T) {
// Wire up a registry to generically invoke hooks
registry := resource.NewRegistry()
Register(registry)
newFailover := func(t *testing.T, name, tenancyStr string, destRefs []*pbresource.Reference) []*pbresource.Resource {
var dr []*pbcatalog.FailoverDestination
for _, destRef := range destRefs {
dr = append(dr, &pbcatalog.FailoverDestination{Ref: destRef})
}
res1 := resourcetest.Resource(pbcatalog.FailoverPolicyType, name).
WithTenancy(resourcetest.Tenancy(tenancyStr)).
WithData(t, &pbcatalog.FailoverPolicy{
Config: &pbcatalog.FailoverConfig{Destinations: dr},
}).
Build()
resourcetest.ValidateAndNormalize(t, registry, res1)
res2 := resourcetest.Resource(pbcatalog.FailoverPolicyType, name).
WithTenancy(resourcetest.Tenancy(tenancyStr)).
WithData(t, &pbcatalog.FailoverPolicy{
PortConfigs: map[string]*pbcatalog.FailoverConfig{
"http": {Destinations: dr},
},
}).
Build()
resourcetest.ValidateAndNormalize(t, registry, res2)
return []*pbresource.Resource{res1, res2}
}
type testcase struct {
res *pbresource.Resource
rules string
check func(t *testing.T, authz acl.Authorizer, res *pbresource.Resource)
readOK string
writeOK string
}
const (
DENY = resourcetest.DENY
ALLOW = resourcetest.ALLOW
DEFAULT = resourcetest.DEFAULT
)
serviceRef := func(tenancy, name string) *pbresource.Reference {
return newRefWithTenancy(pbcatalog.ServiceType, tenancy, name)
}
resOneDest := func(tenancy, destTenancy string) []*pbresource.Resource {
return newFailover(t, "api", tenancy, []*pbresource.Reference{
serviceRef(destTenancy, "dest1"),
})
}
resTwoDests := func(tenancy, destTenancy string) []*pbresource.Resource {
return newFailover(t, "api", tenancy, []*pbresource.Reference{
serviceRef(destTenancy, "dest1"),
serviceRef(destTenancy, "dest2"),
})
}
run := func(t *testing.T, name string, tc resourcetest.ACLTestCase) {
t.Run(name, func(t *testing.T) {
resourcetest.RunACLTestCase(t, tc, registry)
})
}
isEnterprise := versiontest.IsEnterprise()
serviceRead := func(partition, namespace, name string) string {
if isEnterprise {
return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "read" } } }`, partition, namespace, name)
}
return fmt.Sprintf(` service %q { policy = "read" } `, name)
}
serviceWrite := func(partition, namespace, name string) string {
if isEnterprise {
return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "write" } } }`, partition, namespace, name)
}
return fmt.Sprintf(` service %q { policy = "write" } `, name)
}
assert := func(t *testing.T, name string, rules string, resList []*pbresource.Resource, readOK, writeOK string) {
for i, res := range resList {
tc := resourcetest.ACLTestCase{
AuthCtx: resource.AuthorizerContext(res.Id.Tenancy),
Res: res,
Rules: rules,
ReadOK: readOK,
WriteOK: writeOK,
ListOK: DEFAULT,
}
run(t, fmt.Sprintf("%s-%d", name, i), tc)
}
}
tenancies := []string{"default.default"}
if isEnterprise {
tenancies = append(tenancies, "default.foo", "alpha.default", "alpha.foo")
}
for _, policyTenancyStr := range tenancies {
t.Run("policy tenancy: "+policyTenancyStr, func(t *testing.T) {
for _, destTenancyStr := range tenancies {
t.Run("dest tenancy: "+destTenancyStr, func(t *testing.T) {
for _, aclTenancyStr := range tenancies {
t.Run("acl tenancy: "+aclTenancyStr, func(t *testing.T) {
aclTenancy := resourcetest.Tenancy(aclTenancyStr)
maybe := func(match string, parentOnly bool) string {
if policyTenancyStr != aclTenancyStr {
return DENY
}
if !parentOnly && destTenancyStr != aclTenancyStr {
return DENY
}
return match
}
t.Run("no rules", func(t *testing.T) {
rules := ``
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), DENY, DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), DENY, DENY)
})
t.Run("api:read", func(t *testing.T) {
rules := serviceRead(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
t.Run("api:write", func(t *testing.T) {
rules := serviceWrite(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
t.Run("api:write dest1:read", func(t *testing.T) {
rules := serviceWrite(aclTenancy.Partition, aclTenancy.Namespace, "api") +
serviceRead(aclTenancy.Partition, aclTenancy.Namespace, "dest1")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), maybe(ALLOW, false))
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
})
}
})
}
})
}
testFailOverPolicyAcls(t, false)
}
func newRef(typ *pbresource.Type, name string) *pbresource.Reference {
@ -841,9 +717,3 @@ func newRefWithTenancy(typ *pbresource.Type, tenancyStr, name string) *pbresourc
WithTenancy(resourcetest.Tenancy(tenancyStr)).
Reference("")
}
func newRefWithPeer(typ *pbresource.Type, name string, peer string) *pbresource.Reference {
ref := newRef(typ, name)
ref.Tenancy.PeerName = peer
return ref
}

@ -15,6 +15,7 @@ func Register(r resource.Registry) {
RegisterHealthStatus(r)
RegisterFailoverPolicy(r)
RegisterNodeHealthStatus(r)
RegisterComputedFailoverPolicy(r)
// todo (v2): re-register once these resources are implemented.
//RegisterHealthChecks(r)
//RegisterDNSPolicy(r)

@ -0,0 +1,18 @@
// Code generated by protoc-gen-go-binary. DO NOT EDIT.
// source: pbcatalog/v2beta1/computed_failover_policy.proto
package catalogv2beta1
import (
"google.golang.org/protobuf/proto"
)
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *ComputedFailoverPolicy) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *ComputedFailoverPolicy) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}

@ -0,0 +1,209 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: pbcatalog/v2beta1/computed_failover_policy.proto
package catalogv2beta1
import (
_ "github.com/hashicorp/consul/proto-public/pbresource"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a Resource type.
type ComputedFailoverPolicy struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Config defines failover for any named port not present in PortConfigs.
Config *FailoverConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
// PortConfigs defines failover for a specific port on this service and takes
// precedence over Config.
PortConfigs map[string]*FailoverConfig `protobuf:"bytes,2,rep,name=port_configs,json=portConfigs,proto3" json:"port_configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *ComputedFailoverPolicy) Reset() {
*x = ComputedFailoverPolicy{}
if protoimpl.UnsafeEnabled {
mi := &file_pbcatalog_v2beta1_computed_failover_policy_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ComputedFailoverPolicy) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ComputedFailoverPolicy) ProtoMessage() {}
func (x *ComputedFailoverPolicy) ProtoReflect() protoreflect.Message {
mi := &file_pbcatalog_v2beta1_computed_failover_policy_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ComputedFailoverPolicy.ProtoReflect.Descriptor instead.
func (*ComputedFailoverPolicy) Descriptor() ([]byte, []int) {
return file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescGZIP(), []int{0}
}
func (x *ComputedFailoverPolicy) GetConfig() *FailoverConfig {
if x != nil {
return x.Config
}
return nil
}
func (x *ComputedFailoverPolicy) GetPortConfigs() map[string]*FailoverConfig {
if x != nil {
return x.PortConfigs
}
return nil
}
var File_pbcatalog_v2beta1_computed_failover_policy_proto protoreflect.FileDescriptor
var file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDesc = []byte{
0x0a, 0x30, 0x70, 0x62, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2f, 0x76, 0x32, 0x62, 0x65,
0x74, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x61, 0x69,
0x6c, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x20, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f,
0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x32, 0x62,
0x65, 0x74, 0x61, 0x31, 0x1a, 0x27, 0x70, 0x62, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2f,
0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x66, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72,
0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x70,
0x62, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xca, 0x02, 0x0a, 0x16,
0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x48, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f,
0x67, 0x2e, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76,
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x12, 0x6c, 0x0a, 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f,
0x67, 0x2e, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74,
0x65, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x2e, 0x50, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x0b, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x1a, 0x70,
0x0a, 0x10, 0x50, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e,
0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x76,
0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x3a, 0x06, 0xa2, 0x93, 0x04, 0x02, 0x08, 0x03, 0x42, 0xb1, 0x02, 0x0a, 0x24, 0x63, 0x6f, 0x6d,
0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75,
0x6c, 0x2e, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61,
0x31, 0x42, 0x1b, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x6f,
0x76, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01,
0x5a, 0x49, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x70, 0x62, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x2f, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x48, 0x43,
0x43, 0xaa, 0x02, 0x20, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f,
0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x56, 0x32, 0x62,
0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x20, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70,
0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5c,
0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2c, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63,
0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x43, 0x61, 0x74, 0x61, 0x6c,
0x6f, 0x67, 0x5c, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x23, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x43, 0x61, 0x74, 0x61,
0x6c, 0x6f, 0x67, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescOnce sync.Once
file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescData = file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDesc
)
func file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescGZIP() []byte {
file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescOnce.Do(func() {
file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescData = protoimpl.X.CompressGZIP(file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescData)
})
return file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDescData
}
var file_pbcatalog_v2beta1_computed_failover_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_pbcatalog_v2beta1_computed_failover_policy_proto_goTypes = []interface{}{
(*ComputedFailoverPolicy)(nil), // 0: hashicorp.consul.catalog.v2beta1.ComputedFailoverPolicy
nil, // 1: hashicorp.consul.catalog.v2beta1.ComputedFailoverPolicy.PortConfigsEntry
(*FailoverConfig)(nil), // 2: hashicorp.consul.catalog.v2beta1.FailoverConfig
}
var file_pbcatalog_v2beta1_computed_failover_policy_proto_depIdxs = []int32{
2, // 0: hashicorp.consul.catalog.v2beta1.ComputedFailoverPolicy.config:type_name -> hashicorp.consul.catalog.v2beta1.FailoverConfig
1, // 1: hashicorp.consul.catalog.v2beta1.ComputedFailoverPolicy.port_configs:type_name -> hashicorp.consul.catalog.v2beta1.ComputedFailoverPolicy.PortConfigsEntry
2, // 2: hashicorp.consul.catalog.v2beta1.ComputedFailoverPolicy.PortConfigsEntry.value:type_name -> hashicorp.consul.catalog.v2beta1.FailoverConfig
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_pbcatalog_v2beta1_computed_failover_policy_proto_init() }
func file_pbcatalog_v2beta1_computed_failover_policy_proto_init() {
if File_pbcatalog_v2beta1_computed_failover_policy_proto != nil {
return
}
file_pbcatalog_v2beta1_failover_policy_proto_init()
if !protoimpl.UnsafeEnabled {
file_pbcatalog_v2beta1_computed_failover_policy_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ComputedFailoverPolicy); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_pbcatalog_v2beta1_computed_failover_policy_proto_goTypes,
DependencyIndexes: file_pbcatalog_v2beta1_computed_failover_policy_proto_depIdxs,
MessageInfos: file_pbcatalog_v2beta1_computed_failover_policy_proto_msgTypes,
}.Build()
File_pbcatalog_v2beta1_computed_failover_policy_proto = out.File
file_pbcatalog_v2beta1_computed_failover_policy_proto_rawDesc = nil
file_pbcatalog_v2beta1_computed_failover_policy_proto_goTypes = nil
file_pbcatalog_v2beta1_computed_failover_policy_proto_depIdxs = nil
}

@ -0,0 +1,21 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
syntax = "proto3";
package hashicorp.consul.catalog.v2beta1;
import "pbcatalog/v2beta1/failover_policy.proto";
import "pbresource/annotations.proto";
// This is a Resource type.
message ComputedFailoverPolicy {
option (hashicorp.consul.resource.spec) = {scope: SCOPE_NAMESPACE};
// Config defines failover for any named port not present in PortConfigs.
FailoverConfig config = 1;
// PortConfigs defines failover for a specific port on this service and takes
// precedence over Config.
map<string, FailoverConfig> port_configs = 2;
}

@ -0,0 +1,27 @@
// Code generated by protoc-gen-deepcopy. DO NOT EDIT.
package catalogv2beta1
import (
proto "google.golang.org/protobuf/proto"
)
// DeepCopyInto supports using ComputedFailoverPolicy within kubernetes types, where deepcopy-gen is used.
func (in *ComputedFailoverPolicy) DeepCopyInto(out *ComputedFailoverPolicy) {
proto.Reset(out)
proto.Merge(out, proto.Clone(in))
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComputedFailoverPolicy. Required by controller-gen.
func (in *ComputedFailoverPolicy) DeepCopy() *ComputedFailoverPolicy {
if in == nil {
return nil
}
out := new(ComputedFailoverPolicy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new ComputedFailoverPolicy. Required by controller-gen.
func (in *ComputedFailoverPolicy) DeepCopyInterface() interface{} {
return in.DeepCopy()
}

@ -0,0 +1,22 @@
// Code generated by protoc-json-shim. DO NOT EDIT.
package catalogv2beta1
import (
protojson "google.golang.org/protobuf/encoding/protojson"
)
// MarshalJSON is a custom marshaler for ComputedFailoverPolicy
func (this *ComputedFailoverPolicy) MarshalJSON() ([]byte, error) {
str, err := ComputedFailoverPolicyMarshaler.Marshal(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for ComputedFailoverPolicy
func (this *ComputedFailoverPolicy) UnmarshalJSON(b []byte) error {
return ComputedFailoverPolicyUnmarshaler.Unmarshal(b, this)
}
var (
ComputedFailoverPolicyMarshaler = &protojson.MarshalOptions{}
ComputedFailoverPolicyUnmarshaler = &protojson.UnmarshalOptions{DiscardUnknown: false}
)

@ -10,19 +10,26 @@ const (
GroupName = "catalog"
Version = "v2beta1"
DNSPolicyKind = "DNSPolicy"
FailoverPolicyKind = "FailoverPolicy"
HealthChecksKind = "HealthChecks"
HealthStatusKind = "HealthStatus"
NodeKind = "Node"
NodeHealthStatusKind = "NodeHealthStatus"
ServiceKind = "Service"
ServiceEndpointsKind = "ServiceEndpoints"
VirtualIPsKind = "VirtualIPs"
WorkloadKind = "Workload"
ComputedFailoverPolicyKind = "ComputedFailoverPolicy"
DNSPolicyKind = "DNSPolicy"
FailoverPolicyKind = "FailoverPolicy"
HealthChecksKind = "HealthChecks"
HealthStatusKind = "HealthStatus"
NodeKind = "Node"
NodeHealthStatusKind = "NodeHealthStatus"
ServiceKind = "Service"
ServiceEndpointsKind = "ServiceEndpoints"
VirtualIPsKind = "VirtualIPs"
WorkloadKind = "Workload"
)
var (
ComputedFailoverPolicyType = &pbresource.Type{
Group: GroupName,
GroupVersion: Version,
Kind: ComputedFailoverPolicyKind,
}
DNSPolicyType = &pbresource.Type{
Group: GroupName,
GroupVersion: Version,

Loading…
Cancel
Save