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

Fix support for label selectors #971

Merged

Conversation

baijum
Copy link
Member

@baijum baijum commented Jun 4, 2021

  • Fix .spec.application.name field becoming mandatory
  • If more than one application is selected through label selectors, all will be considered for binding

Fixes #965

Copy link
Contributor

@pedjak pedjak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few more unit tests are missing:

  • when both application name and selector are set
  • when label selector is used but no application is annotated so the result is empty
  • there is an error at performing List call

and acceptance tests demonstrating the usage of the label selector and the situation when both name and label selector are set.

if i.serviceBinding.Spec.Application.Name != "" &&
i.serviceBinding.Spec.Application.LabelSelector != nil &&
i.serviceBinding.Spec.Application.LabelSelector.MatchLabels != nil {
return nil, errs.New("Both label selector and name for application should be used")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean - "should not be used"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 5326c6f


}
if i.serviceBinding.Spec.Application.LabelSelector != nil && i.serviceBinding.Spec.Application.LabelSelector.MatchLabels != nil {
gvr, err := i.typeLookup.ResourceForReferable(ref)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this lines repeats also in line 130 - so it could be moved before back to the original place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 5326c6f

}

if len(objList.Items) == 0 {
return nil, errs.New("application not found")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the contract of this method is to return empty array, not array if not applications are not found, see the doc:

Applications() ([]Application, error)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning nil returns an empty slice of Application objects.
See this example: https://play.golang.org/p/QvQLT9CH3Fg

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, nice. However, according to the contract, we should not return error in this place, just empty slice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed and added a test case for the same: 588535c

@@ -91,6 +92,51 @@ var _ = Describe("Context", func() {
Entry("no binding path specified", nil, defaultContainerPath),
Entry("binding path specified", &v1alpha1.BindingPath{ContainersPath: "foo.bar"}, "foo.bar"),
)
DescribeTable("should return slice of size 1 if application is specifiedt through label seclector", func(bindingPath *v1alpha1.BindingPath, expectedContainerPath string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo "specifiedt"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be also more interesting to cover the case when there at least two applications with the same label - with that we check if the loop in https://github.com/redhat-developer/service-binding-operator/pull/971/files#diff-0168dc7ac8aa540873af78cf38f886f6a5ace89fb75dce9880490cedbed1d46eR157 really works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 5326c6f

@codecov
Copy link

codecov bot commented Jun 4, 2021

Codecov Report

Merging #971 (5e91f6d) into master (d4fd4b7) will increase coverage by 0.44%.
The diff coverage is 100.00%.

❗ Current head 5e91f6d differs from pull request most recent head 5446f72. Consider uploading reports for the commit 5446f72 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##           master     #971      +/-   ##
==========================================
+ Coverage   54.27%   54.72%   +0.44%     
==========================================
  Files          23       23              
  Lines        1227     1239      +12     
==========================================
+ Hits          666      678      +12     
  Misses        473      473              
  Partials       88       88              
Impacted Files Coverage Δ
pkg/reconcile/pipeline/context/impl.go 70.67% <100.00%> (+2.90%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d4fd4b7...5446f72. Read the comment docs.

@pedjak
Copy link
Contributor

pedjak commented Jun 4, 2021

@baijum can you think about some better commit message/PR summary describing what was done and how?

@isutton
Copy link
Contributor

isutton commented Jun 5, 2021 via email

@baijum baijum changed the title Fix application.name field became mandatory Fix support for label selectors Jun 7, 2021
@baijum
Copy link
Member Author

baijum commented Jun 7, 2021

@baijum can you think about some better commit message/PR summary describing what was done and how?

I have updated the PR title and description. It can be used for the commit message after squashing the commits.

@baijum baijum closed this Jun 7, 2021
@baijum baijum reopened this Jun 7, 2021
@baijum
Copy link
Member Author

baijum commented Jun 7, 2021

In this particular case, 'nil' is an un-initialized slice of a Application items, not an empty initialized slice of Application items.

Done. 538772c

@baijum
Copy link
Member Author

baijum commented Jun 7, 2021

and acceptance tests demonstrating the usage of the label selector and the situation when both name and label selector are set.

I missed this. I will work on this.

@baijum
Copy link
Member Author

baijum commented Jun 8, 2021

and acceptance tests demonstrating the usage of the label selector and the situation when both name and label selector are set.

I missed this. I will work on this.

Done. 5e472be

@baijum
Copy link
Member Author

baijum commented Jun 8, 2021

  • when both application name and selector are set

Done. 7debb12

@baijum
Copy link
Member Author

baijum commented Jun 8, 2021

  • there is an error at performing List call

I couldn't figure out how to produce an error in the List call. Let me know if we can skip this for now.

Copy link
Contributor

@pedjak pedjak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be good to have an additonal acceptance test demonstrating how to bind 2 applications having the same label.

Comment on lines 131 to 132
if err != nil {
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO this check does not have to be guarded by i.serviceBinding.Spec.Application.Name != "" condition because in line 129 we get just GroupVersionResource struct, Name does play any role in that call.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. af6e990

}

if len(objList.Items) == 0 {
return nil, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we return here also emptyResult, nill, similar to what we have in line 150?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. af6e990

@@ -91,7 +92,175 @@ var _ = Describe("Context", func() {
Entry("no binding path specified", nil, defaultContainerPath),
Entry("binding path specified", &v1alpha1.BindingPath{ContainersPath: "foo.bar"}, "foo.bar"),
)
DescribeTable("should return slice of size 1 if application is specified through label seclector", func(bindingPath *v1alpha1.BindingPath, expectedContainerPath string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test "should return slice of size 2 if 2 applications are specified through label seclector" covers the logic tested here, hence we could drop this test, because it does not introduce any new testing path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. af6e990

pkg/reconcile/pipeline/context/impl_test.go Show resolved Hide resolved
Comment on lines 238 to 208
ref := v1alpha1.Application{
Ref: v1alpha1.Ref{
Group: "app",
Version: "v1",
Kind: "Foo",
Name: "app1",
},
LabelSelector: ls,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be dropped from the test setup, because it is not used anywhere in the test later on.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test is to check the effect of two kinds of application references: label & name.
Even though it is not directly used, this setup is required to validate the functionality.

ctx := &impl{client: client, serviceBinding: &sb, typeLookup: typeLookup}

_, err := ctx.Applications()
Expect(err).To(HaveOccurred())
Copy link
Contributor

@pedjak pedjak Jun 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but which error has occurred? we need to assert that as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. af6e990

@@ -0,0 +1,111 @@
Feature: Bind an application with label selector to a service
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we really consider this as a separate feature? this is all about referring application resources using attached labels

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a separate feature file for the label selector is fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you backup your statement with some reason?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with moving to another file. Please suggest the file.

Given Namespace [TEST_NAMESPACE] is used
* Service Binding Operator is running

Scenario: Bind an application with label selector to backend service
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

application resource has labels attached, but label selector is used inside service binding to define what application should be bound. Could we be a bit more verbose about the setup and the expected outcome? For example:

Bind successfully an application owning a given label to a service

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. af6e990

And The application env var "BACKEND_USERNAME" has value "AzureDiamond"
And The application env var "BACKEND_PASSWORD" has value "hunter2"

Scenario: Attempt to bind an application with label selector and name to a service
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rephrase to include the outcome as well.. in this case service binding will be unsuccessful, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. af6e990


Then jq ".status.conditions[] | select(.type=="CollectionReady").status" of Service Binding "service-binding-a-s-e" should be changed to "True"
And jq ".status.conditions[] | select(.type=="InjectionReady").status" of Service Binding "service-binding-a-s-e" should be changed to "False"
And jq ".status.conditions[] | select(.type=="InjectionReady").reason" of Service Binding "service-binding-a-s-e" should be changed to "ApplicationNotFound"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO - ApplicationNotFound is not correct reason - simply we do not allow name and label to cooexist - Hence, something like InvalidApplicationReference should be more appropriate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need some more time to figure out how to introduce a new failure reason.

@pedjak
Copy link
Contributor

pedjak commented Jun 8, 2021

I couldn't figure out how to produce an error in the List call. Let me know if we can skip this for now.

@baijum use PrependReactor on the fake client and define for what verb and resource you would like to have an error. Something like: kubernetes/client-go#742 (comment)

@baijum
Copy link
Member Author

baijum commented Jun 8, 2021

I couldn't figure out how to produce an error in the List call. Let me know if we can skip this for now.

@baijum use PrependReactor on the fake client and define for what verb and resource you would like to have an error. Something like: kubernetes/client-go#742 (comment)

Done. ee27a52

@pmacik
Copy link
Contributor

pmacik commented Jun 8, 2021

@baijum FYI, PR checks fail because of an issue with the workflows. This PR #973 fixes that.

@baijum
Copy link
Member Author

baijum commented Jun 8, 2021

it would be good to have an additonal acceptance test demonstrating how to bind 2 applications having the same label.

I will work on this.

}

for index, _ := range objList.Items {
i.applications = append(i.applications, &application{gvr: gvr, persistedResource: &(objList.Items[index]), bindingPath: i.serviceBinding.Spec.Application.BindingPath})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The persistedResource value (address of the item) is taken using the index.
Ref. https://play.golang.org/p/ePs7st6f91g

Comment on lines 143 to 147
Expect(applications[0].Resource()).To(Equal(u1))
Expect(applications[0].ContainersPath()).To(Equal(expectedContainerPath))
Expect(applications[1].Resource()).To(Equal(u2))
Expect(applications[1].ContainersPath()).To(Equal(expectedContainerPath))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we can/should really assert on the order of returned application instances. What is only interesting for us that the number of returned instances is 2 and one of them is u2 and another one is u2, it order in the slice is not important. If the order changes suddenly for some reason (say fake client implementation changes), the test would fail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order is not reliable and not part of the API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check Omega assertion library.

Copy link
Member Author

@baijum baijum Jun 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 52aca7f

if i.serviceBinding.Spec.Application.Name != "" &&
i.serviceBinding.Spec.Application.LabelSelector != nil &&
i.serviceBinding.Spec.Application.LabelSelector.MatchLabels != nil {
return nil, errs.New("Both label selector and name for application should not be used")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can extract error into a var that can be used later in test assertion - instead of string copying.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created separate issue: #974

Comment on lines 219 to 223
u := &unstructured.Unstructured{}
u.SetName("app")
u.SetNamespace(sb.Namespace)
u.SetGroupVersionKind(schema.GroupVersionKind{Group: "app", Version: "v1", Kind: "Foo"})
u.SetLabels(map[string]string{"env": "stage"})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do not need to create any app object at all, since we are going to fail earlier. Omitting this will also implicitly check that we do not try to access the cluster for this scenario.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code has been removed. Ref. #974

_, err := ctx.Applications()
Expect(err).To(HaveOccurred())
// FIXME: This is not the expected error message
Expect(err.Error()).To(ContainSubstring("no kind is registered for the type v1.DeploymentList in scheme"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the error should be the same as the one you put in line 263: "Error listing foo"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, that error message is not coming. I have used client.PrependReactor (line 261-264) as per earlier suggestion, so the listing failed as expected.
I tried to use unstructured.Unstructured{} instead of v1.DeploymentList{}, but that also seems to be not working. I will come back to this issue once everything else finished.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 8229485

@baijum
Copy link
Member Author

baijum commented Jun 9, 2021

it would be good to have an additonal acceptance test demonstrating how to bind 2 applications having the same label.

I will work on this.

Done. 53bb65a

client := fake.NewSimpleDynamicClient(runtime.NewScheme())
client.PrependReactor("list", "foos",
func(action testing.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, e.New("Error listing foo")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY: please extract error in a variable and use it later in the assertion

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 5e91f6d


_, err := ctx.Applications()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Error listing foo"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error we get must be the one we got from the client, and the message should be the identical. Such assertion assures that the error is propagated to the caller:

Expect(err).To(Equal(expectedError))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 5e91f6d

@@ -122,6 +262,7 @@ var _ = Describe("Context", func() {
Expect(err).To(HaveOccurred())
Expect(errors.IsNotFound(err)).To(BeTrue())
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert unrelated whitespace change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 5e91f6d

@then(u'The application env var "{name}" has value "{value}" in both apps')
def check_env_var_value_in_both_apps(context, name, value):
found = polling2.poll(lambda: context.application1.get_env_var_value(name) == value, step=5, timeout=400)
assert found, f'Env var "{name}" should contain value "{value}" in {context.application1}'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we reach this line, then found is true always - otherwise Timeout exception occurs. Hence, this and the similar line at line 108 can be removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 5e91f6d

Copy link
Contributor

@pedjak pedjak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 after having required CI jobs passing. Please also squash the commits.

@baijum baijum force-pushed the label-selector branch 3 times, most recently from 6023281 to 0676cc0 Compare June 10, 2021 07:25
- Fix `.spec.application.name` field becoming mandatory
- If more than one application is selected through label selectors, all
  will be considered for binding

Fixes redhat-developer#965

Signed-off-by: Baiju Muthukadan <baiju.m.mail@gmail.com>
@pedjak
Copy link
Contributor

pedjak commented Jun 10, 2021

/lgtm
/approve

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jun 10, 2021

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: pedjak

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@baijum
Copy link
Member Author

baijum commented Jun 10, 2021

/test 4.8-acceptance

@openshift-merge-robot openshift-merge-robot merged commit 847b684 into redhat-developer:master Jun 10, 2021
@baijum baijum deleted the label-selector branch June 11, 2021 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

application.name field became mandatory/redundant in release 0.7.x
5 participants