Add policy api for Selector

Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
This commit is contained in:
Sergen Yalçın 2022-05-12 16:11:37 +03:00
parent 66e5e7ad0b
commit e2fb202fd5
No known key found for this signature in database
GPG Key ID: 1EC8D6C7CC79DB6B
4 changed files with 178 additions and 63 deletions

View File

@ -120,23 +120,19 @@ const (
// IsResolutionPolicyOptional checks whether the resolution policy of relevant reference is Optional. // IsResolutionPolicyOptional checks whether the resolution policy of relevant reference is Optional.
func (p *Policy) IsResolutionPolicyOptional() bool { func (p *Policy) IsResolutionPolicyOptional() bool {
if p != nil { if p == nil || p.Resolution == nil {
if p.Resolution != nil {
return *p.Resolution == ResolutionPolicyOptional
}
}
return false return false
} }
return *p.Resolution == ResolutionPolicyOptional
}
// IsResolvePolicyAlways checks whether the resolution policy of relevant reference is Always. // IsResolvePolicyAlways checks whether the resolution policy of relevant reference is Always.
func (p *Policy) IsResolvePolicyAlways() bool { func (p *Policy) IsResolvePolicyAlways() bool {
if p != nil { if p == nil || p.Resolve == nil {
if p.Resolve != nil {
return *p.Resolve == ResolvePolicyAlways
}
}
return false return false
} }
return *p.Resolve == ResolvePolicyAlways
}
// A Reference to a named object. // A Reference to a named object.
type Reference struct { type Reference struct {
@ -174,6 +170,10 @@ type Selector struct {
// MatchControllerRef ensures an object with the same controller reference // MatchControllerRef ensures an object with the same controller reference
// as the selecting object is selected. // as the selecting object is selected.
MatchControllerRef *bool `json:"matchControllerRef,omitempty"` MatchControllerRef *bool `json:"matchControllerRef,omitempty"`
// Policies of the referenced object.
// +optional
Policy *Policy `json:"policy,omitempty"`
} }
// SetGroupVersionKind sets the Kind and APIVersion of a TypedReference. // SetGroupVersionKind sets the Kind and APIVersion of a TypedReference.

View File

@ -459,6 +459,11 @@ func (in *Selector) DeepCopyInto(out *Selector) {
*out = new(bool) *out = new(bool)
**out = **in **out = **in
} }
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = new(Policy)
(*in).DeepCopyInto(*out)
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector.

View File

@ -114,11 +114,16 @@ type ResolutionRequest struct {
// processed. // processed.
func (rr ResolutionRequest) IsNoOp() bool { func (rr ResolutionRequest) IsNoOp() bool {
isAlways := false isAlways := false
if rr.Reference != nil { if rr.Selector != nil {
if rr.Selector.Policy.IsResolvePolicyAlways() {
isAlways = true
}
} else if rr.Reference != nil {
if rr.Reference.Policy.IsResolvePolicyAlways() { if rr.Reference.Policy.IsResolvePolicyAlways() {
isAlways = true isAlways = true
} }
} }
// We don't resolve values that are already set (if reference resolution policy // We don't resolve values that are already set (if reference resolution policy
// is not set to Always); we effectively cache resolved values. The CR author // is not set to Always); we effectively cache resolved values. The CR author
// can invalidate the cache and trigger a new resolution by explicitly clearing // can invalidate the cache and trigger a new resolution by explicitly clearing
@ -162,12 +167,19 @@ type MultiResolutionRequest struct {
// not be processed. // not be processed.
func (rr MultiResolutionRequest) IsNoOp() bool { func (rr MultiResolutionRequest) IsNoOp() bool {
isAlways := false isAlways := false
if rr.Selector != nil {
if rr.Selector.Policy.IsResolvePolicyAlways() {
isAlways = true
}
} else {
for _, r := range rr.References { for _, r := range rr.References {
if r.Policy.IsResolvePolicyAlways() { if r.Policy.IsResolvePolicyAlways() {
isAlways = true isAlways = true
break break
} }
} }
}
// We don't resolve values that are already set (if reference resolution policy // We don't resolve values that are already set (if reference resolution policy
// is not set to Always); we effectively cache resolved values. The CR author // is not set to Always); we effectively cache resolved values. The CR author
// can invalidate the cache and trigger a new resolution by explicitly clearing // can invalidate the cache and trigger a new resolution by explicitly clearing
@ -227,19 +239,10 @@ func (r *APIResolver) Resolve(ctx context.Context, req ResolutionRequest) (Resol
return ResolutionResponse{ResolvedValue: req.CurrentValue, ResolvedReference: req.Reference}, nil return ResolutionResponse{ResolvedValue: req.CurrentValue, ResolvedReference: req.Reference}, nil
} }
// The reference is already set - resolve it. // The selector is set - resolve it.
if req.Reference != nil { if req.Selector != nil {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name}, req.To.Managed); err != nil {
return ResolutionResponse{}, getResolutionError(req.Reference.Policy, errors.Wrap(err, errGetManaged))
}
rsp := ResolutionResponse{ResolvedValue: req.Extract(req.To.Managed), ResolvedReference: req.Reference}
return rsp, getResolutionError(req.Reference.Policy, rsp.Validate())
}
// The reference was not set, but a selector was. Select a reference.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil { if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil {
return ResolutionResponse{}, errors.Wrap(err, errListManaged) return ResolutionResponse{}, getResolutionError(req.Selector.Policy, errors.Wrap(err, errListManaged))
} }
for _, to := range req.To.List.GetItems() { for _, to := range req.To.List.GetItems() {
@ -248,11 +251,21 @@ func (r *APIResolver) Resolve(ctx context.Context, req ResolutionRequest) (Resol
} }
rsp := ResolutionResponse{ResolvedValue: req.Extract(to), ResolvedReference: &xpv1.Reference{Name: to.GetName()}} rsp := ResolutionResponse{ResolvedValue: req.Extract(to), ResolvedReference: &xpv1.Reference{Name: to.GetName()}}
return rsp, rsp.Validate() return rsp, getResolutionError(req.Selector.Policy, rsp.Validate())
} }
// We couldn't resolve anything. // We couldn't resolve anything.
return ResolutionResponse{}, errors.New(errNoMatches) return ResolutionResponse{}, getResolutionError(req.Selector.Policy, errors.New(errNoMatches))
}
// The selector was not set, check the reference.
if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name}, req.To.Managed); err != nil {
return ResolutionResponse{}, getResolutionError(req.Reference.Policy, errors.Wrap(err, errGetManaged))
}
rsp := ResolutionResponse{ResolvedValue: req.Extract(req.To.Managed), ResolvedReference: req.Reference}
return rsp, getResolutionError(req.Reference.Policy, rsp.Validate())
} }
// ResolveMultiple resolves the supplied MultiResolutionRequest. The returned // ResolveMultiple resolves the supplied MultiResolutionRequest. The returned
@ -264,23 +277,10 @@ func (r *APIResolver) ResolveMultiple(ctx context.Context, req MultiResolutionRe
return MultiResolutionResponse{ResolvedValues: req.CurrentValues, ResolvedReferences: req.References}, nil return MultiResolutionResponse{ResolvedValues: req.CurrentValues, ResolvedReferences: req.References}, nil
} }
// The references are already set - resolve them. // The selector is set - resolve it.
if len(req.References) > 0 { if req.Selector != nil {
vals := make([]string, len(req.References))
for i := range req.References {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.References[i].Name}, req.To.Managed); err != nil {
return MultiResolutionResponse{}, getResolutionError(req.References[i].Policy, errors.Wrap(err, errGetManaged))
}
vals[i] = req.Extract(req.To.Managed)
}
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: req.References}
return rsp, rsp.Validate()
}
// No references were set, but a selector was. Select and resolve references.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil { if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil {
return MultiResolutionResponse{}, errors.Wrap(err, errListManaged) return MultiResolutionResponse{}, getResolutionError(req.Selector.Policy, errors.Wrap(err, errListManaged))
} }
items := req.To.List.GetItems() items := req.To.List.GetItems()
@ -296,7 +296,21 @@ func (r *APIResolver) ResolveMultiple(ctx context.Context, req MultiResolutionRe
} }
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: refs} rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: refs}
return rsp, getResolutionError(req.Selector.Policy, rsp.Validate())
}
// The selector was not set, check the references.
vals := make([]string, len(req.References))
for i := range req.References {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.References[i].Name}, req.To.Managed); err != nil {
return MultiResolutionResponse{}, getResolutionError(req.References[i].Policy, errors.Wrap(err, errGetManaged))
}
vals[i] = req.Extract(req.To.Managed)
}
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: req.References}
return rsp, rsp.Validate() return rsp, rsp.Validate()
} }
func getResolutionError(p *xpv1.Policy, err error) error { func getResolutionError(p *xpv1.Policy, err error) error {

View File

@ -138,8 +138,8 @@ func TestResolve(t *testing.T) {
err: nil, err: nil,
}, },
}, },
"AlwaysResolve": { "AlwaysResolveReference": {
reason: "Should not return early if the current value is non-zero, when the resolution policy is set to" + reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always", "Always",
c: &test.MockClient{ c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
@ -234,7 +234,7 @@ func TestResolve(t *testing.T) {
}, },
}, },
}, },
"OptionalPolicy": { "OptionalReference": {
reason: "No error should be returned when the resolution policy is Optional", reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{ c: &test.MockClient{
MockGet: test.NewMockGetFn(nil), MockGet: test.NewMockGetFn(nil),
@ -287,6 +287,25 @@ func TestResolve(t *testing.T) {
err: errors.New(errNoMatches), err: errors.New(errNoMatches),
}, },
}, },
"OptionalSelector": {
reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: &fake.Managed{},
args: args{
req: ResolutionRequest{
Selector: &xpv1.Selector{
Policy: &xpv1.Policy{Resolution: &optionalPolicy},
},
To: To{List: &FakeManagedList{}},
},
},
want: want{
rsp: ResolutionResponse{},
err: nil,
},
},
"SuccessfulSelect": { "SuccessfulSelect": {
reason: "A managed resource with a matching controller reference should be selected and returned", reason: "A managed resource with a matching controller reference should be selected and returned",
c: &test.MockClient{ c: &test.MockClient{
@ -313,6 +332,35 @@ func TestResolve(t *testing.T) {
err: nil, err: nil,
}, },
}, },
"AlwaysResolveSelector": {
reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: ResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
Policy: &xpv1.Policy{Resolve: &alwaysPolicy},
},
To: To{List: &FakeManagedList{Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
}}},
Extract: ExternalName(),
CurrentValue: "oldValue",
},
},
want: want{
rsp: ResolutionResponse{
ResolvedValue: value,
ResolvedReference: &xpv1.Reference{Name: value},
},
err: nil,
},
},
} }
for name, tc := range cases { for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@ -379,8 +427,8 @@ func TestResolveMultiple(t *testing.T) {
err: nil, err: nil,
}, },
}, },
"AlwaysResolve": { "AlwaysResolveReference": {
reason: "Should not return early if the current value is non-zero, when the resolution policy is set to" + reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always", "Always",
c: &test.MockClient{ c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
@ -476,7 +524,7 @@ func TestResolveMultiple(t *testing.T) {
}, },
}, },
}, },
"OptionalPolicy": { "OptionalReference": {
reason: "No error should be returned when the resolution policy is Optional", reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{ c: &test.MockClient{
MockGet: test.NewMockGetFn(nil), MockGet: test.NewMockGetFn(nil),
@ -530,6 +578,25 @@ func TestResolveMultiple(t *testing.T) {
err: errors.New(errNoMatches), err: errors.New(errNoMatches),
}, },
}, },
"OptionalSelector": {
reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: &fake.Managed{},
args: args{
req: MultiResolutionRequest{
Selector: &xpv1.Selector{
Policy: &xpv1.Policy{Resolution: &optionalPolicy},
},
To: To{List: &FakeManagedList{}},
},
},
want: want{
rsp: MultiResolutionResponse{},
err: nil,
},
},
"SuccessfulSelect": { "SuccessfulSelect": {
reason: "A managed resource with a matching controller reference should be selected and returned", reason: "A managed resource with a matching controller reference should be selected and returned",
c: &test.MockClient{ c: &test.MockClient{
@ -556,6 +623,35 @@ func TestResolveMultiple(t *testing.T) {
err: nil, err: nil,
}, },
}, },
"AlwaysResolveSelector": {
reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: MultiResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
Policy: &xpv1.Policy{Resolve: &alwaysPolicy},
},
To: To{List: &FakeManagedList{Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
}}},
Extract: ExternalName(),
CurrentValues: []string{"oldValue"},
},
},
want: want{
rsp: MultiResolutionResponse{
ResolvedValues: []string{value},
ResolvedReferences: []xpv1.Reference{{Name: value}},
},
err: nil,
},
},
} }
for name, tc := range cases { for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {